【问题标题】:How do I mutate every third element in a list? [duplicate]如何改变列表中的每三个元素? [复制]
【发布时间】:2020-03-25 14:00:44
【问题描述】:

如何检查列表中的每三个元素?

即。鉴于下面的列表,我想改变每三个项目并保持列表的其余部分不变。

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

基本上,如果列表中的第三个元素(在本例中为 3,6,9)是奇数,则减一使其成为偶数。如果是偶数,让它保持不变。

我知道,如果我执行L[2::3],我会得到每第三个元素的新列表,但我只想循环遍历每第三个元素并检查。

for 循环会是什么样子?我需要一些帮助。

感谢并感谢所有帮助!

【问题讨论】:

  • 您可能知道这一点,但为了安全起见:首先制作列表的副本并对其进行迭代,然后对另一个列表进行变异。正如雷蒙德·海廷格 (Raymond Hettinger) 曾经说过的那样:“如果你在迭代某个东西时对其进行了变异,那么你就生活在一种罪恶的状态中,你应该得到任何发生在你身上的事情。”

标签: python python-3.x list mutation


【解决方案1】:

不要想太多。 这是一个例子,列表推导、函数等等都是多余的。您需要遍历一个列表,并就地修改它,对吧?所以你没有修改你正在迭代的结构。迭代目标其实就是列表的索引

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

print(xs)

for idx in range(2, len(xs), 3):
    if xs[idx] % 2 == 1:
        xs[idx] -= 1

print(xs)

这相当于许多低级语言中的for(i = 2; i < len(xs); i++) 循环。


与另一个答案中提出的列表理解解决方案相比,for 循环略快:

from timeit import timeit


def in_place(xs):
    for idx in range(2, len(xs), 3):
        if xs[idx] % 2 == 1:
            xs[idx] -= 1
    return xs


def list_comp(xs):
    xs[2::3] = [i - 1 if (i % 3 == 0 and i % 2 != 0) else i for i in xs[2::3]]
    return xs


# This answer is an improvement, as it eliminates one modulus
def list_comp2(xs):
    xs[2::3] = [x - 1 if x % 2 else x for x in xs[2::3]]
    return xs


context = {"globals": globals(), "setup": "xs = [1, 2, 3, 4, 5, 6, 7, 8, 9]"}
print(f"For loop: {timeit('in_place(xs)', **context)}")
print(f"List comprehension:  {timeit('list_comp(xs)', **context)}")
print(f"List comprehension 2:  {timeit('list_comp2(xs)', **context)}")

在我的机器上,这会产生:

For loop: 0.608657514
List comprehension:  0.7510721879999999
List comprehension 2:  0.641639047

【讨论】:

  • 你可以直接用:if xs[idx] % 2:,我不同意“……等等都是矫枉过正”
  • 虽然这个答案可能会产生正确的输出,但它会比具有列表理解的方法慢得多,甚至更不清晰。因此,我认为您应该考虑接受@Austin 或我的回答,因为它们基本上是一个班轮,而且总体上要快得多。
  • 我的直觉是在这种情况下列表解析会变慢,所以我写了一个基准。我已将其添加到帖子中。我也会坚持我的观点,与 Python 极其不寻常的列表切片赋值相比,for 循环是一种几乎通用的构造,它更易于阅读,我在编写 Python 的多年中从未在实际代码中使用过这种赋值。打高尔夫球?是的。真实代码?不。
  • (也从@Austin 的答案中添加另一个列表理解;由于只使用一个模运算符,它的性能更好。)
  • timeitstandard library 的一部分,多次运行其sn-ps。在这种情况下,一百万(默认值)。无论如何,您是对的,差异并不是特别显着。 可读性的差异更重要。
【解决方案2】:

列表理解是最好的选择:

L = [1,2,3,4,5,6,7,8,9]
L[2::3] = [i-1 if (i%3==0 and i%2!= 0) else i for i in L[2::3]
print(L)

输出:

[1, 2, 2, 4, 5, 6, 7, 8, 8]

说明:

基本上你查看 L 中的每个 third 元素,看看它是否能被 3 整除(余数为 0)且不能被 3 整除(mod2 不是 0),如果是,则减去 1,否则保持原样。替换原始列表中的结果。

【讨论】:

    【解决方案3】:

    使用列表推导定义要应用于迭代元素的函数

    def do(number):
        if number%2 == 1:
            return number -1
        else:
            return number
    
    [do(y) if i%3 == 0 else y for i,y in enumerate(L)]
    

    不明确使用enumerate 的不同解决方案可能是:

    for i in range(len(L)):
        if i%3 == 0:
            L[i] = do(L[i])
    

    您可能还想查看this 了解内嵌操作

    【讨论】:

    • 谢谢!你知道是否有办法在不使用枚举的情况下进行循环?
    • 您需要迭代计数才能跟踪增量。
    • 我明白了,非常感谢!
    • 因为我刚醒来:)。改变了它。谢谢
    • 但这是一个要求吗? Basically, if the third element in the list (in this case 3,6,9) is odd, then subtract one to make it even. If it is even, let it remain the same.
    【解决方案4】:

    您可以分配给切片,然后在此示例中使用列表推导来进行突变。

    L = [1,2,3,4,5,6,7,8,9]
    L[2::3] = [ x if x % 2 == 0 else x-1 for x in L[2::3] ]
    print(L)
    

    尽管语法优美,但一个简单的 for 循环更适合你未来的自己和同事 =D

    【讨论】:

    • 一个小小的简化:L[2::3] = [x-x%2 for x in L[2::3]]
    【解决方案5】:

    您可以使用切片来根据条件修改选定的值来执行列表推导:

    L[2::3] = [x - 1 if x % 2 else x for x in L[2::3]]
    

    示例

    L = [1,2,3,4,5,6,7,8,9]
    
    L[2::3] = [x - 1 if x % 2 else x for x in L[2::3]]
    # [1, 2, 2, 4, 5, 6, 7, 8, 8]
    

    【讨论】:

    • 这似乎使问题过于复杂,因此条件更简单。请参阅我的答案以了解我的意思。
    • @lbragile,请参阅我对您的回答的评论。
    • 只是想知道为什么要专门分配回切片列表?这不能是任何变量吗?
    • OP 希望保留列表中的所有其他元素。我们获取切片,修改并将其放回切片中,因此所有其他元素都保持不变。
    猜你喜欢
    • 2018-08-15
    • 1970-01-01
    • 2020-05-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-07-22
    • 1970-01-01
    • 2019-10-15
    相关资源
    最近更新 更多