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

时间:2013-12-18 18:57:28

标签: 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']]

在它的通用形式中,它是:   查找条件,收集数据直到该条件再次为真,重复

现在,我会做类似

的事情
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的新手,并不能完全包围所有这些。

谢谢!

更新

我实际上已经和史蒂夫杰索普的Grouper课程一起回答。这就是我正在做的事情:

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']}
]

5 个答案:

答案 0 :(得分:5)

我怀疑itertools(或集合)可以使它更清晰,除非确切的模式在某处实现。

我注意到两件事:

  • 总是有一个当前事件(因为第一行是一个事件)
  • 总是将该行附加到当前事件(因此事件本身始终为current_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

答案 1 :(得分:5)

使用itertools.groupby,您可以根据密钥轻松对内容进行分组,例如'event' in line。所以,作为第一步:

>>> 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)。 / p>

另一方面,如果你真的想要他们在一起,那就是相同的步骤,但最后有一个[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行魔法更有用。 / p>

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

>>> 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 = list(groupify(lines))

答案 2 :(得分:4)

我希望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)

给出了

>>> 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所做的事。

答案 3 :(得分:2)

您可以使用列表推导使代码更简洁:

# 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']]

我不确定你在原始可读性方面获得了多少,但是通过这些评论来理解它是非常简单的。

答案 4 :(得分:2)

您可以使用Python中的函数具有状态的事实。这个石斑鱼功能的用途与DSM的accumulate(fn(line) for line in s1)

相同
def grouper(line):
    if is_event(line):
        grouper.count += 1
    return grouper.count
grouper.count = 0

result_as_iterators = (x[1] for x in itertools.groupby(lines, grouper))

然后如果你需要它:

result_as_lists = [list(x) for x in result_as_iterators]

为了允许并发使用,每次使用时都需要一个新的石斑鱼功能对象(因此它有自己的计数)。您可能会发现将其设为类更简单:

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())