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

Posted

技术标签:

【中文标题】循环遍历双引号但忽略单引号中的双引号【英文标题】:Loop through double quotes but ignore double quotes in single quotes 【发布时间】:2013-10-31 23:29:56 【问题描述】:

我猜是逻辑问题。我正在使用 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

因为“花”在单引号内,我希望我的输出是:

hello john
good night

编辑

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

【问题讨论】:

这听起来像是有限状态机的工作。 我尝试在谷歌上搜索有限状态机,但没有接受过计算机工程方面的正式培训,我必须承认我有点迷茫。你有什么额外的建议吗? 问题未明确。 1 " 2 ' 3 " 4 " 5 ' 6 " 7 呢。您想忽略 1 因为它在引号之外。你想产生2 ' 3 然后忽略4,产生5 ' 6,还是你想说单引号内的所有东西都被忽略了,所以这产生2 6 根据具体情况,这可能是下推自动机的工作,而不是有限自动机。 Eric,你提出的第一个解决方案就是我要找的。​​span> 【参考方案1】:

我尝试在谷歌上搜索有限状态机,但没有接受过正式的计算机工程培训,我必须承认我有点迷茫。你有什么补充的指点吗?

FSM 是最简单的计算机形式之一。这个想法是您拥有有限数量的“状态”信息和稳定的输入流。每个输入都会导致状态以可预测的方式发生变化,仅基于当前状态和当前输入,并导致可预测的输出发生。

因此,假设您的输入是单个字符,而您的输出是单个字符或“空”字符。这是一个可以满足您需求的 FSM:

状态为OUTSIDEINSIDEDOUBLEINSIDESINGLE。 输入为"'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,保持OUTSIDEOUTSIDE 得到",发出null,转到INSIDEDOUBLEINSIDEDOUBLE 得到2,发出2,保持INSIDEDOUBLE INSIDEDOUBLE 得到",发出null,转到OUTSIDEOUTSIDE 得到3,发出null,保持OUTSIDEOUTSIDE 得到',发出null,去INSIDESINGLE

...其余的自己填写。

你写代码的草图就够了吗?

【讨论】:

谢谢,我明白了。回家后试试这个! "如果我们是 INSIDEDOUBLE 并得到 ",则转到 OUTSIDEDOUBLE 并发出 null" 我们不应该转到 OUTSIDE 吗?应该只有 3 个状态不?对于 INSIDESINGLE 和 get 'go OUTSIDESINGLE 也是如此? 【参考方案2】:

很好的解决方案;对于小型 FSM,使用 switch 语句是执行此操作的传统方法,但是当状态和输入的数量变得庞大而复杂时,它就会变得笨拙。这是一个更易于扩展的替代解决方案:表格驱动解决方案。也就是把关于transitions和actions的事实放到一个数组中,然后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"))

【讨论】:

我喜欢这个!用户有一个问题,Eric 提供了伪代码,并且对于更改,用户实际上能够生成一个有效的 C# 代码。作为蛋糕上的一颗樱桃,Eric 提供了一个更好的实现。我希望关于 SO 的每个问题/答案都是这样的。 @SolutionYogi:谢谢——虽然我不会说“更好”,我只会说“不同”。正如 perl 程序员所说,TIMTOWTDIBSCINABTE。 你太谦虚了。 :) 顺便说一句,我还使用 FSM 库发布了一个答案。这是更多的代码,但有助于更好地记录 FSM,将不胜感激您的 cmets。 Eric,如果您有几分钟的时间,能否请您看看我对这个代码审查问题的回答:codereview.stackexchange.com/questions/33152/… 原始问题中的代码正是我 5 年前写的。但是由于您的回答/博客文章,我已经成为一个更好的开发人员(恕我直言),如果您有任何额外的 cmets,我将不胜感激。 @SolutionYogi:对我来说看起来很合理!【参考方案3】:

非常感谢 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 是寻求此类建议的地方。 谢谢。实际上,我自己使用了一个 int ,只是放了一个字符串,以使其对其他人更清楚。但你是对的,我应该使用枚举!【参考方案4】:

只是为了好玩,我决定使用一个名为stateless 的非常轻量级的 FSM 库来解决这个问题。

如果您要使用此库,代码将如下所示。

就像 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;
    

【讨论】:

这是一个非常流畅的界面。我应该看看那个图书馆。 @EricLippert 很高兴您喜欢它。对于更复杂的需求,我肯定会推荐Solid State:code.google.com/p/solid-state它的灵感来自于无状态库。

以上是关于循环遍历双引号但忽略单引号中的双引号的主要内容,如果未能解决你的问题,请参考以下文章

单引号中的双引号,反之亦然

JavaScript中的双引号与单引号[重复]

Oracle中的单引号怎么转义

单引号字符串中的双引号 - NodeJs

Prettier 不适用于将可视代码中的双引号更改为单引号

核心数据关系映射:值表达式中的双引号自动变成单引号