【问题标题】:Split a string ignoring quoted sections拆分字符串忽略引用的部分
【发布时间】:2010-09-05 14:14:34
【问题描述】:

给定这样的字符串:

a,"string, with",various,"values, and some",quoted

有什么好的算法可以根据逗号分割,而忽略引用部分中的逗号?

输出应该是一个数组:

[ "a", "string, with", "various", "values, and some", "quoted" ]

【问题讨论】:

  • 如果原始字符串中出现奇数个引号怎么办?
  • 这意味着输入字符串引用不正确,因此可以选择抛出异常。
  • 如何在引号内加上引号?

标签: language-agnostic parsing csv


【解决方案1】:

当然,使用 CSV 解析器会更好,但只是为了好玩:

Loop on the string letter by letter.
    If current_letter == quote : 
        toggle inside_quote variable.
    Else if (current_letter ==comma and not inside_quote) : 
        push current_word into array and clear current_word.
    Else 
        append the current_letter to current_word
When the loop is done push the current_word into array 

【讨论】:

    【解决方案2】:

    作者在这里放入了一段 C# 代码来处理您遇到问题的场景:

    CSV File Imports in .Net

    翻译应该不会太难。

    【讨论】:

      【解决方案3】:

      如果我选择的语言没有提供一种不假思索的方法,那么我最初会考虑两个选项作为简单的解决方法:

      1. 预解析并用另一个控制字符替换字符串中的逗号,然后拆分它们,然后对数组进行后解析,以用逗号替换之前使用的控制字符。

        李>
      2. 或者将它们用逗号分隔,然后将生成的数组后解析到另一个数组中,检查每个数组条目上的前导引号并将条目连接起来,直到达到终止引号。

      但是,这些都是技巧,如果这是纯粹的“心理”练习,那么我怀疑它们将被证明是无用的。如果这是一个现实世界的问题,那么了解该语言将有助于我们提供一些具体的建议。

      【讨论】:

      • 我正在寻找一种类似问题的算法,我必须处理巨大的文本文件(以 GB 为单位)。这些文本文件包含合格的数据,即字段/记录分隔符是用单引号/双引号括起来的数据的一部分。我正在寻找可以帮助我并行(通过多个线程)处理这些文件的算法。我们使用的语言是Java。如果您有任何建议,请告诉我
      【解决方案4】:

      我用它来解析字符串,不确定它在这里是否有帮助;但也许有一些小的修改?

      function getstringbetween($string, $start, $end){
          $string = " ".$string;
          $ini = strpos($string,$start);
          if ($ini == 0) return "";
          $ini += strlen($start);   
          $len = strpos($string,$end,$ini) - $ini;
          return substr($string,$ini,$len);
      }
      
      $fullstring = "this is my [tag]dog[/tag]";
      $parsed = getstringbetween($fullstring, "[tag]", "[/tag]");
      
      echo $parsed; // (result = dog) 
      

      /mp

      【讨论】:

        【解决方案5】:

        这是一个简单的算法:

        1. 判断字符串是否以'"'字符开头
        2. 将字符串拆分为由'"' 字符分隔的数组。
        3. 用占位符标记引用的逗号#COMMA#
          • 如果输入以 '"' 开头,则在数组中标记索引 % 2 == 0 的那些项
          • 否则标记数组中索引 % 2 == 1 的那些项
        4. 连接数组中的项目以形成修改后的输入字符串。
        5. 将字符串拆分为由',' 字符分隔的数组。
        6. 将#COMMA# 占位符数组中的所有实例替换为',' 字符。
        7. 数组是您的输出。

        以下是 python 实现:
        (固定处理'"a,b",c,"d,e,f,h","i,j,k"')

        def parse_input(input):
        
            quote_mod = int(not input.startswith('"'))
        
            input = input.split('"')
            for item in input:
                if item == '':
                    input.remove(item)
            for i in range(len(input)):
                if i % 2 == quoted_mod:
                    input[i] = input[i].replace(",", "#COMMA#")
        
            input = "".join(input).split(",")
            for item in input:
                if item == '':
                    input.remove(item)
            for i in range(len(input)):
                input[i] = input[i].replace("#COMMA#", ",")
            return input
        
        # parse_input('a,"string, with",various,"values, and some",quoted')
        #  -> ['a,string', ' with,various,values', ' and some,quoted']
        # parse_input('"a,b",c,"d,e,f,h","i,j,k"')
        #  -> ['a,b', 'c', 'd,e,f,h', 'i,j,k']
        

        【讨论】:

          【解决方案6】:

          这是标准 CSV 样式的解析。很多人尝试使用正则表达式来做到这一点。使用正则表达式可以达到大约 90%,但您确实需要一个真正的 CSV 解析器才能正确执行此操作。几个月前我发现了一个我强烈推荐的fast, excellent C# CSV parser on CodeProject!

          【讨论】:

          • 当然,.NET 框架中也有一个。即使它在 Microsoft.VisualBasic 中,您仍然可以从 C# 中使用它。 msdn.microsoft.com/en-us/library/…
          • 谢谢!作为一个 C# 人,我总是忘记有一堆有用的 VB 库可供我使用。老实说,我认为它们的名字很糟糕,因为它们并不是真正的 VB。它们只是 .NET。
          【解决方案7】:

          这是一个伪代码(又名 Python),一次性完成:-P

          def parsecsv(instr):
              i = 0
              j = 0
          
              outstrs = []
          
              # i is fixed until a match occurs, then it advances
              # up to j. j inches forward each time through:
          
              while i < len(instr):
          
                  if j < len(instr) and instr[j] == '"':
                      # skip the opening quote...
                      j += 1
                      # then iterate until we find a closing quote.
                      while instr[j] != '"':
                          j += 1
                          if j == len(instr):
                              raise Exception("Unmatched double quote at end of input.")
          
                  if j == len(instr) or instr[j] == ',':
                      s = instr[i:j]  # get the substring we've found
                      s = s.strip()    # remove extra whitespace
          
                      # remove surrounding quotes if they're there
                      if len(s) > 2 and s[0] == '"' and s[-1] == '"':
                          s = s[1:-1]
          
                      # add it to the result
                      outstrs.append(s)
          
                      # skip over the comma, move i up (to where
                      # j will be at the end of the iteration)
                      i = j+1
          
                  j = j+1
          
              return outstrs
          
          def testcase(instr, expected):
              outstr = parsecsv(instr)
              print outstr
              assert expected == outstr
          
          # Doesn't handle things like '1, 2, "a, b, c" d, 2' or
          # escaped quotes, but those can be added pretty easily.
          
          testcase('a, b, "1, 2, 3", c', ['a', 'b', '1, 2, 3', 'c'])
          testcase('a,b,"1, 2, 3" , c', ['a', 'b', '1, 2, 3', 'c'])
          
          # odd number of quotes gives a "unmatched quote" exception
          #testcase('a,b,"1, 2, 3" , "c', ['a', 'b', '1, 2, 3', 'c'])
          

          【讨论】:

            【解决方案8】:

            看起来你在这里得到了一些很好的答案。

            对于那些希望自己处理 CSV 文件解析的人,请听从专家的建议和 Don't roll your own CSV parser。

            您的第一个想法是,“我需要处理引号内的逗号。”

            你的下一个想法是,“哦,废话,我需要处理引号内的引号。转义引号。双引号。单引号......”

            这是一条通往疯狂的道路。不要自己写。找到一个具有广泛单元测试覆盖率的库,该库涵盖了所有困难的部分,并为你经历了地狱。对于 .NET,请使用免费的 FileHelpers 库。

            【讨论】:

            • secretgeek 上的一个很棒的链接 - 非常有趣。但它只能回答那些遗憾地使用 .NET 的人的问题。
            • 真;尽管该建议适用于所有地方的开发人员:不要扮演自己的 CSV 解析器的角色。 Ruby 有一个内置的,并且有 Python、C++ 和大多数广泛使用的语言的库。
            • 虽然 SecretGeek 似乎不知道它,但 VB.NET 也内置了一个。 msdn.microsoft.com/en-us/library/…
            【解决方案9】:

            我忍不住想看看我是否可以让它在 Python 单行中工作:

            arr = [i.replace("|", ",") for i in re.sub('"([^"]*)\,([^"]*)"',"\g<1>|\g<2>", str_to_test).split(",")]
            

            返回 ['a', 'string, with', 'various', 'values, and some', 'quoted']

            首先将引号内的“,”替换为另一个分隔符 (|), 在 ',' 上拆分字符串并替换 |再次分隔符。

            【讨论】:

            • 你怎么知道没有 |在原始字符串中?在带引号的字符串中转义引号怎么样?
            【解决方案10】:

            Python:

            import csv
            reader = csv.reader(open("some.csv"))
            for row in reader:
                print row
            

            【讨论】:

            • 我认为这是最好的答案。正是我需要的!
            【解决方案11】:

            如果出现奇数个引号怎么办 在原始字符串中?

            这看起来很像 CSV 解析,它在处理引用字段时有一些特殊性。只有当字段用双引号分隔时,该字段才会被转义,所以:

            field1, "field2, field3", field4, "field5, field6" field7

            变成

            字段1

            字段2,字段3

            字段4

            “字段5

            field6" 字段7

            注意,如果它不以引号开头和结尾,那么它不是带引号的字段,双引号被简单地视为双引号。

            如果我没记错的话,我链接到的代码实际上并没有正确处理这个问题。

            【讨论】:

              【解决方案12】:

              这是一个基于 Pat 伪代码的简单 python 实现:

              def splitIgnoringSingleQuote(string, split_char, remove_quotes=False):
                  string_split = []
                  current_word = ""
                  inside_quote = False
                  for letter in string:
                    if letter == "'":
                      if not remove_quotes:
                         current_word += letter
                      if inside_quote:
                        inside_quote = False
                      else:
                        inside_quote = True
                    elif letter == split_char and not inside_quote:
                      string_split.append(current_word)
                      current_word = ""
                    else:
                      current_word += letter
                  string_split.append(current_word)
                  return string_split
              

              【讨论】:

                【解决方案13】:

                既然你说语言不可知论,我尽可能用最接近伪代码的语言编写我的算法:

                def find_character_indices(s, ch):
                    return [i for i, ltr in enumerate(s) if ltr == ch]
                
                
                def split_text_preserving_quotes(content, include_quotes=False):
                    quote_indices = find_character_indices(content, '"')
                
                    output = content[:quote_indices[0]].split()
                
                    for i in range(1, len(quote_indices)):
                        if i % 2 == 1: # end of quoted sequence
                            start = quote_indices[i - 1]
                            end = quote_indices[i] + 1
                            output.extend([content[start:end]])
                
                        else:
                            start = quote_indices[i - 1] + 1
                            end = quote_indices[i]
                            split_section = content[start:end].split()
                            output.extend(split_section)
                
                        output += content[quote_indices[-1] + 1:].split()                                                                 
                
                    return output
                

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2015-09-14
                  • 2018-10-26
                  • 1970-01-01
                  • 2013-04-18
                  • 1970-01-01
                  • 2015-12-27
                  相关资源
                  最近更新 更多