在多层可迭代对象中查找匹配字符串(Python)

1 投票
3 回答
566 浏览
提问于 2025-04-15 15:09

假设我们有一个多层的可迭代对象,最里面的层级是一些字符串。没错,字符串是可以被迭代的,但我想你明白我的意思:

['something', 
('Diff',
('diff', 'udiff'),
('*.diff', '*.patch'),
('text/x-diff', 'text/x-patch')),

('Delphi',
('delphi', 'pas', 'pascal', 'objectpascal'),
('*.pas',),
('text/x-pascal',['lets', 'put one here'], )),

('JavaScript+Mako',
('js+mako', 'javascript+mako'),
('application/x-javascript+mako',
'text/x-javascript+mako',
'text/javascript+mako')),
...
]

有没有什么方便的方法可以让我实现一个搜索,找出匹配字符串的索引?我想要的效果类似于这样(上面的列表是 data):

>>> grep('javascript', data)

然后它会返回 [ (2,1,1), (2,2,0), (2,2,1), (2,2,2) ] 之类的结果。也许我错过了某种类似的解决方案,它没有返回这样的结果,但能帮助我在一个多层的可迭代对象中找到一些字符串。

我写了一点代码,但感觉有些幼稚和不优雅,所以我想在这里问问。我想我可以像一开始那样继续嵌套异常,直到函数支持的层级数,但我希望能得到一些简洁、抽象、符合Python风格的东西。

import re

def rgrep(s, data):
    ''' given a iterable of strings or an iterable of iterables of strings,

    returns the index/indices of strings that contain the search string.

    Args::

        s - the string that you are searching for
        data - the iterable of strings or iterable of iterables of strings
    '''


    results = []
    expr = re.compile(s)
    for item in data:
        try:
            match = expr.search(item)
            if match != None:
                results.append( data.index(item) )

        except TypeError:
            for t in item:
                try:
                    m = expr.search(t)
                    if m != None:
                        results.append( (list.index(item), item.index(t)) )

                except TypeError:
                    ''' you can only go 2 deep! '''
                    pass

    return results

3 个回答

0

要获取位置,可以使用 enumerate()

>>> data = [('foo', 'bar', 'frrr', 'baz'), ('foo/bar', 'baz/foo')]
>>> 
>>> for l1, v1 in enumerate(data):
...     for l2, v2 in enumerate(v1):
...             if 'f' in v2:
...                     print l1, l2, v2
... 
0 0 foo
1 0 foo/bar
1 1 baz/foo

在这个例子中,我使用了一个简单的匹配方式 'foo' in bar,不过你可能会用正则表达式来完成这个工作。

显然,enumerate() 可以支持超过两个层级,就像你编辑的帖子中提到的那样。

1

这里有一个使用递归来搜索数据结构的grep。

要注意,好的数据结构能让解决问题变得简单优雅。 而糟糕的数据结构则会让你费尽心思去适应它。 我觉得这就是一个糟糕的数据结构在阻碍你,而不是在帮助你。

使用一个更简单、结构更统一的数据结构 (而不是用这个grep)可能会更值得考虑。

#!/usr/bin/env python

data=['something', 
('Diff',
('diff', 'udiff'),
('*.diff', '*.patch'),
('text/x-diff', 'text/x-patch',['find','java deep','down'])),

('Delphi',
('delphi', 'pas', 'pascal', 'objectpascal'),
('*.pas',),
('text/x-pascal',['lets', 'put one here'], )),

('JavaScript+Mako',
('js+mako', 'javascript+mako'),
('application/x-javascript+mako',
'text/x-javascript+mako',
'text/javascript+mako')),
]

def grep(astr,data,prefix=[]):
    result=[]
    for idx,elt in enumerate(data):
        if isinstance(elt,basestring):
            if astr in elt:
                result.append(tuple(prefix+[idx]))
        else:
            result.extend(grep(astr,elt,prefix+[idx]))
    return result

def pick(data,idx):
    if idx:
        return pick(data[idx[0]],idx[1:])
    else:
        return data
idxs=grep('java',data)
print(idxs)
for idx in idxs:
    print('data[%s] = %s'%(idx,pick(data,idx)))
3

我建议把递归的枚举和查找分开:

def enumerate_recursive(iter, base=()):
    for index, item in enumerate(iter):
        if isinstance(item, basestring):
            yield (base + (index,)), item
        else:
            for pair in enumerate_recursive(item, (base + (index,))):
                yield pair

def grep_index(filt, iter):
    return (index for index, text in iter if filt in text)

这样你就可以同时进行非递归和递归的查找:

l = list(grep_index('opt1', enumerate(sys.argv)))   # non-recursive
r = list(grep_index('diff', enumerate_recursive(your_data)))  # recursive

另外要注意,我们在这里使用了迭代器,这样可以在需要的时候节省内存,特别是处理较长的序列时。

一个更通用的解决方案是给grep_index传递一个可调用的对象,而不是字符串。不过这对你来说可能不是必须的。

撰写回答