在Python列表中每隔n个元素插入一个元素

40

假设我有一个类似于以下Python列表:

letters = ['a','b','c','d','e','f','g','h','i','j']

我想在每隔n个元素后插入一个"x",比如说在列表的每三个字符后插入。结果应该是:

letters = ['a','b','c','x','d','e','f','x','g','h','i','x','j']

我知道可以通过循环和插入来实现。我真正寻找的是一种Pythonic的方式,也许是一行代码吗?


对于一种更加扩展的方法,其中要插入的元素在另一个列表中:每n个位置将列表中的项目插入到另一个列表中 - yatu
11个回答

22

我有两个单行代码。

给定:

>>> letters = ['a','b','c','d','e','f','g','h','i','j']
  1. Use enumerate to get index, add 'x' every 3rd letter, eg: mod(n, 3) == 2, then concatenate into string and list() it.

    >>> list(''.join(l + 'x' * (n % 3 == 2) for n, l in enumerate(letters)))
    
    ['a', 'b', 'c', 'x', 'd', 'e', 'f', 'x', 'g', 'h', 'i', 'x', 'j']
    

    But as @sancho.s points out this doesn't work if any of the elements have more than one letter.

  2. Use nested comprehensions to flatten a list of lists(a), sliced in groups of 3 with 'x' added if less than 3 from end of list.

    >>> [x for y in (letters[i:i+3] + ['x'] * (i < len(letters) - 2) for
         i in xrange(0, len(letters), 3)) for x in y]
    
    ['a', 'b', 'c', 'x', 'd', 'e', 'f', 'x', 'g', 'h', 'i', 'x', 'j']
    
(a) [item for subgroup in groups for item in subgroup]可以将一个嵌套的列表扁平化。

5
如果元素不是单个字符,第一个方法就无法起作用。它会将字符串拆分为每个元素都是单个字符的列表。 - sancho.s ReinstateMonicaCellio

19

试一试

i = n
while i < len(letters):
    letters.insert(i, 'x')
    i += (n+1)

其中n表示要插入'x'之前有多少个元素。

操作方式是初始化一个变量i并将其设置为n,然后设置一个while循环,在循环中只要i小于letters的长度就一直运行。在循环中,你需要在letters列表的索引i处插入'x'。接下来必须将n+1的值加到i上。这么做的原因是,当你向letters列表中插入一个元素时,列表的长度会增加1。

n为3且要插入'x'为例,操作如下:

letters = ['a','b','c','d','e','f','g','h','i','j']
i = 3
while i < len(letters):
    letters.insert(i, 'x')
    i += 4

print letters

会打印出

['a', 'b', 'c', 'x', 'd', 'e', 'f', 'x', 'g', 'h', 'i', 'x', 'j']

这是您期望的结果。


1
这是不必要的二次方,这在这种情况下可能重要,也可能不重要。 - Mike Graham
@MikeGraham 这怎么是二次的?我觉得这是线性的吧?(不是想反驳你,只是对时间复杂度很不擅长) - michaelpri
2
@michaelpri,insert() 本身需要 O(n) 的时间,如果在 while 循环中使用它,则会使时间复杂度变为二次方。这里是 wiki 显示每个操作的时间复杂度。 - Ozgur Vatansever
@ozgur哦,我不知道insert是O(n)的。谢谢 :) - michaelpri
@MikeGraham 在OP的例子中,这个二次方并不重要,如果他们保持像这样简单,那么这并不重要,但随着列表变得越来越长,时间将开始发挥作用。 - michaelpri
我更喜欢易于理解但性能较差的代码,而不是难以阅读但性能高的代码。+1 - Kresten

10
我想每个项目添加一个新元素。

这样怎么样?

a=[2,4,6]
for b in range (0,len(a)):
    a.insert(b*2,1)

a 现在
[1, 2, 1, 4, 1, 6]

做得好 - 易于理解的解决方案 - A.Kot

6

虽然在for循环中使用list.insert()似乎更节省内存,但为了在一行中完成操作,您也可以将给定值附加到列表上每个被均等分割的块的末尾,这些块在列表上的每个第nth索引处进行分割。

>>> from itertools import chain

>>> n = 2
>>> ele = 'x'
>>> lst = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

>>> list(chain(*[lst[i:i+n] + [ele] if len(lst[i:i+n]) == n else lst[i:i+n] for i in xrange(0, len(lst), n)]))
[0, 1, 'x', 2, 3, 'x', 4, 5, 'x', 6, 7, 'x', 8, 9, 'x', 10]

2
+1 for itertools.chain 方法,它可以创建一个可迭代对象 [item for subgroup in group for item in subgroup],这个表达式总是很难记住。同时也要赞一下在列表推导式中使用 三元表达式,出于某种原因我之前没有想到过,太棒了! - Mark Mikofski

3
一个非常简单的方法:
>>> letters = ['a','b','c','d','e','f','g','h','i','j']
>>> new_list = []
>>> n = 3
>>> for start_index in range(0, len(letters), n):
...     new_list.extend(letters[start_index:start_index+n])
...     new_list.append('x')
... 
>>> new_list.pop()
'x'
>>> new_list
['a', 'b', 'c', 'x', 'd', 'e', 'f', 'x', 'g', 'h', 'i', 'x', 'j']

你可以使用 itertools 文档中的 grouper 配方来进行分块。

如果 new_list.pop() 弹出了一个实际上应该存在的值,那该怎么办? - Navith
@Navith,它弹出了最后一个值,也就是我最后添加的 'x' - Mike Graham

3

这是一个老话题,但是在我看来它缺乏最简单、最"pythonic"的解决方案。它不过是Mark Mikofski的第二部分 被接受的答案 的扩展,可以说它提高了可读性(因此更具有Python特色)。

>>> letters = ['a','b','c','d','e','f','g','h','i','j']
>>> [el for y in [[el, 'x'] if idx % 3 == 2 else el for 
     idx, el in enumerate(letters)] for el in y]

['a', 'b', 'c', 'x', 'd', 'e', 'f', 'x', 'g', 'h', 'i', 'x', 'j']

3

for循环已经具备使用特定值递增/递减的选项:

letters = ['a','b','c','d','e','f','g','h','i','j']
n = 3

for i in range ( n, len(letters)+n, n+1 ):
  letters.insert ( i, 'X' )

print ( letters )

它不需要除法或取模运算,只需加法和一个尺寸计算。输出:
['a', 'b', 'c', 'X', 'd', 'e', 'f', 'X', 'g', 'h', 'i', 'X', 'j']

2
值得一提的是简单的实现方法:
letters = ['a','b','c','d','e','f','g','h','i','j']

i = 3   #initial step
while i < len(letters):
    letters.insert(i,'x')
    i = i + 3 + 1  #increment step by one for every loop to account for the added element

它确实使用了基本的循环和插入,但与一行代码的示例相比,它看起来更简单舒适,更符合一开始所请求的“Python化”的要求。Pythonish

1

虽然Mark Mikofski答案是可行的,但通过分配切片有更快的解决方案:

import string

# The longer the list the more speed up for list3
# letters = ['a','b','c','d','e','f','g','h','i','j']
letters = list(string.ascii_letters)
print("org:", letters)

# Use enumerate to get index, add 'x' every 3rd letter, eg: mod(n, 3) == 2, then concatenate into string and list() it.
list1 = list(''.join(l + 'x' * (n % 3 == 2) for n, l in enumerate(letters)))
print("list1:", list1)
%timeit list(''.join(l + 'x' * (n % 3 == 2) for n, l in enumerate(letters)))

# But as @sancho.s points out this doesn't work if any of the elements have more than one letter.
# Use nested comprehensions to flatten a list of lists(a), sliced in groups of 3 with 'x' added if less than 3 from end of list.
list2 = [x for y in (letters[i:i+3] + ['x'] * (i < len(letters) - 2) for i in range(0, len(letters), 3)) for x in y]
print("list2:", list2)
%timeit [x for y in (letters[i:i+3] + ['x'] * (i < len(letters) - 2) for i in range(0, len(letters), 3)) for x in y]

# Use list slice assignments
len_letters = len(letters)
len_plus_x = ll // 3
list3 = [None for _ in range(len_letters + len_plus_x)]
list3[::4] = letters[::3]
list3[2::4] = letters[2::3]
list3[1::4] = letters[1::3]
list3[3::4] = ['x' for _ in range(len_plus_x)]
print("list3:", list3)
%timeit ll = len(letters); lp = ll//3; new_letters = [None for _ in range(ll + lp)]; new_letters[::4] = letters[::3]; new_letters[2::4] = letters[2::3]; new_letters[1::4] = letters[1::3]; new_letters[3::4] = ['x' for _ in range(lp)]

生成(使用Jupyter Notebook)

org: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
list1: ['a', 'b', 'c', 'x', 'd', 'e', 'f', 'x', 'g', 'h', 'i', 'x', 'j', 'k', 'l', 'x', 'm', 'n', 'o', 'x', 'p', 'q', 'r', 'x', 's', 't', 'u', 'x', 'v', 'w', 'x', 'x', 'y', 'z', 'A', 'x', 'B', 'C', 'D', 'x', 'E', 'F', 'G', 'x', 'H', 'I', 'J', 'x', 'K', 'L', 'M', 'x', 'N', 'O', 'P', 'x', 'Q', 'R', 'S', 'x', 'T', 'U', 'V', 'x', 'W', 'X', 'Y', 'x', 'Z']
13 µs ± 1.09 µs per loop (mean ± std. dev. of 7 runs, 100000 loops each)
list2: ['a', 'b', 'c', 'x', 'd', 'e', 'f', 'x', 'g', 'h', 'i', 'x', 'j', 'k', 'l', 'x', 'm', 'n', 'o', 'x', 'p', 'q', 'r', 'x', 's', 't', 'u', 'x', 'v', 'w', 'x', 'x', 'y', 'z', 'A', 'x', 'B', 'C', 'D', 'x', 'E', 'F', 'G', 'x', 'H', 'I', 'J', 'x', 'K', 'L', 'M', 'x', 'N', 'O', 'P', 'x', 'Q', 'R', 'S', 'x', 'T', 'U', 'V', 'x', 'W', 'X', 'Y', 'x', 'Z']
13.7 µs ± 336 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
list3: ['a', 'b', 'c', 'x', 'd', 'e', 'f', 'x', 'g', 'h', 'i', 'x', 'j', 'k', 'l', 'x', 'm', 'n', 'o', 'x', 'p', 'q', 'r', 'x', 's', 't', 'u', 'x', 'v', 'w', 'x', 'x', 'y', 'z', 'A', 'x', 'B', 'C', 'D', 'x', 'E', 'F', 'G', 'x', 'H', 'I', 'J', 'x', 'K', 'L', 'M', 'x', 'N', 'O', 'P', 'x', 'Q', 'R', 'S', 'x', 'T', 'U', 'V', 'x', 'W', 'X', 'Y', 'x', 'Z']
4.86 µs ± 35.6 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

0
我会将问题分解为两个问题。首先将列表分成块,然后使用任何其他要插入的内容将它们连接起来。
from itertools import chain

def join_at(elements: list, item: object, n: int) -> list:
    # lambda for inserting the element when chunking
    insert = lambda i: [] if i == 0 else [item]
    # chunk up the original list based on where n lands, inserting the element along the way
    chunked = [insert(i) + elements[i:i+n] for i in range(0, len(elements), n)]
    # flatten the chunks back out
    return list(chain(*chunked))

print(join_at([0,1,2,3,4,5,6,7,8,9], 'x', 3))

这将在每个第三个位置插入一个x,并输出原始的整数列表:

[0, 1, 2, 'x', 3, 4, 5, 'x', 6, 7, 8, 'x', 9]


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接