根据 Python 中的一组索引将列表拆分为多个部分

Posted

技术标签:

【中文标题】根据 Python 中的一组索引将列表拆分为多个部分【英文标题】:Split a list into parts based on a set of indexes in Python 【发布时间】:2018-09-24 10:20:23 【问题描述】:

根据任意数量的索引将列表拆分为多个部分的最佳方法是什么?例如。给出下面的代码

indexes = [5, 12, 17]
list = range(20)

返回类似的东西

part1 = list[:5]
part2 = list[5:12]
part3 = list[12:17]
part4 = list[17:]

如果没有索引,它应该返回整个列表。

【问题讨论】:

我对你的答案选择标准有点兴趣......更简单更快不是“Pythonic”吗? 几乎是最高票数和我对代码的熟悉程度的结合。我不是大师,所以不能说哪个更快。尽管我不完全理解它是如何工作的,但您的解决方案确实看起来很有趣。如果这里有几个人愿意确认您的解决方案确实更好,我愿意修改我的选择。毕竟,Stack Overflow 的意义不在于群众的智慧吗:) 你不需要成为大师;使用 timeit 模块。理解:(1)用print替换yield(2)阅读:http://***.com/questions/231767/can-somebody-explain-me-the-python-yield-statement 【参考方案1】:

我也有兴趣看到一种更 Pythonic 的方式来执行此操作。但这是一个糟糕的解决方案。您需要添加一个空索引列表检查。

类似的东西:

indexes = [5, 12, 17]
list = range(20)

output = []
prev = 0

for index in indexes:
    output.append(list[prev:index])
    prev = index

output.append(list[indexes[-1]:])

print output

生产

[[0, 1, 2, 3, 4], [5, 6, 7, 8, 9, 10, 11], [12, 13, 14, 15, 16], [17, 18, 19]]

【讨论】:

对将想法扔进底池的 2 票反对票不是很满意。特别是没有cmets。我陈述了解决方案的粗略性质。 这个网站不应该允许在没有解释的情况下投反对票。一点用处都没有。 这就是我讨厌流行语“Pythonic”的原因。就好像所有用 Python 编写的东西都应该以某种特定于 Python 的特殊方式编写,最好是出于某种原因强行压缩成一行。 在我看来,“pythonic”只是意味着好的、惯用的风格。这并不意味着展示每个 python 功能的超浓缩单行解决方案。这对我来说看起来非常pythonic。它适当地使用切片,当 range 比 xrange 更合适时使用 range,并直接迭代列表而不是循环遍历索引。蟒蛇?查看。可以理解吗?查看。准确的?查看。 +1 哦,在python 2中,你可以在for循环退出后使用prev,所以你可以用output.append(list[prev:])替换output.append(list[indexes[-1]:])【参考方案2】:
indices = [5, 12, 17]
input = range(20)
output = []

reduce(lambda x, y: output.append(input[x:y]) or y, indices + [len(input)], 0)
print output

【讨论】:

【参考方案3】:
>>> def burst_seq(seq, indices):
...    startpos = 0
...    for index in indices:
...       yield seq[startpos:index]
...       startpos = index
...    yield seq[startpos:]
...
>>> list(burst_seq(range(20), [5, 12, 17]))
[[0, 1, 2, 3, 4], [5, 6, 7, 8, 9, 10, 11], [12, 13, 14, 15, 16], [17, 18, 19]]
>>> list(burst_seq(range(20), []))
[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]]
>>> list(burst_seq(range(0), [5, 12, 17]))
[[], [], [], []]
>>>

Maxima mea culpa:它使用 for 语句,并且没有使用诸如 itertools、zip()、None 作为哨兵、列表推导等 whizzbang 的东西......

;-)

【讨论】:

之所以使用“whizzbang stuff”,不是因为它很时髦或让人看起来更聪明,而是因为它更接近解决方案的声明性规范。可变状态的建设性方法通常也更容易出错......【参考方案4】:

这是另一个答案。

def partition(l, indexes):
    result, indexes = [], indexes+[len(l)]
    reduce(lambda x, y: result.append(l[x:y]) or y, indexes, 0)
    return result

它支持负索引等。

>>> partition([1,2,3,4,5], [1, -1])
[[1], [2, 3, 4], [5]]
>>> 

【讨论】:

【参考方案5】:

这是我能想到的最简单和最 Pythonic 的解决方案:

def partition(alist, indices):
    return [alist[i:j] for i, j in zip([0]+indices, indices+[None])]

如果输入非常大,那么迭代器解决方案应该更方便:

from itertools import izip, chain
def partition(alist, indices):
    pairs = izip(chain([0], indices), chain(indices, [None]))
    return (alist[i:j] for i, j in pairs)

当然还有非常非常懒惰的解决方案(如果您不介意获取数组而不是列表,但无论如何您都可以将它们恢复为列表):

import numpy
partition = numpy.split

【讨论】:

那个 -1 将切断最后一项。您可以使用None 来代替将其视为与空切片部分相同的部分(也可以用于开始,尽管没关系) 是的,你是对的......我的初始版本有 len(alist) 并且工作正常,但我替换为 -1,因为它看起来不那么冗长:-s @Brian 哇,我只需要剪掉最后一个元素!谢谢! partition 是一个误导性的名称,因为在许多语言中,partition 函数将一个列表分成两个列表——通过的项目和不符合条件的项目(作为 A -> 布尔函数传递),例如分区(偶数, [1, 2, 3, 4, 5]) = ([2, 4], [1, 3, 5])【参考方案6】:

我的解决方案与 Il-Bhima 的类似。

>>> def parts(list_, indices):
...     indices = [0]+indices+[len(list_)]
...     return [list_[v:indices[k+1]] for k, v in enumerate(indices[:-1])]

替代方法

如果您愿意稍微改变输入索引的方式,从绝对索引变为相对索引(即从 [5, 12, 17][5, 7, 5],下面的内容也会为您提供所需的输出,但它不会创建中间人名单。

>>> from itertools import islice
>>> def parts(list_, indices):
...     i = iter(list_)
...     return [list(islice(i, n)) for n in chain(indices, [None])]

【讨论】:

+1 表示简短和重用值(enumerate 迭代现有数组,而不是 zip 正如 Blixt 指出的那样,我认为您的意思是索引而不是索引。然后在传递 [0,5,12,17] 之类的索引时遇到小问题,在这种情况下,您的结果将包含空列表 list_[0:0] @Il-Bhima:可以说是正确的,因为第一部分的长度为 0,这与 OP 的示例一致。 @Il-Bhima,我认为传入“在索引 0 处拆分”的预期行为是获取一个空数组作为第一个拆分值。就个人而言,我讨厌根据参数变化的“神奇”行为。 我更新了这个版本,在提高简单性的同时不创建新列表(除了必要的切片)。【参考方案7】:

Cide 对数组进行了三份复制:[0]+indices 复制,([0]+indices)+[] 再次复制,indices[:-1] 将复制第三次。 Il-Bhima 制作了五份。 (当然,我没有计算返回值。)

这些可以减少(izip、islice),但这里有一个零拷贝版本:

def iterate_pairs(lst, indexes):
    prev = 0
    for i in indexes:
        yield prev, i
        prev = i
    yield prev, len(lst)

def partition(lst, indexes):
    for first, last in iterate_pairs(lst, indexes):
        yield lst[first:last]

indexes = [5, 12, 17]
lst = range(20)

print [l for l in partition(lst, indexes)]

当然,与解释型 Python 相比,数组副本相当便宜(本机代码),但这还有另一个优点:它很容易重用,可以直接改变数据:

for first, last in iterate_pairs(lst, indexes):
    for i in range(first, last):
        lst[i] = first
print lst
# [0, 0, 0, 0, 0, 5, 5, 5, 5, 5, 5, 5, 12, 12, 12, 12, 12, 17, 17, 17]

(这就是我将索引传递给 iterate_pairs 的原因。如果您不关心这一点,您可以删除该参数,只需将最后一行设为“yield prev,None”,这就是 partition() 需要的所有内容。)

【讨论】:

【参考方案8】:

我能想到的就这些了

def partition(list_, indexes):
    if indexes[0] != 0:
        indexes = [0] + indexes
    if indexes[-1] != len(list_):
        indexes = indexes + [len(list_)]
    return [ list_[a:b] for (a,b) in zip(indexes[:-1], indexes[1:])]

【讨论】:

聪明的解决方案,非常简洁 =) 不过,我会给 kjfletch 的答案 +1,因为它重用现有值,而此方法会创建/修改大量列表。 删除条件会更加一致——如果第一个索引为 0,则第一项应该为空。只需使用indexes = [0] + indexes + [None] 另外,使用 itertools.izip 代替 zip 和使用 itertools.islice 代替直接切片可能会更好。 @Glenn 嗯,我实际上是想避免在开头和结尾出现那些空列表。不知道这是否是原海报想要的 不要覆盖内置变量(列表)【参考方案9】:

index 的复数形式是indices。追求简单/可读性。

indices = [5, 12, 17]
input = range(20)
output = []

for i in reversed(indices):
    output.append(input[i:])
    input[i:] = []
output.append(input)

while len(output):
    print output.pop()

【讨论】:

“索引”和“索引”都是正确的。 “Indexes”是“index”的复数形式,在美国较为常见,而“indices”则源自拉丁语,在英国较为常见。 有人想讨论从右到左工作的可行性吗?还是现在是 GrammarOverflow? 从右到左工作意味着您要么在末尾以外的其他位置插入,要么在返回之前反转列表。这两种情况都不理想。

以上是关于根据 Python 中的一组索引将列表拆分为多个部分的主要内容,如果未能解决你的问题,请参考以下文章

python - 如何首先根据初始列表的单个元素将列表拆分为子列表,然后在python中将列表的连续部分拆分为子列表?

Python:根据索引范围将列表拆分为子列表

Pandas - 根据日期将数据框拆分为多个数据框?

python中split的用法分割的字符串怎么命名?

算法-排序系列04之快速排序

根据包含必须过滤的索引的另一个列表拆分列表