Pythonic 循环列表

30

假设我有一个列表,

l = [1, 2, 3, 4, 5, 6, 7, 8]

我想获取任意元素的索引及其相邻元素的值。例如:

i = l.index(n)
j = l[i-1]
k = l[i+1]

然而,对于当i == len(l) - 1时的特殊情况则会失败。因此我想把它简单地处理一下,

if i == len(l) - 1:
    k = l[0]
else:
    k = l[i+1]

有没有一种 Pythonic 的方法来做这件事?


你是否想要在给定小于零或大于列表长度的索引时实现特殊行为? - jimifiki
只是为了包装一下。我总是希望jk指向某些东西。而且我希望能够通过jk遍历整个列表。 - john
1
你接受了一个答案,但没有注意到超出范围的索引... - jimifiki
我有点困惑。如果你将索引值对列表长度取模,它怎么可能超出范围呢? - john
我的意思是k[10]有一个含义,我以为你不想让它表示k[2],而是希望引发一个错误。就这样。 - jimifiki
如果您仔细阅读我的问题,您会发现 k[10] 永远不会发生,因为 l.index(n) 只会返回最大值为 len(l) - 1 的东西,在这种情况下是 i = 7。我将 i 增加了 1,就这样。虽然我没有明确地写出来,但我在 l.index(n) 前面加上了 if n in l,并适当处理了错误。因此,接受的答案是这个特定问题的解决方案,但还是谢谢您的关注。 - john
7个回答

46

你可以使用取模运算符!

i = len(l) - 1
jIndex = (i - 1) % len(l)
kIndex = (i + 1) % len(l)

j = l[jIndex]
k = l[kIndex]

或者说,更简洁地说:

k = l[(i + 1) % len(l)]

6
随机评论:请注意,如果0 <= i < len(l),则可以将l[(i + 1) % len(l)]写为l[i - (len(l)-1)],从而避免使用取模操作。(它返回的索引往往是负数,这意味着从末尾开始计数,但其值是正确的。) - Armin Rigo
@ArminRigo,你的意思是什么 (它给出的索引通常是负数,这意味着从末尾开始计数,但其值是正确的。) - user5319825

33

用 %(取模)运算符对固定长度的列表进行环绕是最简单的方法。

list_element = my_list[idx % len(my_list)]

但无论如何,看看https://docs.python.org/library/itertools.html#itertools.cycle

from itertools import cycle

for p in cycle([1,2,3]):
  print "endless cycle:", p

还请注意警告:请注意,该工具包成员可能需要大量的辅助存储空间(根据可迭代对象的长度)。


8
典型的将值调整到特定范围的方法是使用`%`运算符:
k = l[(i + 1) % len(l)]

5

如果你想要将它作为一个类,我已经快速编写了这个CircularList:

import operator

class CircularList(list):
    def __getitem__(self, x):
        if isinstance(x, slice):
            return [self[x] for x in self._rangeify(x)]

        index = operator.index(x)
        try:
            return super().__getitem__(index % len(self))
        except ZeroDivisionError:
            raise IndexError('list index out of range')

    def _rangeify(self, slice):
        start, stop, step = slice.start, slice.stop, slice.step
        if start is None:
            start = 0
        if stop is None:
            stop = len(self)
        if step is None:
            step = 1
        return range(start, stop, step)

它支持切片操作,所以

CircularList(range(5))[1:10] == [1, 2, 3, 4, 0, 1, 2, 3, 4]

1
如果我打印 a = CircularList([]) 然后 a[0],我会得到一个 ZeroDivisionError 错误,这是错误的。你应该添加 except ZeroDivisionError: raise IndexError('list index out of range') - Daniel Moskovich

0
使用其他人提到的模数方法,我创建了一个类,其中包含实现循环列表的属性。
class Circle:
    """Creates a circular array of numbers

    >>> c = Circle(30)
    >>> c.position
    -1
    >>> c.position = 10
    >>> c.position
    10
    >>> c.position = 20
    >>> c.position
    20
    >>> c.position = 30
    >>> c.position
    0
    >>> c.position = -5
    >>> c.position
    25
    >>>

    """
    def __init__(self, size):
        if not isinstance(size, int):  # validating length
            raise TypeError("Only integers are allowed")
        self.size = size

    @property
    def position(self):
        try:
            return self._position
        except AttributeError:
            return -1

    @position.setter
    def position(self, value):
        positions = [x for x in range(0, self.size)]
        i = len(positions) - 1
        k = positions[(i + value + 1) % len(positions)]
        self._position = k

0
a = [2,3,5,7,11,13]

def env (l, n, count):
    from itertools import cycle, islice
    index = l.index(n) + len(l)
    aux = islice (cycle (l), index - count, index + count + 1)
    return list(aux)

表现如下

>>> env (a, 2,1)
[13, 2, 3]
>>> env (a,13,2)
[7, 11, 13, 2, 3]
>>> env (a,7,0)
[7]

0

如果您不想进行包装,最Pythonic的答案是使用切片。缺失的邻居将被替换为None。例如:

def nbrs(l, e):
   i = l.index(e)
   return (l[i-1:i] + [None])[0], (l[i+1:i+2] + [None])[0]

下面是这个函数的工作方式:

>>> nbrs([2,3,4,1], 1)
(4, None)
>>> nbrs([1,2,3], 1)
(None, 2)
>>> nbrs([2,3,4,1,5,6], 1)
(4, 5)
>>> nbrs([], 1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in nbrs
ValueError: 1 is not in list

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