循环也访问上一个和下一个值

Posted

技术标签:

【中文标题】循环也访问上一个和下一个值【英文标题】:Loop that also accesses previous and next values 【发布时间】:2010-11-03 23:01:32 【问题描述】:

如何遍历对象列表,访问上一个、当前和下一个项目?像这个 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 在列表的开头或结尾会发生什么?目前,这将超出您的数组范围。 如果你需要第一次出现“foo”,那么在匹配时从“for”块中“break”。 您要从第 1 个(不是第 0 个)元素开始迭代,并在最后一个元素结束迭代吗? 是否保证foo 在列表中只出现一次?如果它多次出现,这里的一些方法会失败,或者只找到第一个。如果它永远不会发生,其他方法将会失败,或者抛出像 ValueError 这样的异常。提供一些测试用例会有所帮助。 另外,您的示例是一系列对象,它们都具有已知长度,并且是可索引的。这里的一些答案是泛化到迭代器,它们并不总是可索引的,并不总是有长度的,也不总是有限的.. 【参考方案1】:

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

此外,它们不处理列表中可以有重复元素的事实。

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

所以我刚刚创建了一个函数。使用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 现在有 3 个基于 some_iterable 的独立序列,如下所示: prevs: None, A, B, C, D, E items: A, B, C, D, E nexts: B, C, D, E, None 最后,izip 用于将 3 个序列变为一个三元组序列。

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

还要注意teeizipislicechain来自itertools模块;它们在运行中(懒惰地)对其输入序列进行操作,这使它们变得高效,并且不需要随时将整个序列存储在内存中。

python 3中,导入izip会报错,你可以用zip代替izip。无需导入zip,预定义在python 3 - source

【讨论】:

@becomingGuru:没有必要将 SO 变成 Python 参考文档的镜像。所有这些功能在官方文档中都有很好的解释(带有示例) @becomingGuru:添加了文档链接。 @LakshmanPrasad 我很喜欢维基,所以我添加了一些解释:-)。 值得一提的是,在 Python 3 中 izip 可以替换为内置的 zip 函数 ;-) 这是一个不错的解决方案,但似乎过于复杂。请参阅受此启发的***.com/a/54995234/1265955。【参考方案2】:

这应该可以解决问题。

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 函数的文档。

【讨论】:

但最好不要使用 'next' 作为变量名,因为它是一个内置函数。 编辑后的版本在逻辑上仍然不合理:在循环结束时,objnext_ 将是最后一次迭代的同一个对象,这可能会产生意想不到的副作用。 问题陈述明确表示 OP 希望从第一个(而不是第 0 个)元素开始迭代,并在最后一个元素处结束迭代。所以index 应该从1 ... (l-1) 运行,而不是像你在这里的0 ... l,并且不需要特殊情况的 if 子句。顺便说一句,有一个参数enumerate(..., start=1),但不是end。所以我们真的不想使用enumerate() 如果index &gt;= (l -1),可以考虑将“下一个”对象设为None?只需使用 else 语句即可轻松实现 - if index &lt; (l - 1): .... else:?【参考方案3】:

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

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)]

【讨论】:

魅力十足!谢谢。这适用于 for 循环“ for c,p,n in three_tuple: 然后在循环内您可以访问 c=current,p=prev 和 n =nxt【参考方案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)

【讨论】:

【参考方案5】:

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

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”,这与您的代码不同。同样,边界语义很奇怪......并且很难从您的代码中理解:)

【讨论】:

【参考方案6】:

如果您只想迭代 具有 下一个和上一个元素的元素(例如,您想跳过第一个和最后一个元素)并且您的输入是一个列表,您可以 @987654321 @输入本身没有第一个元素和没有第二个元素:

words = "one two three four five".split()

for prev, current, nxt in zip(words, words[1:], words[2:]):
    print(prev, current, nxt)

输出:

one two three
two three four
three four five

如果您不想跳过第一个和最后一个元素,并且希望在第一个元素上时将 prev 设置为 None(并且在最后一个元素上将 nxt 设置为 None元素),首先用这些值填充您的列表:

words = "one two three four five".split()

padded_words = [None, *words, None]

for prev, current, nxt in zip(padded_words, padded_words[1:], padded_words[2:]):
    print(prev, current, nxt)

输出:

None one two
one two three
two three four
three four five
four five None

你可以用任何你喜欢的东西来填充。如果您希望您的列表“环绕”(例如,第一个元素的 prev 是最后一个元素,最后一个元素的 nxt 是第一个元素),请用这些而不是 @987654333 填充您的输入@:

# avoid IndexError if words is an empty list
padded_words = [words[-1], *words, words[0]] if words else []

输出:

five one two
one two three
two three four
three four five
four five one

【讨论】:

【参考方案7】:

使用条件表达式来简化 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)

【讨论】:

【参考方案8】:

对于正在寻找解决方案并希望循环元素的任何人,以下可能有效 -

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 ...” 为什么这比简单的for循环和访问objects[i-1], objects[i], objects[i+1]更可取?还是发电机?对我来说,这似乎完全是蒙昧主义者。此外,由于 PREV 和 NEXT 会复制数据,因此它不必要地使用了 3 倍内存。 @smci 如何让i+1 方法适用于列表中的最后一个元素?它的下一个元素应该是第一个。我越界了。【参考方案9】:

两个简单的解决方案:

    如果必须定义前一个值和下一个值的变量:
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
    如果列表中的所有值都必须由当前值变量遍历:
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

【讨论】:

【参考方案10】:

使用生成器,很简单:

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 不实习的值。不过,任何单例标记值都可以用作信号,这在某些情况下可能会简化用户代码。

【讨论】:

“很简单” 当你的意思是“哨兵”时,不要说“信号”。此外,永远不要使用if iB is signal 来比较对象是否相等,除非信号 = None,在这种情况下直接写 None。不要使用iter 作为参数名称,因为它会影响内置的iter()。同上next。无论如何,生成器方法可以简单地是yield prev, curr, next_ @smci 也许您的字典对信号和哨兵有不同的定义。我专门使用“is”是因为我想测试特定项目,而不是其他具有相同价值的项目,“is”是该测试的正确运算符。仅使用 iter 和 next shadow 没有以其他方式引用的东西,所以不是问题,但同意,不是最佳实践。您需要显示更多代码来为您最后的声明提供上下文。 @Victoria:'sentinel [value]' 是定义明确的软件术语,'signal' 不是(不是在谈论信号处理或内核信号)。至于[用is而不是==比较Python中的东西],这是一个众所周知的陷阱,原因有很多:你可以摆脱它的字符串,因为你依赖cPython实习字符串,但即便如此v1 = 'monkey'; v2 = 'mon'; v3 = 'key,然后v1 is (v2 + v3) 给出False。如果您的代码曾经切换到使用对象而不是整数/字符串,那么使用 is 将会中断。所以一般来说你应该使用==来比较相等性。 @smci 计算机软件中最困难的问题是通信,网络无法正常工作,因为不同的人群使用不同的术语。俗话说,标准很好,人人都有。我完全理解 Python == 和 is 运算符之间的区别,这正是我选择使用 is 的原因。如果您回顾一下先入为主的“术语和规则”,您会意识到 == 将允许任何比较等于的项目终止序列,而使用 is 只会在用作信号的特定对象处终止(或哨兵,如果你愿意)。【参考方案11】:

您可以使用列表中的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'

【讨论】:

【参考方案12】:

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

【讨论】:

【参考方案13】:

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

【讨论】:

如果value 在末尾,它将失败。此外,如果 value 是第一个元素,则将最后一个元素返回为 previous_value 这取决于您的要求。 previous_value 的负索引将返回列表中的最后一个元素,next_value 将引发 IndexError,这就是错误 我有时一直在寻找这种方法..现在我明白了..谢谢@ImportError。现在我可以很好地扩展我的脚本了.. 限制:value 可能在objects 中出现多次,但使用.index() 只会找到它的第一次出现(如果没有出现,则返回ValueError)。【参考方案14】:

我认为这可行,并不复杂

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支持数组中的负索引,谢谢! 它最终会失败:(【参考方案15】:

非常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.")

【讨论】:

以上是关于循环也访问上一个和下一个值的主要内容,如果未能解决你的问题,请参考以下文章

如何在已排序的 xsl:for-each 循环中访问前一个和下一个元素的值

python 循环遍历迭代的前一个,当前值和下一个值的Helper方法

将上一个和下一个按钮添加到 li item php 或 jquery

根据angular2中的第一个div和最后一个div显示或隐藏上一个和下一个按钮

检查值是不是是上一个、当前和下一个值的最大值/最小值

Laravel 计算上一个和下一个重复日期