以递归方式映射对角元素并检查条件,Python

时间:2016-01-06 13:48:51

标签: python

给出一个由列表表示的发言会话列表,其中每个元素都是一个包含开始时间,结束时间和发言人姓名的列表,例如:

a = [ [  265,  604, "S1" ],
      [  604, 2373, "S1" ],
      [ 2373, 3719, "S1" ],
      [ 3719, 4910, "S2" ],
      [ 4910, 6790, "S2" ] ]

我希望将其缩小为一个新的列表,其中连续的会话应该合并。

合并是将会话的第一个开始时间和连续会话的结束时间结合起来,即:

[a[i][0], a[i+1][1], a[i][2]]

如果连续会话的发言者相同,并且会话之间的中断时间不长,即可以进行合并,即

 a[i+1][0] - a[i][1] < 1000  and  a[i][2] == a[i+1][2]

如果结果列表可以进一步合并,它也应该合并。

所以对于上面的例子,结果应该是:

[ [265, 3719, 'S1'], [3719, 6790, 'S2'] ].

我正在使用上面提到的条件迭代列表,但由于某种原因,我只会遇到前两组元素。

3 个答案:

答案 0 :(得分:1)

你想要的是找到分组的开始和结束:

def grps(a):
    it = iter(a)
    i = next(it)
    start, spk = i[0], i[2]
    for ele in it:
        if spk != ele[2]:
            yield [start, ele[0], spk]
            spk = ele[2]
            start = ele[0]
    yield start, ele[1], spk


print(list(grps(a)))

哪会给你:

[[265, 3719, 'S1'], [3719, 6790, 'S2']]

当你遇到一个新的扬声器时,它们的开始时间是最后一个扬声器结束时间,你每次遇到一个新扬声器时都只更新起始变量,所以你总是输出每个扬声器的开始和结束时间,最后一个扬声器我们在循环外使用自己的第二个元素来获得结束时间。

如果下一位发言者不包含结束时间,即存在差距的另一种方法是使用前一个元素:

def grps(a):
    it = iter(a)
    prev = next(it)
    start, spk = prev[0], prev[2]
    for ele in it:
        if spk != ele[2]:
            yield [start, prev[1], spk]
            start = ele[0]
            spk = ele[2]
        prev = ele
    yield start, ele[1], spk

但是在你的情况下,一旦格式与发布相同,则不需要。

或使用itertools.groupby

from itertools import groupby
from operator import itemgetter


def gps(a):
    for k, v in groupby(a, key=itemgetter(2)):
        v = list(v)
        yield [v[0][0],  v[-1][1], v[0][2]]


print(list(gps(a)))

输出:

[[265, 3719, 'S1'], [3719, 6790, 'S2']]

如果您只是想在没有呼叫列表的情况下拉出第一个和最后一个,那么可能会略有不同:

from itertools import groupby
from operator import itemgetter
from collections import deque


def gps(a):
    for k, v in groupby(a, key=itemgetter(2)):
        start, end = next(v), deque(v, maxlen=1).pop()
        yield [start[0],  end[1], end[2]]

如果您的数据恰好是无序的,您可以使用dict:

def gps(a):
    d = defaultdict(lambda: {"mn":float("inf"),"mx":float("-inf")})
    for sub in a:
        key = sub[-1]
        if d[key]["mn"] > sub[0]:
            d[key]["mn"] = sub[0]
        elif d[key]["mx"] < sub[1]:
            d[key]["mx"]  = sub[1]
    return d

for k,v in gps(a).items():
    print([v["mn"], v["mx"], k])

答案 1 :(得分:1)

源自Padraic的答案,在我看来更具可读性,并解决了千差异问题:

def nextSpeech(segments):
    it = iter(segments)
    start = end = next(it)
    def isSameSpeech(element): return element[2] == start[2] and end[0] - start[1] < 1000
    def getSpeech(): return start[0], end[1], end[2]

    for element in it:
        if isSameSpeech(element):
            end = element
        else:
            yield getSpeech()
            start = end = element
    yield getSpeech()

list(nextSpeech(a))

你得到:

[(265, 3719, 'S1'), (3719, 6790, 'S2')]

如果输入的细分未排序,您可以运行list(nextSpeech(sorted(a)))或修改函数的前两行,默认情况下使用&#39;排序&#39;对输入进行排序。参数:

def nextSpeech(segments, sort=True):
    it = iter(sorted(segments) if sort else segments)
    ...

请注意,sorted()可以替换为您喜欢的任何其他排序函数(或lambda)。

答案 2 :(得分:0)

很容易在会话之间找到(索引)中断:

breaks = [i + 1 
          for (i, (a0, a1)) in enumerate(zip(a, a[1:]))
          if (a1[0] - a0[1]) >= 1000 or (a0[2] != a1[2])]

然后找到要合并的会话:

sessions = zip([0] + breaks, b + [len(breaks)-1])

因此答案是:

answer = [[a[start][0], a[end][1], a[start][2]] 
          for (start, end) in sessions]

我们可以不用索引:

 breaks = [b for b in zip(a, a[1:])
           if (a1[0] - a0[1]) >= 1000 or (a0[2] != a1[2])]
 sessions = zip([(None, a[0])] + breaks, 
                 breaks + [(a[-1], None)])
 answer = [[p[1][0], n[0][1], p[1][2]] 
           for (p,n) in sessions]