如何在排序列表中将具有相似开头的字符串分组?

3 投票
4 回答
1677 浏览
提问于 2025-04-18 04:14

给定这个:

['2014\\2014-01 Jan\\2014-01-01',
 '2014\\2014-01 Jan\\2014-01-02',
 '2014\\2014-01 Jan\\2014-01-03',
 '2014\\2014-01 Jan\\2014-01-04',
 '2014\\2014-01 Jan\\2014-01-05',
 '2014\\2014-01 Jan\\2014-01-06',
 '2014\\2014-01 Jan\\2014-01-07',
 '2014\\2014-01 Jan\\2014-01-08',
 '2014\\2014-01 Jan\\2014-01-09',
 '2014\\2014-01 Jan\\2014-01-10',
 '2014\\2014-01 Jan\\2014-01-11',
 '2014\\2014-01 Jan\\2014-01-12',
 '2014\\2014-01 Jan\\2014-01-13',
 '2014\\2014-01 Jan\\2014-01-14',
 '2014\\2014-01 Jan\\2014-01-15',
 '2014\\2014-01 Jan\\2014-01-16',
 '2014\\2014-01 Jan\\2014-01-17',
 '2014\\2014-01 Jan\\2014-01-18',
 '2014\\2014-01 Jan\\2014-01-19',
 '2014\\2014-01 Jan\\2014-01-20',
 '2014\\2014-01 Jan\\2014-01-21',
 '2014\\2014-01 Jan\\2014-01-22',
 '2014\\2014-01 Jan\\2014-01-23',
 '2014\\2014-01 Jan\\2014-01-24',
 '2014\\2014-01 Jan\\2014-01-25',
 '2014\\2014-01 Jan\\2014-01-26',
 '2014\\2014-01 Jan\\2014-01-27',
 '2014\\2014-01 Jan\\2014-01-28',
 '2014\\2014-01 Jan\\2014-01-29',
 '2014\\2014-01 Jan\\2014-01-30',
 '2014\\2014-01 Jan\\2014-01-31',
 '2014\\2014-02 Feb\\2014-02-01',
 '2014\\2014-02 Feb\\2014-02-02',
 '2014\\2014-02 Feb\\2014-02-03',
 '2014\\2014-02 Feb\\2014-02-04',
 '2014\\2014-02 Feb\\2014-02-05',
 '2014\\2014-02 Feb\\2014-02-06',
 '2014\\2014-02 Feb\\2014-02-07',
 '2014\\2014-02 Feb\\2014-02-08',
 '2014\\2014-02 Feb\\2014-02-09',
 '2014\\2014-02 Feb\\2014-02-10',
 '2014\\2014-02 Feb\\2014-02-11',
 '2014\\2014-02 Feb\\2014-02-12',
 '2014\\2014-02 Feb\\2014-02-13',
 '2014\\2014-02 Feb\\2014-02-14',
 '2014\\2014-02 Feb\\2014-02-15',
 '2014\\2014-02 Feb\\2014-02-16',
 '2014\\2014-02 Feb\\2014-02-17',
 '2014\\2014-02 Feb\\2014-02-18',
 '2014\\2014-02 Feb\\2014-02-19']

你怎么能得到像这样的结果?(解决方案1:基于分隔符,用户可以自定义分隔符)

['2014\\2014-01 Jan\\2014-01-01',
 '                 \\2014-01-02',
 '                 \\2014-01-03',
 '                 \\2014-01-04',
 '                 \\2014-01-05',
 '                 \\2014-01-06',
 '                 \\2014-01-07',
 '                 \\2014-01-08',
 '                 \\2014-01-09',
 '                 \\2014-01-10',
 '                 \\2014-01-11',
 '                 \\2014-01-12',
 '                 \\2014-01-13',
 '                 \\2014-01-14',
 '                 \\2014-01-15',
 '                 \\2014-01-16',
 '                 \\2014-01-17',
 '                 \\2014-01-18',
 '                 \\2014-01-19',
 '                 \\2014-01-20',
 '                 \\2014-01-21',
 '                 \\2014-01-22',
 '                 \\2014-01-23',
 '                 \\2014-01-24',
 '                 \\2014-01-25',
 '                 \\2014-01-26',
 '                 \\2014-01-27',
 '                 \\2014-01-28',
 '                 \\2014-01-29',
 '                 \\2014-01-30',
 '                 \\2014-01-31',
 '    \\2014-02 Feb\\2014-02-01',
 '                 \\2014-02-02',
 '                 \\2014-02-03',
 '                 \\2014-02-04',
 '                 \\2014-02-05',
 '                 \\2014-02-06',
 '                 \\2014-02-07',
 '                 \\2014-02-08',
 '                 \\2014-02-09',
 '                 \\2014-02-10',
 '                 \\2014-02-11',
 '                 \\2014-02-12',
 '                 \\2014-02-13',
 '                 \\2014-02-14',
 '                 \\2014-02-15',
 '                 \\2014-02-16',
 '                 \\2014-02-17',
 '                 \\2014-02-18',
 '                 \\2014-02-19']

我经常遇到这种情况,基本上我有一串字符串,想通过去掉开头重复的部分来让它们更容易看懂。现在我知道,这种树形输出是用来正常浏览文件夹的,但这些并不是真正的文件夹,只是列表中的字符串。

理想情况下,这个功能应该能接受一个层级分隔符,或者直接按字符处理(分隔符=None)。

def printheirarchy(data,seperator=","):

字符级别的输出应该像这样:(解决方案2:逐字符处理)

['2014\\2014-01 Jan\\2014-01-01',
 '                            2',
 '                            3',
 '                            4',
 '                            5',
 '                            6',
 '                            7',
 '                            8',
 '                            9',
 '                           10',
 '                            1',
 '                            2',
 '                            3',
 '                            4',
 '                            5',
 '                            6',
 '                            7',
 '                            8',
 '                            9',
 '                           20',
 '                            1',
 '                            2',
 '                            3',
 '                            4',
 '                            5',
 '                            6',
 '                            7',
 '                            8',
 '                            9',
 '                           30',
 '                            1',
 '            2 Feb\\2014-02-01',
 '                            2',
 '                            3',
 '                            4',
 '                            5',
 '                            6',
 '                            7',
 '                            8',
 '                            9',
 '                           10',
 '                            1',
 '                            2',
 '                            3',
 '                            4',
 '                            5',
 '                            6',
 '                            7',
 '                            8',
 '                            9']

在这个例子中,这似乎没什么用,但在分析网址、日志等时就很明显了。理想情况下,你可以把相似的部分变成灰色,而不是直接去掉它们,但我甚至不知道该从哪里开始。(或者反过来,把不同的部分加粗)。基本上,你是在比较每个元素和前一个元素,突出显示不同的地方,同时抑制相似的部分。

我搜索过,发现很多接近这个的选项,但没有完全符合的。os.path.commonprefix就是一个例子。也许可以用difflib?

这样做的价值在于减少在查看项目列表时的视觉杂乱。

相关问题:

4 个回答

0

好的,受到这个问题中关于公共前缀的回答的启发,我玩了一会儿,灵感来了,我意识到每次可以只发送一个包含两个元素的列表!

这是我的代码,这段代码只处理逐个字符的情况,我不太确定这个方法好不好(我怀疑效果不太好!因为会有很多不必要的复制)。不过我成功地重现了我问题中的第三个输出。至于其他部分,还是没有解决。

def printheirarchy(data,seperator=","):
    if len(data) < 2:
        pprint(data)
        return
    newdata = []
    newdata.append(data[0])
    for i in range(1,len(data)):
        prefix = os.path.commonprefix(data[i-1:i+1])
        newdata.append(data[i].replace(prefix," "*len(prefix),1))
    pprint(newdata)
1

在编程中,有时候我们需要处理一些数据,这些数据可能来自不同的地方,比如用户输入、文件或者网络请求。为了让程序能够理解和使用这些数据,我们通常会将它们转换成一种统一的格式。

这种转换的过程就叫做“解析”。简单来说,就是把复杂的数据结构变得简单易懂。比如说,如果你收到了一段文本,里面包含了很多信息,你需要把这些信息提取出来,才能在程序中使用。

解析的方式有很多种,具体取决于数据的格式。有些数据是以JSON格式存储的,这是一种很常见的轻量级数据交换格式,易于人阅读和编写,也容易被机器解析和生成。

在解析数据时,我们可能会用到一些工具或者库,这些工具可以帮助我们快速而准确地完成解析工作。这样,我们就可以把注意力集中在程序的其他部分,而不是花时间去处理数据的细节。

总之,解析就是把复杂的数据变简单,让程序能够轻松使用这些数据。

from difflib import SequenceMatcher

def remove_redundant_prefixes(it):
    """
    remove_redundant_prefixes(it) -> iterable (generator)

        Iterate through a list of strings, removing successive common prefixes.
    """
    prev_line = ''
    for line in sorted(it):
        sm = SequenceMatcher(a=prev_line, b=line)
        prev_line = line

        # Returns 3 element tuple, last element is the size of the match.
        match_size = sm.get_matching_blocks()[0][2]

        # No match == no prefix, don't prune the string.
        if match_size == 0:
            yield line
        else:
            # Prune per the match
            yield line.replace(line[:match_size], ' ' * match_size, 1)
4

看起来你想要重新发明一种叫做基数树的东西。

无论如何,这里有一个简单的生成器:

def grouped(iterable):
    prefix = None
    for i in iterable:
        pre, suf = i[:16], i[16:]
        if pre != prefix:
            prefix = pre
            yield pre + suf
        else:
            yield " " * 16 + suf
2

这是个不错的问题。我们来看看这个简单的解决方案:

def commonPrefix(a, b):
  i = 0
  while i < len(a) and i < len(b) and a[i] == b[i]:
    i += 1
  return i

def eachWithPrefix(v):
  p = ''
  for x in v:
    yield commonPrefix(p, x), x
    p = x

现在你可以选择你想要的内容:

list(eachWithPrefix(v))

这个会返回一个你的值的列表,每个值会说明有多少个字符和前一行相等,所以

print '\n'.join(' '*p + x[p:] for p, x in eachWithPrefix(v))

会打印出你提出的第二个解决方案。

print '\n'.join('\t' * p + '\\'.join(x[p:]) for p, x in eachWithPrefix(x.split('\\') for x in v))

另一方面,这个会对分隔符\执行相同的操作,并把要省略的部分替换成制表符。虽然这和你在第一个输出示例中提出的格式不完全一样,但我想你明白我的意思。

试试:

print '\n'.join('\\'.join([ s if i >= p else ' '*len(s) for i, s in enumerate(x) ]) for p, x in eachWithPrefix(x.split('\\') for x in v))

这个会把相等的部分替换成大小相同的空字符串。不过,输出中仍然会包含分隔符,但也许这样更好:

2014\2014-01 Jan\2014-01-01
    \           \2014-01-02
    \           \2014-01-03
    \           \2014-01-04
    \           \2014-01-05
...
    \           \2014-01-31
    \2014-02 Feb\2014-02-01
    \           \2014-02-02
    \           \2014-02-03
...

如果你想把这些也去掉,可以用这个方法:

print '\n'.join(' ' * len('\\'.join(x[:p])) + '\\'.join(x)[len('\\'.join(x[:p])):] for p, x in eachWithPrefix(x.split('\\') for x in v))

不过这个现在有些代码重复,所以也许用一个循环会更好:

for p, x in eachWithPrefix(x.split('\\') for x in v):
  s = '\\'.join(x)
  c = '\\'.join(x[:p])
  print ' '*len(c) + s[len(c):]

或者用一个简单易用的生成器:

def heirarchy(data, separator=","):
  for p, x in eachWithPrefix(x.split(separator) if separator else list(x) for x in data):
    s = separator.join(x)
    c = separator.join(x[:p])
    yield ' '*len(c) + s[len(c):]

所以现在heirarchy(data, separator='\\')就能生成你期待的输出了。

撰写回答