Python:在列表中插入元素的最优雅方法是什么?

36

输入:

intersperse(666, ["once", "upon", "a", 90, None, "time"])

输出:

["once", 666, "upon", 666, "a", 666, 90, 666, None, 666, "time"]

如何用最优雅的方式(即使用 Pythonic 风格)编写 intersperse 函数?

15个回答

39

我本来可以自己编写生成器,就像这样:

def joinit(iterable, delimiter):
    it = iter(iterable)
    yield next(it)
    for x in it:
        yield delimiter
        yield x

1
注意:第一个参数可以称为“可迭代对象”。函数可以被称为“joinit()”。顺便说一下,你的解决方案的好处是它也适用于空序列(next()引发StopIteration,生成器joinseq()立即返回)。 - jfs
@J.F.:感谢你的提示。把它命名为“iterable”应该是我会做的第一件事情。但相反,我养成了像其他人一样称呼它为“sequence”的习惯。 :) - Jeff Mercado
6
我喜欢 joinit 这个双关语,它既可以表示 "加入它",也可以表示 "加入可迭代对象(iterable)"。 - Claudiu
6
我喜欢你在两年后回来指出这一点 :) - undergroundmonorail

21

itertools可以拯救你
- 或者 -
你能在一行代码中使用多少个itertools函数?

from itertools import chain, izip, repeat, islice

def intersperse(delimiter, seq):
    return islice(chain.from_iterable(izip(repeat(delimiter), seq)), 1, None)

使用方法:

>>> list(intersperse(666, ["once", "upon", "a", 90, None, "time"])
["once", 666, "upon", 666, "a", 666, 90, 666, None, 666, "time"]

啊,回到上一个版本。 :) - Jeff Mercado
@JeffMercado:是的,谢谢您的评论(我想这是您?) :) 它确实有效... - Felix Kling
@JeffMercado:我知道......同步问题;)不确定这是否真的有效......迭代器太多了....我喜欢你的解决方案:) 但是知道多种方法也很好(尽管Python的座右铭是只有一种方法;))。 - Felix Kling
1
请注意,对于chain的参数扩展需要迭代所有s。对于像intersperse(666, repeat(777))这样的情况,生成器可以很好地处理。您可以通过改用chain.from_iterable(izip(...))来解决这个问题。 - Andrew Clark
@Andrew:哦,我不知道。但是现在想想很有道理...谢谢,我会把这个加入我的答案中。 - Felix Kling
如果Python的语法不同,我认为这将会更加优雅。 - Claudiu

17

另一种适用于序列的选项:

def intersperse(seq, value):
    res = [value] * (2 * len(seq) - 1)
    res[::2] = seq
    return res

7
解决方案很简单,可以使用more_itertools.intersperse来实现:
>>> from more_itertools import intersperse
>>> list(intersperse(666, ["once", "upon", "a", 90, None, "time"]))
['once', 666, 'upon', 666, 'a', 666, 90, 666, None, 666, 'time']

从技术上讲,这个答案并不是“编写”intersperse函数,而只是从另一个库中使用它。但这可能会避免其他人重复发明轮子。


4
def intersperse(word,your_list):
    x = [j for i in your_list for j in [i,word]]

>>> intersperse(666, ["once", "upon", "a", 90, None, "time"])
['once', 666, 'upon', 666, 'a', 666, 90, 666, None, 666, 'time', 666]

[编辑] 以下是更正后的代码:

def intersperse(word,your_list):
    x = [j for i in your_list for j in [i,word]]
    x.pop()
    return x

>>> intersperse(666, ["once", "upon", "a", 90, None, "time"])
['once', 666, 'upon', 666, 'a', 666, 90, 666, None, 666, 'time']

1
最后的 666 不应该在那里。 - Claudiu

4
我建议使用简单的生成器。
def intersperse(val, sequence):
    first = True
    for item in sequence:
        if not first:
            yield val
        yield item
        first = False

然后,您可以像这样获取您的列表:
>>> list(intersperse(666, ["once", "upon", "a", 90, None, "time"]))
['once', 666, 'upon', 666, 'a', 666, 90, 666, None, 666, 'time']

或者你可以这样做:

def intersperse(val, sequence):
    for i, item in enumerate(sequence):
        if i != 0:
            yield val
        yield item

我不确定哪种写法更符合Pythonic风格。


到目前为止,我最喜欢这个:没有切片,也没有对迭代器进行任何特定的操作,比如调用 len。虽然不是一行代码,但看起来比一行代码更好看。 - Claudiu

2

我刚想到了这个,搜了一下看看有没有更好的东西……但我认为没有 :-)

def intersperse(e, l):    
    return list(itertools.chain(*[(i, e) for i in l]))[0:-1]

2
如何考虑以下内容:
from itertools import chain,izip_longest

def intersperse(x,y):
     return list(chain(*izip_longest(x,[],fillvalue=y)))

1

不知道是否符合Python的风格,但它非常简单:

def intersperse(elem, list):
    result = []
    for e in list:
      result.extend([e, elem])
    return result[:-1]

我想过那个方法,但我不喜欢在最后切割列表。 - Claudiu
我可以将结果初始化为list[0],但这样我必须先检查列表是否为空。[][:-1]方便地返回[],使我免于这个问题。似乎生成器的答案是迄今为止唯一不使用切片的答案。 - sverre

1

相比于yield next(iterator)或者itertools.iterator_magic(),我认为这个看起来更加美观易懂 :)

def list_join_seq(seq, sep):
  for i, elem in enumerate(seq):
    if i > 0: yield sep
    yield elem

print(list(list_join_seq([1, 2, 3], 0)))  # [1, 0, 2, 0, 3]

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