也可以访问上一个和下一个值的Python循环


90

如何遍历对象列表,访问上一个,当前和下一个项目?像这样的C / C ++代码一样,在Python中?

foo = somevalue;
previous = next = 0;

for (i=1; i<objects.length(); i++) {
    if (objects[i]==foo) {
        previous = objects[i-1];
        next = objects[i+1];
    }
}

如果foo位于列表的开头或结尾,该怎么办?当前,这将超出数组的范围。
布赖恩2009年

2
如果需要首次出现“ foo”,则在匹配时从“ for”块执行“ break”。
van van

您是否要从第1个(而不是第0个)元素开始迭代,并在最后一个但又一个元素处结束迭代?
smci

是否保证foo在列表中仅发生一次?如果发生乘法,则此处的某些方法将失败,或仅找到第一个。而且,如果它永远不会发生,其他方法将失败,或者抛出诸如ValueError之类的异常。提供一些测试用例会有所帮助。
smci

同样,这里的示例是一系列对象,它们的长度都已知,并且可以索引。这里的某些答案被推广到迭代器,这并不总是可转位,并不总是有长度,而不是总是有限..
SMCI

Answers:


107

这应该可以解决问题。

foo = somevalue
previous = next_ = None
l = len(objects)
for index, obj in enumerate(objects):
    if obj == foo:
        if index > 0:
            previous = objects[index - 1]
        if index < (l - 1):
            next_ = objects[index + 1]

这是该enumerate函数的文档。


17
但最好的做法是不要使用“ next”作为变量名,因为它是内置函数。
mkosmala

1
这个编辑后的版本依然没有逻辑上的声音:在循环的结束objnext_将是最后一次迭代,这可能会产生意想不到的副作用同一个对象。
TemporalWolf

问题语句明确表示,OP希望从第1个(而不是第0个)元素开始迭代,并在最后一个但最后一个元素处结束迭代。因此,index应从开始运行1 ... (l-1),而不是从0 ... l这里开始运行,并且不需要特殊情况的if子句。顺便说一句,有一个参数,enumerate(..., start=1)但没有for end。所以我们真的不想使用enumerate()
smci

147

到目前为止,解决方案仅处理列表,并且大多数都在复制列表。以我的经验,很多时候这是不可能的。

同样,它们也不处理列表中可以包含重复元素的事实。

问题的标题为“循环中的上一个和下一个值”,但是如果您在循环中在此处运行大多数答案,则最终将在每个元素上再次遍历整个列表以找到它。

所以我刚刚创建了一个函数。使用该itertools模块,对可迭代对象进行拆分和切片,并生成具有前一个元素和下一个元素在一起的元组。不完全是您的代码所做的事情,但是值得一看,因为它可能可以解决您的问题。

from itertools import tee, islice, chain, izip

def previous_and_next(some_iterable):
    prevs, items, nexts = tee(some_iterable, 3)
    prevs = chain([None], prevs)
    nexts = chain(islice(nexts, 1, None), [None])
    return izip(prevs, items, nexts)

然后循环使用它,您将拥有上一个和下一个项目:

mylist = ['banana', 'orange', 'apple', 'kiwi', 'tomato']

for previous, item, nxt in previous_and_next(mylist):
    print "Item is now", item, "next is", nxt, "previous is", previous

结果:

Item is now banana next is orange previous is None
Item is now orange next is apple previous is banana
Item is now apple next is kiwi previous is orange
Item is now kiwi next is tomato previous is apple
Item is now tomato next is None previous is kiwi

它可以与任何大小的列表一起使用(因为它不会复制列表),并且可以与任何可迭代的文件(文件,集合等)一起使用。这样,您可以遍历整个序列,并在循环中获得上一个和下一个项目。无需再次搜索序列中的项目。

代码的简短说明:

  • tee 用于有效地在输入序列上创建3个独立的迭代器
  • chain将两个序列链接为一个;在这里用于将单元素序列附加[None]prevs
  • islice用于制作除第一个元素外的所有元素的序列,然后chain用于None在其末尾附加a
  • 现在有3个基于some_iterable该序列的独立序列,如下所示:
    • prevsNone, A, B, C, D, E
    • itemsA, B, C, D, E
    • nextsB, C, D, E, None
  • 最后izip用于将3个序列更改为一个三胞胎序列。

请注意,izip当任何输入序列用尽时,它将停止,因此的最后一个元素prevs将被忽略,这是正确的-没有这样的元素,最后一个元素将是它的prev。我们可以尝试从中删除最后一个元素,prevs但是izip的行为使其变得多余

还要注意的是teeizipislicechain来自itertools模块; 它们动态地(懒惰地)对输入序列进行操作,这使它们高效,并且不需要引入整个序列一次存储在内存中的需求。

在中python 3,导入时会显示错误izip,您可以使用zip代替izip。无需进口zip,这是预定义的python 3-


3
@becomingGuru:不必将SO变成Python参考文档的镜像。所有这些功能在官方文档中都得到了很好的解释(带有示例)
Eli Bendersky

1
@becomingGuru:添加了指向文档的链接。
nosklo

6
@LakshmanPrasad我很喜欢Wiki,所以我添加了一些解释:-)。
科斯2012年

7
可能值得一提的是,在Python 3中izip可以用内置zip函数;-)
Tomasito665'2

1
这是一个不错的解决方案,但似乎过于复杂。请参阅受此启发的stackoverflow.com/a/54995234/1265955
维多利亚

6

使用列表推导,返回一个包含当前,上一个和下一个元素的三元组:

three_tuple = [(current, 
                my_list[idx - 1] if idx >= 1 else None, 
                my_list[idx + 1] if idx < len(my_list) - 1 else None) for idx, current in enumerate(my_list)]

4

我不知道这还没有出现,因为它仅使用内置函数并且可以轻松扩展到其他偏移量:

values = [1, 2, 3, 4]
offsets = [None] + values[:-1], values, values[1:] + [None]
for value in list(zip(*offsets)):
    print(value) # (previous, current, next)

(None, 1, 2)
(1, 2, 3)
(2, 3, 4)
(3, 4, None)

4

这是使用生成器的版本,没有边界错误:

def trios(iterable):
    it = iter(iterable)
    try:
        prev, current = next(it), next(it)
    except StopIteration:
        return
    for next in it:
        yield prev, current, next
        prev, current = current, next

def find_prev_next(objects, foo):
    prev, next = 0, 0
    for temp_prev, current, temp_next in trios(objects):
        if current == foo:
            prev, next = temp_prev, temp_next
    return prev, next

print(find_prev_next(range(10), 1))
print(find_prev_next(range(10), 0))
print(find_prev_next(range(10), 10))
print(find_prev_next(range(0), 10))
print(find_prev_next(range(1), 10))
print(find_prev_next(range(2), 10))

请注意,边界行为是我们从不在第一个或最后一个元素中查找“ foo”,这与您的代码不同。同样,边界语义很奇怪...并且很难从您的代码中理解:)


2

使用条件表达式来简化python> = 2.5

def prenext(l,v) : 
   i=l.index(v)
   return l[i-1] if i>0 else None,l[i+1] if i<len(l)-1 else None


# example
x=range(10)
prenext(x,3)
>>> (2,4)
prenext(x,0)
>>> (None,2)
prenext(x,9)
>>> (8,None)

2

对于寻求解决方案且又想循环元素的任何人,下面的方法可能有用-

from collections import deque  

foo = ['A', 'B', 'C', 'D']

def prev_and_next(input_list):
    CURRENT = input_list
    PREV = deque(input_list)
    PREV.rotate(-1)
    PREV = list(PREV)
    NEXT = deque(input_list)
    NEXT.rotate(1)
    NEXT = list(NEXT)
    return zip(PREV, CURRENT, NEXT)

for previous_, current_, next_ in prev_and_next(foo):
    print(previous_, current_, next)

在下一个next_下划线?无法编辑-“必须至少为6 ...”
Xpector

为什么这比简单的for循环和访问更可取objects[i-1], objects[i], objects[i+1]?还是发电机?在我看来,这完全是晦涩的。此外,由于PREV和NEXT会复制数据,因此它不必要地使用3倍内存。
smci

@smci如何使i+1方法适用于列表中的最后一个元素?那么下一个元素应该是第一个。我越界了。
ElectRocnic

1

使用生成器,非常简单:

signal = ['→Signal value←']
def pniter( iter, signal=signal ):
    iA = iB = signal
    for iC in iter:
        if iB is signal:
            iB = iC
            continue
        else:
            yield iA, iB, iC
        iA = iB
        iB = iC
    iC = signal
    yield iA, iB, iC

if __name__ == '__main__':
    print('test 1:')
    for a, b, c in pniter( range( 10 )):
        print( a, b, c )
    print('\ntest 2:')
    for a, b, c in pniter([ 20, 30, 40, 50, 60, 70, 80 ]):
        print( a, b, c )
    print('\ntest 3:')
    cam = { 1: 30, 2: 40, 10: 9, -5: 36 }
    for a, b, c in pniter( cam ):
        print( a, b, c )
    for a, b, c in pniter( cam ):
        print( a, a if a is signal else cam[ a ], b, b if b is signal else cam[ b ], c, c if c is signal else cam[ c ])
    print('\ntest 4:')
    for a, b, c in pniter([ 20, 30, None, 50, 60, 70, 80 ]):
        print( a, b, c )
    print('\ntest 5:')
    for a, b, c in pniter([ 20, 30, None, 50, 60, 70, 80 ], ['sig']):
        print( a, b, c )
    print('\ntest 6:')
    for a, b, c in pniter([ 20, ['→Signal value←'], None, '→Signal value←', 60, 70, 80 ], signal ):
        print( a, b, c )

请注意,包含None且与信号值相同的值的测试仍然有效,因为对信号值的检查使用“ is”,并且信号是Python不会内插的值。但是,任何单例标记值都可以用作信号,这在某些情况下可以简化用户代码。


8
“这很简单”
Miguel Stevens

当您指的是“前哨”时,不要说“信号”。另外,if iB is signal除非信号=无,否则永远不要使用比较对象的相等性,在这种情况下,请直接写入None。请勿将其iter用作参数名称,因为这会掩盖内置函数iter()。同上next。无论如何,生成器方法可能只是yield prev, curr, next_
smci

@smci也许您的词典对信号和哨兵有不同的定义。我之所以专门使用“ is”,是因为我想测试特定项目,而不是其他具有相同值的项目,因此“ is”是该测试的正确运算符。仅使用iter和next影子不涉及其他内容,因此不是问题,但可以达成共识,而不是最佳实践。您需要显示更多代码以提供上一个声明的上下文。
维多利亚

@Victoria:'sentinel [value]'是定义明确的软件术语,'signal'不是(不是在谈论信号处理或内核信号)。至于[用Pythonis代替而不是==]进行比较,这是一个众所周知的陷阱,原因有以下几种:您可以不使用它来处理字符串,因为您依赖于cPython内部字符串,但是即使这样v1 = 'monkey'; v2 = 'mon'; v3 = 'keyv1 is (v2 + v3)也可以False。而且,如果您的代码曾经切换到使用对象而不是整数/字符串,则使用is会中断。因此,通常您应该使用它==来比较相等性。
smci

@smci由于不同的人群使用不同的术语,计算机软件中最困难的问题是通信,网络无法正常工作。俗话说,标准很棒,每个人都有。我完全理解Python ==和is运算符之间的区别,这正是我选择使用is的原因。如果您不了解先入为主的“术语和规则”,就会意识到==将允许任何比较相等的项终止该序列,而using是仅终止于用作信号的特定对象(如果您愿意,也可以定点)。
维多利亚

1

两种简单的解决方案:

  1. 如果必须同时定义上一个和下一个值的变量:
alist = ['Zero', 'One', 'Two', 'Three', 'Four', 'Five']

prev = alist[0]
curr = alist[1]

for nxt in alist[2:]:
    print(f'prev: {prev}, curr: {curr}, next: {nxt}')
    prev = curr
    curr = nxt

Output[1]:
prev: Zero, curr: One, next: Two
prev: One, curr: Two, next: Three
prev: Two, curr: Three, next: Four
prev: Three, curr: Four, next: Five
  1. 如果必须使用当前值变量遍历列表中的所有值:
alist = ['Zero', 'One', 'Two', 'Three', 'Four', 'Five']

prev = None
curr = alist[0]

for nxt in alist[1:] + [None]:
    print(f'prev: {prev}, curr: {curr}, next: {nxt}')
    prev = curr
    curr = nxt

Output[2]:
prev: None, curr: Zero, next: One
prev: Zero, curr: One, next: Two
prev: One, curr: Two, next: Three
prev: Two, curr: Three, next: Four
prev: Three, curr: Four, next: Five
prev: Four, curr: Five, next: None

0

您可以仅index在列表上使用以查找位置somevalue,然后根据需要获取上一个和下一个:


def find_prev_next(elem, elements):
    previous, next = None, None
    index = elements.index(elem)
    if index > 0:
        previous = elements[index -1]
    if index < (len(elements)-1):
        next = elements[index +1]
    return previous, next


foo = 'three'
list = ['one','two','three', 'four', 'five']

previous, next = find_prev_next(foo, list)

print previous # should print 'two'
print next # should print 'four'



0

AFAIK这应该很快,但是我没有测试:

def iterate_prv_nxt(my_list):
    prv, cur, nxt = None, iter(my_list), iter(my_list)
    next(nxt, None)

    while True:
        try:
            if prv:
                yield next(prv), next(cur), next(nxt, None)
            else:
                yield None, next(cur), next(nxt, None)
                prv = iter(my_list)
        except StopIteration:
            break

用法示例:

>>> my_list = ['a', 'b', 'c']
>>> for prv, cur, nxt in iterate_prv_nxt(my_list):
...    print prv, cur, nxt
... 
None a b
a b c
b c None

0

我认为这有效并且并不复杂

array= [1,5,6,6,3,2]
for i in range(0,len(array)):
    Current = array[i]
    Next = array[i+1]
    Prev = array[i-1]

我几乎不知道python支持数组中的负索引,谢谢!
ElectRocnic

1
最终它会失败:(
ElectRocnic

0

非常C / C ++样式的解决方案:

    foo = 5
    objectsList = [3, 6, 5, 9, 10]
    prev = nex = 0
    
    currentIndex = 0
    indexHigher = len(objectsList)-1 #control the higher limit of list
    
    found = False
    prevFound = False
    nexFound = False
    
    #main logic:
    for currentValue in objectsList: #getting each value of list
        if currentValue == foo:
            found = True
            if currentIndex > 0: #check if target value is in the first position   
                prevFound = True
                prev = objectsList[currentIndex-1]
            if currentIndex < indexHigher: #check if target value is in the last position
                nexFound = True
                nex = objectsList[currentIndex+1]
            break #I am considering that target value only exist 1 time in the list
        currentIndex+=1
    
    if found:
        print("Value %s found" % foo)
        if prevFound:
            print("Previous Value: ", prev)
        else:
            print("Previous Value: Target value is in the first position of list.")
        if nexFound:
            print("Next Value: ", nex)
        else:
            print("Next Value: Target value is in the last position of list.")
    else:
        print("Target value does not exist in the list.")

-1

Pythonic优雅的方式:

objects = [1, 2, 3, 4, 5]
value = 3
if value in objects:
   index = objects.index(value)
   previous_value = objects[index-1]
   next_value = objects[index+1] if index + 1 < len(objects) else None

1
如果value最后将失败。此外,返回最后一个元素是previous_value,如果value是第一位的。
董里(Trang Oul)

这取决于您的要求。from的负索引previous_value 将返回列表中的最后一个元素并next_value引发IndexError错误,即错误
ImportError 2016年

我有时已经在寻找这种方法了。现在我明白了。谢谢@ImportError。现在,我可以很好地扩展脚本了。.–
Azam,

局限性:value可能在中发生多次objects,但使用.index()只会发现其首次出现(如果未发生,则为ValueError)。
smci
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.