Python 如何根据条件将列表中的数据收集到组中?

Python 如何根据条件将列表中的数据收集到组中?,python,parsing,grouping,Python,Parsing,Grouping,我不知道该如何命名这个问题。我遇到过一些情况,在这些情况下,我有一个数据列表,可能带有一些属性注释,我希望将它们收集到组中 例如,我可能有这样一个文件: some event reading: 25.4 reading: 23.4 reading: 25.1 different event reading: 22.3 reading: 21.1 reading: 26.0 reading: 25.2 another event reading: 25.5 reading: 25.1 >&

我不知道该如何命名这个问题。我遇到过一些情况,在这些情况下,我有一个数据列表,可能带有一些属性注释,我希望将它们收集到组中

例如,我可能有这样一个文件:

some event
reading: 25.4
reading: 23.4
reading: 25.1
different event
reading: 22.3
reading: 21.1
reading: 26.0
reading: 25.2
another event
reading: 25.5
reading: 25.1
>>> for event in groupify(lines):
...     print(event)
我想把每一组读数分组,根据一个条件(在本例中,一个事件发生)将它们分开,这样我就得到了一个类似

[['some event',
  'reading: 25.4',
  'reading: 23.4',
  'reading: 25.1'],
 ['different event',
  'reading: 22.3',
  'reading: 21.1',
  'reading: 26.0',
  'reading: 25.2'],
 ['another event',
  'reading: 25.5',
  'reading: 25.1']]
就其一般形式而言,它是: 查找条件,收集数据,直到该条件再次为真,重复

现在,我会做一些类似的事情

events = []
current_event = []

for line in lines:
    if is_event(line):
        if current_event:
            events.append(current_event)
        current_event = [line]

    else:
        current_event.append(line)
else:
    if current_event:
        events.append(current_event)


def is_event(line):
    return 'event' in line
这就产生了我想要的东西,但这很难看也很难理解。我敢肯定一定有更好的办法

我的猜测是,这涉及到一些itertools魔法,但我对itertools是新手,不能完全理解所有这些

谢谢

更新 事实上,我在一节石斑鱼课上接受了史蒂夫·杰索普的答案。以下是我正在做的:

class Grouper(object):
    def __init__(self, condition_function):
        self.count = 0
        self.condition_function = condition_function

    def __call__(self, line):
        if self.condition_function(line):
            self.count += 1
        return self.count
然后像这样使用它

event_grouper = Grouper(is_event)
result_as_iterators = (x[1] for x in itertools.groupby(lines, event_grouper))
然后把它变成一本字典,我会的

event_dictionary = [{event: readings} for event, *readings in result_as_iterators]

[
 {'some event': ['reading: 25.4', 'reading: 23.4', 'reading: 25.1']},
 {'different event': ['reading: 22.3','reading: 21.1','reading: 26.0','reading: 25.2']},
 {'another event': ['reading: 25.5', 'reading: 25.1']}
]
>>> with open("event.dat") as fp:
...     s = list(splitter(fp, lambda x: x.strip().endswith("event")))
...     
>>> s
[['some event\n', 'reading: 25.4\n', 'reading: 23.4\n', 'reading: 25.1\n'], 
['different event\n', 'reading: 22.3\n', 'reading: 21.1\n', 'reading: 26.0\n', 'reading: 25.2\n'], 
['another event\n', 'reading: 25.5\n', 'reading: 25.1']]
我怀疑itertools(或集合)能否让它比这更清晰,除非在那里的某个地方实现了确切的模式

我注意到两件事:

  • 您总是有一个当前事件(因为第一行是一个事件)
  • 您总是将该行附加到当前事件(因此事件本身总是
    current\u event[0]
因此,如果您有当前事件,您可以跳过检查,也不必创建它。此外,由于“当前”事件始终是最后一个事件,我们可以使用负索引直接跳到它:

events = []

for line in lines:
    if is_event(line):
        events.append([])
    events[-1].append(line)

def is_event(line):
    return 'event' in line
使用,您可以轻松地根据键对内容进行分组,如第行中的
'event'。因此,作为第一步:

>>> for k, g in itertools.groupby(lines, lambda line: 'event' in line):
...     print(k, list(g))
当然,这不会将事件及其值放在一起。我怀疑您确实不希望事件与其值放在一起,但实际上更希望有一个
event:[values]
的目录或一个
(event,[values]的列表)
。在这种情况下,您就快完成了。例如,要获得口述,只需使用石斑食谱(或
zip(*[iter(groups)]*2)
)分组,然后使用口述理解将这些对中的
k,v
映射到
next(k):list(v)

另一方面,如果你真的想把它们放在一起,步骤是一样的,但最后有一个
[next(k)]+list(v)]
列表

然而,如果你对groupby的理解不够透彻,无法将描述转化为代码,那么你可能应该写一些你确实理解的东西。这并不难:

def groupify(lines):
    event = []
    for line in lines:
        if 'event' in line:
            if event: yield event
            event = [line]
        else:
            event.append(line)
    if event: yield event
是的,它是7行(通过一些技巧可以压缩为4行),而不是3行(通过以一种丑陋的方式嵌套理解可以压缩为1行),但是你理解并可以调试的7行比3行魔术更有用

当您迭代此函数创建的生成器时,它将为您提供行列表,如下所示:

some event
reading: 25.4
reading: 23.4
reading: 25.1
different event
reading: 22.3
reading: 21.1
reading: 26.0
reading: 25.2
another event
reading: 25.5
reading: 25.1
>>> for event in groupify(lines):
...     print(event)
这将打印:

['some event', 'reading: 25.4', 'reading: 23.4', 'reading: 25.1']
['different event', 'reading: 22.3', 'reading: 21.1', 'reading: 26.0', 'reading: 25.2']
['another event', 'reading: 25.5', 'reading: 25.1']
如果您想要一个列表而不是生成器(这样您可以对它进行索引,或对它进行两次迭代),您可以执行与将任何其他iterable转换为列表相同的操作:

events = list(groupify(lines))

您可以使用列表理解使代码更加简洁:

# Load the file
lines  = [l.rstrip() for l in open("test.txt") ]

# Record the line indices where events start/stop
events = [ i for i in range(len(lines)) if "event" in lines[i] ]
events.append( len(lines) ) # required to get the last event

# Group the lines into their respective events
groups = [ lines[events[i]:events[i+1]] for i in range(len(events)-1) ]
print groups
输出:

[['some event', 'reading: 25.4', 'reading: 23.4', 'reading: 25.1'],
 ['different event', 'reading: 22.3', 'reading: 21.1', 'reading: 26.0', 'reading: 25.2'],
 ['another event', 'reading: 25.5', 'reading: 25.1']]

我不确定您在原始可读性方面获得了多少好处,但通过注释理解起来非常简单。

我希望
itertools
有一个可以满足您需要的函数。对于娱乐价值,在现代Python中,您可以执行以下操作

from itertools import groupby, accumulate, tee
def splitter(source, fn):
    s0, s1 = tee(source)
    tick = accumulate(fn(line) for line in s1)
    grouped = groupby(s0, lambda x: next(tick))
    return (list(g) for k,g in grouped)

[
 {'some event': ['reading: 25.4', 'reading: 23.4', 'reading: 25.1']},
 {'different event': ['reading: 22.3','reading: 21.1','reading: 26.0','reading: 25.2']},
 {'another event': ['reading: 25.5', 'reading: 25.1']}
]
>>> with open("event.dat") as fp:
...     s = list(splitter(fp, lambda x: x.strip().endswith("event")))
...     
>>> s
[['some event\n', 'reading: 25.4\n', 'reading: 23.4\n', 'reading: 25.1\n'], 
['different event\n', 'reading: 22.3\n', 'reading: 21.1\n', 'reading: 26.0\n', 'reading: 25.2\n'], 
['another event\n', 'reading: 25.5\n', 'reading: 25.1']]

但老实说,我可能会像@abarnert那样做。

您可以使用Python中的函数具有状态这一事实。此grouper函数的用途与DSM的
累加(fn(line)表示s1中的line)

如果您需要它:

result_as_lists = [list(x) for x in result_as_iterators]
为了允许并发使用,您每次使用时都需要一个新的grouper函数对象(以便它有自己的计数)。您可能会发现将其作为类更简单:

class Grouper(object):
    def __init__(self):
        self.count = 0
    def __call__(self, line):
        if is_event(line):
            self.count += 1
        return self.count

results_as_iterators = itertools.groupby(lines, Grouper())

如果我的答案正确,请投我的赞成票:)回答得好。你能添加一行关于使用groupify生成器的内容吗?啊!我没有意识到创建一个发电机是如此简单,非常干净。是的,我想在看到一些替代品后,itertools对我来说可能有点疯狂case@JoePinsonault:我可能会用
itertools
来实现这一点,学习如何用一连串的genexpr和itertools调用来编写东西是值得的(请参阅David Beazley的文章,了解一个很好的起点)。如果我不理解,我就不想使用这些代码。@JoePinsonault:另外,如果您还没有阅读Python教程中的和以下两个部分,它们非常方便。虽然很多人没有领会到
yield
是一种返回值但继续运行的方法这一基本思想,这是许多新手都要求的,但大多数中级开发人员都懒得这么做,因为他们认为这是不可能的。@abarnert这是对新手/中级开发人员的一个很好的观察。在编写python的1年时间里,我一直不关心列表操作占用内存的问题,因为我处理过小数据集,但最近生成器的价值变得显而易见。开始对~100MB的文件进行操作,我不能再做内存牛仔了。另外,它们是一个非常有趣的概念。他们就像小朋友,只在你需要的时候才把数据交给你,不需要提前。哦,太好了,我已经看到了,这很明显it@JoePinsonault几年后,你会向别人展示一个类似的例子,他们也会说同样的话这是最好的学习方式我得研究一下这个,它很有异国情调,也很有趣。我想我已经有了下一个与python相关的阅读主题,我很确定我提交了类似的内容,它不需要
tee
ing迭代两次,但更复杂,在Erik发现的一些边缘案例中有一个bug,我从来没有费心去修复……如果你想尝试一下d