【问题标题】:How do you dynamically identify unknown delimiters in a data file?如何动态识别数据文件中的未知分隔符?
【发布时间】:2011-04-26 12:18:01
【问题描述】:

我有三个输入数据文件。每个都对其中包含的数据使用不同的分隔符。数据文件一如下所示:

苹果 |香蕉| 高分辨率照片| CLIPARTO橘子| 高分辨率照片| CLIPARTO葡萄

数据文件二长这样:

25 美分硬币、1 美分硬币、1 美分硬币

数据文件三长这样:

马牛猪鸡山羊

(列数的变化也是有意的)

我的想法是计算非字母字符的数量,并假设最高计数是分隔符。但是,具有非空格分隔符的文件在分隔符之前和之后也有空格,因此空格在所有三个文件上都占优势。这是我的代码:

def count_chars(s):
    valid_seps=[' ','|',',',';','\t']
    cnt = {}
    for c in s:
        if c in valid_seps: cnt[c] = cnt.get(c,0) + 1
    return cnt

infile = 'pipe.txt' #or 'comma.txt' or 'space.txt'
records = open(infile,'r').read()
print count_chars(records)

它将打印一个包含所有可接受字符计数的字典。在每种情况下,空格总是获胜,所以我不能依靠它来告诉我分隔符是什么。

但我想不出更好的方法来做到这一点。

有什么建议吗?

【问题讨论】:

    标签: python parsing csv text-files textinput


    【解决方案1】:

    试试 Python CSV 的标准怎么样:http://docs.python.org/library/csv.html#csv.Sniffer

    import csv
    
    sniffer = csv.Sniffer()
    dialect = sniffer.sniff('quarter, dime, nickel, penny')
    print dialect.delimiter
    # returns ','
    

    【讨论】:

    • 哦。那个很有趣!它在 2.6 版中可用吗?
    • +1:绝对使用 csv 模块。解析分隔文件,特别是如果它们可能包含转义分隔符、引用字符串中的分隔符、引用字符串中的换行符等,对于正则表达式来说是行不通的。正则表达式解决方案迟早会失败,并且错误会很微妙且令人麻木。
    • 这是一个很好的答案——但它不适用于 OPs 第一个示例。 apples | bananas | oranges | grapes 的输入声称分隔符是 ' '。如果您删除管道周围的空间,它将按预期工作。
    • 这是 csv 模块上的一个有趣的功能,但要小心,如果您将 ; 作为分隔符(csv 的另一个常见分隔符)并且任何其他值都有逗号,则Sniffer 将返回 , 作为分隔符。示例 sniffer.sniff('quarter,cent;dime;nickel;penny').delimiter 将返回 ,
    • 但是,如果您对分隔符的外观有所了解,可以设置具有优先权的分隔符:sniffer.preferred = [';', '|']
    【解决方案2】:

    如果您使用的是 python,我建议您使用所有有效的预期分隔符调用re.split

    >>> l = "big long list of space separated words"
    >>> re.split(r'[ ,|;"]+', l)
    ['big', 'long', 'list', 'of', 'space', 'separated', 'words']
    

    唯一的问题是如果其中一个文件使用分隔符作为数据的一部分。

    如果您必须识别分隔符,最好的办法是计算除空格之外的所有内容。如果几乎没有出现,那么它可能是空格,否则,它是映射字符的最大值。

    不幸的是,真的没有办法确定。您可能有用逗号填充的空格分隔数据,或者您可能有 |用分号填充的分隔数据。它可能并不总是有效。

    【讨论】:

    • 这并不能真正解决问题。
      在这种情况下,我最终得到的是文件中的每个字符都拆分为自己的列表,例如:“['a'] ['p'] ['p'] ['l'] ['e'] ['s'] [' '] ['|'](...等等...)。相反,我想要的是将每一行分成一个列表,例如“[ “苹果”、“香蕉”、“橙子”、“葡萄”]"
    • 我假设您正在尝试识别分隔符,以便您可以分隔数据。为什么要识别分隔符?
    • @Greg Gauthier:非常抱歉。我的意思是说 re.split。我已更改答案以反映正确的方法。
    • infile = 'Data/pipe.txt' records = open(infile,'r').read() for line in records: print line.split('|, ; \t')
    • @Greg Gauthier,您可以尝试在正则表达式中添加一个 +(见答案)。然后它将匹配连续的分隔符并删除大部分空列表项。
    【解决方案3】:

    由于空格的问题,我最终选择了正则表达式。这是我完成的代码,以防万一有人感兴趣,或者可以在其中使用其他任何东西。顺便说一句,找到一种动态识别列顺序的方法会很巧妙,但我意识到这有点棘手。与此同时,我正在使用旧技巧来解决这个问题。

    for infile in glob.glob(os.path.join(self._input_dir, self._file_mask)):
                #couldn't quite figure out a way to make this a single block 
                #(rather than three separate if/elifs. But you can see the split is
                #generalized already, so if anyone can come up with a better way,
                #I'm all ears!! :)
                for row in open(infile,'r').readlines():
                    if infile.find('comma') > -1: 
                        datefmt = "%m/%d/%Y"
                        last, first, gender, color, dobraw = \
                                [x.strip() for x in re.split(r'[ ,|;"\t]+', row)]
                    elif infile.find('space') > -1: 
                        datefmt = "%m-%d-%Y"
                        last, first, unused, gender, dobraw, color = \
                                [x.strip() for x in re.split(r'[ ,|;"\t]+', row)]
    elif infile.find('pipe') > -1: datefmt = "%m-%d-%Y" last, first, unused, gender, color, dobraw = \ [x.strip() for x in re.split(r'[ ,|;"\t]+', row)] #There is also a way to do this with csv.Sniffer, but the #spaces around the pipe delimiter also confuse sniffer, so #I couldn't use it. else: raise ValueError(infile + "is not an acceptable input file.")

    【讨论】:

      【解决方案4】:

      我们可以根据一些先验信息(例如常用分隔符列表)和所有行给出相同数量的分隔符的频率计数来确定分隔符的正确性

      def head(filename: str, n: int):
          try:
              with open(filename) as f:
                  head_lines = [next(f).rstrip() for x in range(n)]
          except StopIteration:
              with open(filename) as f:
                  head_lines = f.read().splitlines()
          return head_lines
      
      
      def detect_delimiter(filename: str, n=2):
          sample_lines = head(filename, n)
          common_delimiters= [',',';','\t',' ','|',':']
          for d in common_delimiters:
              ref = sample_lines[0].count(d)
              if ref > 0:
                  if all([ ref == sample_lines[i].count(d) for i in range(1,n)]):
                      return d
          return ','
      

      通常 n=2 行就足够了,请检查更多行以获得更可靠的答案。当然,有些情况(通常是人为的)会导致错误检测,但在实践中不太可能发生。

      这里我使用了一个高效的 p​​ython 实现的 head 函数,它只读取文件的第 n 行。在How to read first N-lines of a file 上查看我的回答

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-02-09
        • 1970-01-01
        • 2020-06-02
        • 2018-03-10
        • 1970-01-01
        相关资源
        最近更新 更多