【问题标题】:Cycle a list from alternating sides从交替的侧面循环列表
【发布时间】:2023-03-07 21:20:01
【问题描述】:

给定一个列表

a = [0,1,2,3,4,5,6,7,8,9]

我怎样才能得到

b = [0,9,1,8,2,7,3,6,4,5]

也就是说,产生一个新的列表,其中每个连续的元素交替取自原列表的两侧?

【问题讨论】:

  • 为什么不使用双端队列?! l1=list(range(10)); d1=deque(l1); [d1.pop() if i%2 else d1.popleft() for i,_ in enumerate(l1) if d1]

标签: python algorithm list iteration


【解决方案1】:
>>> [a[-i//2] if i % 2 else a[i//2] for i in range(len(a))]
[0, 9, 1, 8, 2, 7, 3, 6, 4, 5]

解释:
此代码从 a 的开头 (a[i//2]) 和结尾 (a[-i//2]) 交替选择数字 (if i%2 else)。总共选择了len(a) 数字,因此即使len(a) 是奇数,这也不会产生不良影响。
[-i//2 for i in range(len(a))] 产生0, -1, -1, -2, -2, -3, -3, -4, -4, -5,
[ i//2 for i in range(len(a))] 产生0, 0, 1, 1, 2, 2, 3, 3, 4, 4,
i%2FalseTrue 之间交替,
所以我们从a 中提取的索引是:0, -1, 1, -2, 2, -3, 3, -4, 4, -5

我对 pythonicness 的评价:
这种单线的好处是它很短并且显示出对称性(+i//2-i//2)。
但不好的是,这种对称性具有欺骗性:
有人可能会认为-i//2i//2 相同,但符号翻转了。 But in Python, integer division returns the floor 结果而不是截断为零。所以-1//2 == -1.
此外,我发现通过索引访问列表元素的方式比迭代更少。

【讨论】:

  • 您目前只拥有一个不需要滚动条并使用奇数长度列表的班轮的记录,因为 OP 有 stated they like one liners 我倾向于说这是最好的回答(+1 顺便说一句)
  • 这也是少数几个不会遇到奇数列表问题的答案之一。干得好!
  • 它在“优雅”部分失败了,因为没有人看到该代码可以立即理解它的作用。
  • @Matsemann 在发帖时这是最干净的方法,Noman has acknowledged 说这个答案不是很明显,因此他更喜欢我的答案。
  • @Matsemann Nah,我不会说没有人 ;-) 但我很欣赏你的意思,我同意。在我看来,Tadhg 的答案现在是迄今为止最蟒蛇的。郑重声明,如果 OP 不接受我的回答以促进真正的可读性,我一点也不介意。
【解决方案2】:

cycle 在从前向 iterreversed 获取项目之间。只要确保你在len(a)islice 停下来。

from itertools import islice, cycle

iters = cycle((iter(a), reversed(a)))
b = [next(it) for it in islice(iters, len(a))]

>>> b
[0, 9, 1, 8, 2, 7, 3, 6, 4, 5]

这可以很容易地放在一行中,但它会变得更加难以阅读:

[next(it) for it in islice(cycle((iter(a),reversed(a))),len(a))]

如果您愿意,将其放在一行中也会阻止您使用另一半迭代器:

>>> iters = cycle((iter(a), reversed(a)))
>>> [next(it) for it in islice(iters, len(a))]
[0, 9, 1, 8, 2, 7, 3, 6, 4, 5]
>>> [next(it) for it in islice(iters, len(a))]
[5, 4, 6, 3, 7, 2, 8, 1, 9, 0]

【讨论】:

  • 实际上,我更喜欢您根据@canaaerus 的想法制作的两行代码,而不是接受的答案。看到i//2我还是要想想为什么索引需要减半,但是有了[next(iters[n%2]) for n in a]立即理解。恕我直言,这两条线是“从列表的前面和结尾交替取数字”的最真实的演绎。我觉得它应该比你答案的结尾更突出。
  • @norman 我已经编辑了我的帖子以使用itertools.cycle,我比以前的版本更喜欢它。
  • 哇好 :-) 这更好。 Python代码的可读性有什么限制吗!?
【解决方案3】:

Python 2.7 中一个非常好的单行代码:

results = list(sum(zip(a, reversed(a))[:len(a)/2], ()))
>>>> [0, 9, 1, 8, 2, 7, 3, 6, 4, 5]

首先将列表与它的反向压缩,取出列表的一半,将元组相加形成一个元组,然后然后转换为列表。

在 Python 3 中,zip 返回一个生成器,因此您必须使用来自itertoolsislice

from itertools import islice
results = list(sum(islice(zip(a, reversed(a)),0,int(len(a)/2)),()))

编辑:这似乎只适用于偶数列表长度 - 奇数列表长度将省略中间元素:( 对 int(len(a)/2)int(len(a)/2) + 1 的小修正会给你一个重复中间值,请注意。

【讨论】:

  • TypeError: 'zip' 对象不可下标
  • @canaaerus 这适用于 Python 2.7。让我看看我可以在 Python 3 中做什么。
  • @canaaerus zip 在 3.5 中返回生成器,并在 2.7 中列出。使用itertools.islice
  • 当列表有奇数个元素时,这会丢失中间值。
  • 使用sum() 连接一个可迭代的列表或元组是错误的。它使用二次运行时。请改用itertools.chain.from_iterable() 或嵌套列表推导式。
【解决方案4】:

你可以来回pop

b = [a.pop(-1 if i%2 else 0) for i in range(len(a))]

注意:这会破坏原始列表a

【讨论】:

  • 原始列表是a,这会破坏原始列表。
  • @TadhgMcDonald-Jensen:OP 没有要求采用非破坏性方法。
  • 不,但这是您真正想在回答中提及的内容。
  • 这里是非破坏性替代方案:stackoverflow.com/questions/36533553/…
【解决方案5】:

使用正确的toolz

from toolz import interleave, take

b = list(take(len(a), interleave((a, reversed(a)))))

首先,我使用 itertools (Python 3) 尝试了类似于 Raymond Hettinger 的解决方案。

from itertools import chain, islice

interleaved = chain.from_iterable(zip(a, reversed(a)))
b = list(islice(interleaved, len(a)))

【讨论】:

    【解决方案6】:

    与其他一些答案没有太大不同,但它避免了用于确定索引符号的条件表达式。

    a = range(10)
    b = [a[i // (2*(-1)**(i&1))] for i in a]
    

    i & 1 在 0 和 1 之间交替。这会导致指数在 1 和 -1 之间交替。 会导致索引除数在 2 和 -2 之间交替,这会导致索引随着 i 的增加而从头到尾交替变化。序列为a[0]a[-1]a[1]a[-2]a[2]a[-3]等。

    (我在a 上迭代i,因为在这种情况下a 的每个值都等于它的索引。一般来说,迭代range(len(a))。)

    【讨论】:

    • 该死,我自己只是在摆弄-1的力量:-D
    • 嘿。随时将此添加到您的答案中,我将删除;它可能比分支稍微高效一些,但它的可读性明显较低 :) 因此,几乎不值得额外回答。
    • 您可能希望将for i in range(len(a)) 用于除range 函数的直接结果之外的任何列表。
    【解决方案7】:

    您的问题背后的基本原理是所谓的循环算法。 itertools-documentation-page 包含它的可能实现:

    from itertools import cycle, islice
    
    def roundrobin(*iterables):
        """This function is taken from the python documentation!
        roundrobin('ABC', 'D', 'EF') --> A D E B F C
        Recipe credited to George Sakkis"""
        pending = len(iterables)
        nexts = cycle(iter(it).__next__ for it in iterables) # next instead of __next__ for py2
        while pending:
            try:
                for next in nexts:
                    yield next()
            except StopIteration:
                pending -= 1
                nexts = cycle(islice(nexts, pending))
    

    所以您所要做的就是将您的列表分成两个子列表,一个从左端开始,一个从右端开始:

    import math
    mid = math.ceil(len(a)/2) # Just so that the next line doesn't need to calculate it twice
    
    list(roundrobin(a[:mid], a[:mid-1:-1]))
    # Gives you the desired result: [0, 9, 1, 8, 2, 7, 3, 6, 4, 5]
    

    或者,您可以创建一个更长的列表(包含从左到右的序列中的交替项目和从右到左的完整序列的项目)并且只获取相关元素:

    list(roundrobin(a, reversed(a)))[:len(a)]
    

    或将其用作带有 next 的显式生成器:

    rr = roundrobin(a, reversed(a))
    [next(rr) for _ in range(len(a))]
    

    或@Tadhg McDonald-Jensen 建议的快速变体(谢谢!):

    list(islice(roundrobin(a,reversed(a)),len(a)))
    

    【讨论】:

    • 我倾向于喜欢这个答案,尽管可能最有效的使用是b = list(islice(roundrobin(a,reversed(a)),len(a))),因为reversed 不需要像[::-1] 那样创建一个全新的列表,您不妨使用islice 再次从头到尾,减少列表创建大修。
    • @TadhgMcDonald-Jensen 我在示例中包含了reversed 并包含了您的建议(我希望正确归因)。感谢您的评论。
    【解决方案8】:

    不确定,这是否可以写得更紧凑,但它是有效的,因为它只使用迭代器/生成器

    a = [0,1,2,3,4,5,6,7,8,9]
    
    iter1 = iter(a)
    iter2 = reversed(a)
    b = [item for n, item in enumerate(
            next(iter) for _ in a for iter in (iter1, iter2)
        ) if n < len(a)]
    

    【讨论】:

    • iters = iter(a), reversed(a) ; b = [next(iters[n%2]) for n in range(len(a))] 最初我在我的答案中发布了这个,但后来我想出了一个我更喜欢的版本。
    【解决方案9】:

    为了好玩,这里有一个 itertools 变体:

    >>> a = [0,1,2,3,4,5,6,7,8,9]
    >>> list(chain.from_iterable(izip(islice(a, len(a)//2), reversed(a))))
    [0, 9, 1, 8, 2, 7, 3, 6, 4, 5]
    

    这适用于len(a) 是偶数的地方。对于奇数长度的输入,它需要一个特殊的代码。

    享受吧!

    【讨论】:

    • 当列表有奇数个元素时,这会丢失中间值。
    【解决方案10】:

    一点也不优雅,但它是一个笨拙的单行:

    a = range(10)
    [val for pair in zip(a[:len(a)//2],a[-1:(len(a)//2-1):-1]) for val in pair]
    

    请注意,它假定您这样做是为了获得一个偶数长度的列表。如果它中断,那么它就会中断(它会删除中间项)。请注意,我从here 得到了一些想法。

    【讨论】:

    • 当列表有奇数个元素时,这会丢失中间值。
    • 如我的回答中所述。 :-) 我实际上是以此为借口使用 zip,但你是对的,这种用法肯定会杀死奇数长度的列表。
    【解决方案11】:

    还没有看到两个版本:

    b = list(sum(zip(a, a[::-1]), ())[:len(a)])
    

    import itertools as it
    
    b = [a[j] for j in it.accumulate(i*(-1)**i for i in range(len(a)))]
    

    【讨论】:

    • 使用accumulate 获取索引序列[0, -1, 1, -2, 2, -3, 3, -4, 4, -5] 是个好主意。
    【解决方案12】:
    mylist = [0,1,2,3,4,5,6,7,8,9]
    result = []
    
    for i in mylist:
        result += [i, mylist.pop()]
    

    注意:

    当心:就像@Tadhg McDonald-Jensen 所说(见下面的评论) 它会破坏原始列表对象的一半。

    【讨论】:

    • 这会破坏一半的原始列表,您确定要这样做吗?
    • @TadhgMcDonald-Jensen 你是 100% 正确的!但是,我们不知道真正的 OP 目标是什么。
    • 当列表有奇数个元素时,这会丢失中间值。
    【解决方案13】:

    对偶数列表执行此操作的一种方法(受this post 启发):

    a = range(10)
    
    b = [val for pair in zip(a[:5], a[5:][::-1]) for val in pair]
    

    【讨论】:

    • 大声笑,这和我的答案完全一样,省略了总和和列表。
    • 事实上我想接受你的答案(喜欢 1-liners),但如果我将它应用到 a = range(11),它会丢失数字 '5'
    • @pythonic,long one liner is not very pythonesque 所以你的 SO 名字很容易误导。
    • 哈,你说得有道理。我不清楚实际上我应该接受哪个答案
    • @Pythonic 这里的很多答案都遇到了同样的问题。只有诺曼的答案解决了这个问题,并且是一个短的单线(其他单线更长)。我认为他赢得了这个问题,应该接受答案:)
    【解决方案14】:

    我会做这样的事情

    a = [0,1,2,3,4,5,6,7,8,9]
    b = []
    i = 0
    j = len(a) - 1
    mid = (i + j) / 2
    while i <= j:
        if i == mid and len(a) % 2 == 1:
            b.append(a[i])
            break
        b.extend([a[i], a[j]])
        i = i + 1
        j = j - 1
    
    print b
    

    【讨论】:

    • 当列表有奇数个元素时,这会丢失中间值。
    • 是的@TadhgMcDonald-Jensen 你是对的,让我来纠正它
    【解决方案15】:

    您可以将列表分成中间左右两部分,将后半部分反转并压缩两个分区,如下所示:

    a = [0,1,2,3,4,5,6,7,8,9]
    mid = len(a)//2
    l = []
    for x, y in zip(a[:mid], a[:mid-1:-1]):
        l.append(x)
        l.append(y)
    # if the length is odd
    if len(a) % 2 == 1:
        l.append(a[mid])
    print(l)
    

    输出:

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

    【讨论】:

      猜你喜欢
      • 2012-01-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-10-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多