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