循环遍历双引号但忽略单引号中的双引号。

3

我很可能遇到了一个逻辑问题。我正在使用C#进行编码,但欢迎提供一般的伪代码解决方案。

我有这些文本文件,例如包含以下文本:

blah "hello john"
blah 'the code is "flower" '
blah "good night"

我想循环遍历双引号并对其进行某些操作,但我希望忽略单引号中包含的双引号。我通过以下方式(使用包含文本文件内容的string data)获取开头和结尾双引号的位置:

C#

// Start searching from beginning
int quotestart = 0, quoteend = 0;

while (data.IndexOf('"', quotestart) != -1)
{
  // Get opening double quote
  quotestart = data.IndexOf('"', quotestart);
  // Get ending double quote
  quoteend = data.IndexOf('"', quotestart + 1);

  string sub = data.Substring(quotestart + 1, quoteend - quotestart - 1);
  Console.WriteLine(sub);

  // Set the start position for the next round
  quotestart = quoteend + 1;
}

使用我的代码,输出将是:
hello john
flower
good night

因为“flower”在单引号内,所以我希望我的输出结果是:
hello john
good night

编辑

我目前正在研究一种方法,即首先使用例如“ A”填充单引号之间的所有数据。这样,当我遍历双引号时,任何在单引号之间的数据都将被忽略。不确定这是否是正确的方法。


这听起来像是有限状态机的工作。 - Eric Lippert
我尝试谷歌有限状态机,但由于我没有计算机工程的正式培训,我必须承认我有点迷失。你有任何额外的指针吗? - Juicy
问题未详细说明。1 " 2 ' 3 " 4 " 5 ' 6 " 7 这个怎么处理呢?你想忽略掉引号外面的 1,然后是要输出 2 ' 3 再忽略掉 4,输出 5 ' 6,还是认为单引号里面的都要被忽略,输出 2 6 - Eric Lippert
根据情况,这可能需要使用下推自动机来完成,而不是有限状态自动机。 - Eric Lippert
Eric,你提出的第一个解决方案正是我所需要的。 - Juicy
4个回答

7
我尝试谷歌有限状态机,但由于没有计算机工程的正式培训,我必须承认我有点迷失。你有任何额外的指导吗?
FSM是最简单的计算机之一。思路是你有一个有限数量的“状态”信息和一个稳定的输入流。每个输入都会以可预测的方式改变状态,仅基于当前状态和当前输入,并导致可预测的输出发生。
因此,想象一下你的输入是单个字符,而你的输出是单个字符或“null”字符。这里是一个做你想要的FSM:
- 状态为“OUTSIDE”,“INSIDEDOUBLE”和“INSIDESINGLE”。 - 输入为“”,“'”和“x”。 (WOLOG让x代表任何其他字符。)
我们有三种状态和三种输入,因此有九种可能的组合。
- 如果我们在“OUTSIDE”并获得“x”,则保持“OUTSIDE”并发出“null”。 - 如果我们在“OUTSIDE”并获得“”,则转到“INSIDEDOUBLE”并发出“null”。 - 如果我们在“OUTSIDE”并获得“'”,则进入“INSIDESINGLE”并发出“null”。 - 如果我们在“INSIDEDOUBLE”并获得“x”,则保持“INSIDEDOUBLE”并发出“x”。 - 如果我们在“INSIDEDOUBLE”并获得“”,则转到“OUTSIDE”并发出“null”。 - 如果我们在“INSIDEDOUBLE”并获得“'”,则保持“INSIDEDOUBLE”并发出“'”。 - 如果我们在“INSIDESINGLE”并获得“x”,则保持“INSIDESINGLE”并发出“null”。 - 如果我们在“INSIDESINGLE”并获得“”,则保持“INSIDESINGLE”并发出“null”。 - 如果我们在“INSIDESINGLE”并获得“'”,则转到“OUTSIDE”并发出“null”。
唯一剩下的就是说开始状态是“OUTSIDE”。
假设输入为1 " 2 " 3 ' 4 " 5 " ' 6,状态和输出如下:
  • OUTSIDE 得到 1,发出 null,保持 OUTSIDE
  • OUTSIDE 得到 ",发出 null,进入 INSIDEDOUBLE 状态。
  • INSIDEDOUBLE 得到 2,发出 2,保持 INSIDEDOUBLE
  • INSIDEDOUBLE 得到 ",发出 null,进入 OUTSIDE 状态。
  • OUTSIDE 得到 3,发出 null,保持 OUTSIDE
  • OUTSIDE 得到 ',发出 null,进入 INSIDESINGLE 状态。
... 其余部分由您自己填充。
这个草图足够让您编写代码了吗?

谢谢,我懂了。我回家后会试试这个! - Juicy
如果我们处于 INSIDEDOUBLE 并且获取到 ",则跳出双引号并发出 null。难道我们不应该跳出去吗?状态只有三种,对于 INSIDESINGLE 也是如此,获取 ' 跳出单引号。 - Juicy

5

很好的解决方案。在小型FSMs中,使用switch语句是传统的做法,但当状态和输入数量变得庞大和复杂时,它变得难以处理。这里有一种更易于扩展的替代方案:表驱动解决方案。也就是说,将有关转换和操作的事实放入数组中,然后FSM只是一系列数组查找而已:

// States
const int Outside = 0;
const int InDouble = 1;
const int InSingle = 2;

// Inputs
const int Other = 0;
const int DoubleQuote = 1;
const int SingleQuote = 2;

static readonly int[,] stateTransitions =
{   /*               Other     DoubleQ   SingleQ */
    /* Outside */  { Outside,  InDouble, InSingle },
    /* InDouble */ { InDouble, Outside,  InDouble },
    /* InSingle */ { InSingle, InSingle, Outside }
};

// Do we emit the character or ignore it?
static readonly bool[,] actions =
{   /*              Other   DoubleQ SingleQ */
    /* Outside */ { false,  false,  false },
    /* InDouble */{ true,   false,  true  },
    /* InSingle */{ false,  false,  false }
};

static int Classify(char c)
{
    switch (c)
    {
        case '\'': return SingleQuote;
        case '\"': return DoubleQuote;
        default: return Other;
    }
}

static IEnumerable<char> FSM(IEnumerable<char> inputs)
{
    int state = Outside;
    foreach (char input in inputs)
    {
        int kind = Classify(input);
        if (actions[state, kind]) 
            yield return input;
        state = stateTransitions[state, kind];
    }
}

现在我们可以使用以下方法获取结果:

string.Join("", FSM(@"1""2'3""4""5'6""7'8""9""A'B"))

1
我喜欢这个!用户有一个问题,Eric提供了伪代码,结果用户实际上能够生成一个可工作的C#代码。而且作为锦上添花,Eric还提供了一个更好的实现。我希望SO上的每一个问题/答案都像这样。 - SolutionYogi
@SolutionYogi:谢谢,虽然我不会说“更好”,但我只会说“不同”。就像Perl程序员所说的那样,TIMTOWTDIBSCINABTE。 - Eric Lippert
1
你太谦虚了。 :) 顺便说一下,我也使用了一个FSM库来发布一个答案。这需要更多的代码,但有助于更好地记录FSM,请希望获得您的评论。 - SolutionYogi
Eric,如果您有几分钟的时间,能否请您查看我的代码审查问题的答案:http://codereview.stackexchange.com/questions/33152/function-that-builds-dictionary-based-on-lambda-params/ 。原始问题中的代码正是我5年前所写。但由于您的回答/博客文章,我已经成为了一个更好的开发人员(在我看来),如果您有任何其他意见,我将不胜感激。 - SolutionYogi
@SolutionYogi:在我看来,看起来相当合理! - Eric Lippert
谢谢你,Eric,我非常感激! - SolutionYogi

2
感谢Eric Lippert提供了这个解决方案的逻辑。我提供下面的C#解决方案,以防有人需要。它有一些不必要的重新分配,我只是为了清晰起见而保留了它们。
string state = "outside";

for (int i = 0; i < data.Length; i++)
{
    c = data[i];
    switch (state)
    {
        case "outside":
            switch (c)
            {
                case '\'':
                    state = "insidesingle";
                    break;
                case '"':
                    state = "insidedouble";
                    break;
                default:
                    state = "outside";
                    break;
            }
            break;

        case "insidedouble":
            switch (c)
            {
                case '\'':
                    state = "insidedouble";
                    Console.Write(c);
                    break;
                case '"':
                    state = "outside";
                    break;
                default:
                    state = "insidedouble";
                    Console.Write(c);
                    break;
            }
            break;  

        case "insidesingle":
            switch (c)
            {
                case '\'':
                    state = "outside";
                    break;
                case '"':
                    state = "insidesingle";
                    break;
                default:
                    state = "insidesingle";
                    break;
            }
            break;
    }
}

使用枚举变量来代替字符串变量作为状态变量会是一个更好的选择。还有其他优化代码的方式,但是codereview.SE是你寻求建议的地方。 - Brian
谢谢。实际上我自己用的是 int,只不过加了一个字符串以使其对其他人更清晰。但你是对的,我应该使用枚举! - Juicy

2
只是为了好玩,我决定使用一个非常轻量级的FSM库stateless来解决这个问题。
如果你要使用这个库,代码看起来会像这样。
和Eric的解决方案一样,下面的代码可以轻松地根据新的需求进行更改。
void Main()
{
    Console.WriteLine(string.Join("", GetCharacters(@"1""2'3""4""5'6""7'8""9""A'B")));  
}

public enum CharacterType
{
    Other,
    SingleQuote,
    DoubleQuote
}

public enum State
{
    OutsideQuote,
    InsideSingleQuote,
    InsideDoubleQuote
}

public IEnumerable<char> GetCharacters(string input)
{
    //Initial state of the machine is OutSideQuote.
    var sm = new StateMachine<State, CharacterType>(State.OutsideQuote);

    //Below, we configure state transitions.
    //For a given state, we configure how CharacterType 
    //transitions state machine to a new state.

    sm.Configure(State.OutsideQuote)
        .Ignore(CharacterType.Other)        
        //If you are outside quote and you receive a double quote, 
        //state transitions to InsideDoubleQuote.
        .Permit(CharacterType.DoubleQuote, State.InsideDoubleQuote)
        //If you are outside quote and you receive a single quote,
        //state transitions to InsideSingleQuote.
        //Same logic applies for other state transitions below.
        .Permit(CharacterType.SingleQuote, State.InsideSingleQuote);

    sm.Configure(State.InsideDoubleQuote)
        .Ignore(CharacterType.Other)
        .Ignore(CharacterType.SingleQuote)
        .Permit(CharacterType.DoubleQuote, State.OutsideQuote);

    sm.Configure(State.InsideSingleQuote)
        .Ignore(CharacterType.Other)
        .Ignore(CharacterType.DoubleQuote)
        .Permit(CharacterType.SingleQuote, State.OutsideQuote);

    foreach (var character in input)
    {
        var characterType = GetCharacterType(character);
        sm.Fire(characterType);
        if(sm.IsInState(State.InsideDoubleQuote) && characterType != CharacterType.DoubleQuote)
            yield return character;
    }       

}

public CharacterType GetCharacterType(char input)
{
    switch (input)
    {
        case '\'': return CharacterType.SingleQuote;
        case '\"': return CharacterType.DoubleQuote;
        default: return CharacterType.Other;
    }
}

1
这是一个不错的流畅接口。我应该研究一下那个库。 - Eric Lippert
@EricLippert 很高兴你喜欢它。对于更复杂的需求,我一定会推荐 Solid State:https://code.google.com/p/solid-state/ 它是受到无状态库的启发。 - SolutionYogi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接