Python 3:在排序列表中反向连续运行?

时间:2017-01-27 16:07:14

标签: python algorithm list generator timsort

这是一个问题,是What's the most Pythonic way to identify consecutive duplicates in a list?的扩展。

假设您有一个元组列表:

my_list = [(1,4), (2,3), (3,2), (4,4), (5,2)]

然后按每个元组的最后一个值对其进行排序:

my_list = sorted(my_list, key=lambda tuple: tuple[1])
# [(3,2), (5,2), (2,3), (1,4), (4,4)]

然后我们连续两次运行(查看每个元组中的最后一个值),即[(3,2), (5,2)][(1,4), (4,4)]

什么是扭转每次运行的pythonic方式(而不是内部的元组),例如

reverse_runs(my_list)
# [(5,2), (3,2), (2,3), (4,4), (1,4)]

这可以在发电机内完成吗?

更新

我注意到,也许示例清单不清楚。所以请考虑:

my_list = [(1,"A"), (2,"B"), (5,"C"), (4,"C"), (3,"C"), (6,"A"),(7,"A"), (8,"D")]

reverse_runs的理想输出

[(7,"A"), (6,"A"), (1,"A"), (2,"B"), (3,"C"), (4,"C"), (5,"C"), (8,"D")]

要明确术语,我正在采用"运行"用于描述TimSort这是Python的排序函数所基于的 - 给它(排序函数)它的安全性。

因此,如果您对集合进行排序,那么集合是否应该是多方面的,那么只有指定的维度在上排序,如果两个元素是对于指定维度,相同,其排序将更改。

因此以下功能:

sorted(my_list,key=lambda t: t[1])

的产率:

[(1, 'A'), (6, 'A'), (7, 'A'), (2, 'B'), (5, 'C'), (4, 'C'), (3, 'C'), (8, 'D')]

并且"C"(即(5, 'C'), (4, 'C'), (3, 'C'))上的游戏不会受到干扰。

总之,尚未定义的函数reverse_runs的期望输出:

1。)按元组的最后一个元素对元组进行排序

2。)维持第一个元素的顺序,反转在最后一个元素上运行

理想情况下,我希望在生成器函数中使用它,但是(目前对我来说)这似乎不可能。

因此可以采用以下策略:

1。)通过sorted(my_list, key=lambda tuple: tuple[1])

按最后一个元素对元组进行排序

2.)当后续元组(i + 1)与(i)中的最后一个元素不同时,标识每个元组中最后一个元素的索引。即识别运行

3。)制作一个空列表

4.使用拼接运算符,获取,反转,并将每个子列表附加到空列表

2 个答案:

答案 0 :(得分:4)

我认为这会奏效。

my_list = [(1,4), (2,3), (3,2), (4,4), (5,2)]
my_list = sorted(my_list, key=lambda tuple: (tuple[1], -tuple[0]))

print(my_list)

<强>输出

[(5, 2), (3, 2), (2, 3), (4, 4), (1, 4)]

误解了问题。不太漂亮,但这应该适合你真正想要的东西:

from itertools import groupby
from operator import itemgetter


def reverse_runs(l):
    sorted_list = sorted(l, key=itemgetter(1))
    reversed_groups = (reversed(list(g)) for _, g in groupby(sorted_list, key=itemgetter(1)))
    reversed_runs = [e for sublist in reversed_groups for e in sublist]

    return reversed_runs


if __name__ == '__main__':
    print(reverse_runs([(1, 4), (2, 3), (3, 2), (4, 4), (5, 2)]))
    print(reverse_runs([(1, "A"), (2, "B"), (5, "C"), (4, "C"), (3, "C"), (6, "A"), (7, "A"), (8, "D")]))

<强>输出

[(5, 2), (3, 2), (2, 3), (4, 4), (1, 4)]
[(7, 'A'), (6, 'A'), (1, 'A'), (2, 'B'), (3, 'C'), (4, 'C'), (5, 'C'), (8, 'D')]

生成器版本:

from itertools import groupby
from operator import itemgetter


def reverse_runs(l):
    sorted_list = sorted(l, key=itemgetter(1))
    reversed_groups = (reversed(list(g)) for _, g in groupby(sorted_list, key=itemgetter(1)))

    for group in reversed_groups:
        yield from group


if __name__ == '__main__':
    print(list(reverse_runs([(1, 4), (2, 3), (3, 2), (4, 4), (5, 2)])))
    print(list(reverse_runs([(1, "A"), (2, "B"), (5, "C"), (4, "C"), (3, "C"), (6, "A"), (7, "A"), (8, "D")])))

答案 1 :(得分:2)

最常见的情况需要2种。第一种排序是第二个标准的reversed排序。第二种是对第一个标准的前向排序:

pass1 = sorted(my_list, key=itemgetter(0), reverse=True)
result = sorted(pass1, key=itemgetter(1))

我们可以像这样排序多次,因为python的排序算法保证为stable

然而,在现实生活中,通常可以简单地构造一个更聪明的关键功能,允许在一次通过中进行排序。这通常涉及&#34;否定&#34;其中一个值并依赖于元组自我排序的事实lexicographically

result = sorted(my_list, key=lambda t: (t[1], -t[0]))

为了响应您的更新,看起来,如下所示可能是合适的解决方案:

from operator import itemgetter
from itertools import chain, groupby
my_list = [(1,"A"), (2,"B"), (5,"C"), (4,"C"), (3,"C"), (6,"A"),(7,"A"), (8,"D")]

pass1 = sorted(my_list, key=itemgetter(1))
result = list(chain.from_iterable(reversed(list(g)) for k, g in groupby(pass1, key=itemgetter(1))))
print(result)

我们可以拆开表达式:

chain.from_iterable(reversed(list(g)) for k, g in groupby(pass1, key=itemgetter(1)))

试图找出它在做什么......

首先,让我们看一下groupby(pass1, key=itemgetter(1))groupby将产生2元组。元组中的第一项(k)是&#34;键&#34; - 例如从itemgetter(1)返回的任何内容。分组发生后,关键在这里并不重要,因此我们不会使用它。第二项(g - for&#34; group&#34;)是一个可迭代的,它产生具有相同&#34;键&#34;的连续值。这正是您请求的项目,但是,它们按排序后的顺序排列。您以相反的顺序请求它们。为了反转任意迭代,我们可以从中构造一个列表,然后反转列表。例如reversed(list(g))。最后,我们需要将这些块重新粘贴在一起,这是chain.from_iterable的用武之地。

如果我们想要变得更聪明,我们可能会从算法的角度做得更好(假设&#34;密钥&#34;用于垃圾箱)。诀窍是在字典中对对象进行分区,然后对分类进行排序。这意味着我们可能会排序比原始列表短得多的列表:

from collections import defaultdict, deque
from itertools import chain

my_list = [(1,"A"), (2,"B"), (5,"C"), (4,"C"), (3,"C"), (6,"A"),(7,"A"), (8,"D")]

bins = defaultdict(deque)
for t in my_list:
    bins[t[1]].appendleft(t)

print(list(chain.from_iterable(bins[key] for key in sorted(bins))))

请注意 这是否比第一种方法更好,这非常依赖于初始数据。由于TimSort是一个非常漂亮的算法,如果数据已经开始已经分组到箱子中,那么这个算法可能不会击败它(不过,我会把它作为练习让你尝试......) 。但是,如果数据分散(导致TimSort表现得更像MergeSort),那么首先进行分区可能会略微获胜。