【问题标题】:Strange behavior of list iteration using for loop in python [duplicate]在python中使用for循环的列表迭代的奇怪行为[重复]
【发布时间】:2017-08-24 08:56:39
【问题描述】:

我遇到了这样一种情况,即我的 python 代码在类似情况下表现不同。代码如下:

import time
def greedy_cow_transport(cows,limit=10):
    weights = []
    for weight in cows.values():
        weights.append(weight)
    weights.sort(reverse=True)
    cows_copy = cows.copy()
    all_trips = []
    while (len(weights) > 0):
        avail_weight = limit
        curr_trip = []
        for weight in weights:
            if weight <= avail_weight:
                for n, w in cows_copy.items():
                    if weight == w:
                        curr_trip.append(n)
                        weights.remove(weight)
                        cows_copy.pop(n, None)
                        avail_weight -= w
                        break
        all_trips.append(curr_trip)
    return all_trips
cows = {'Lola': 2, 'Oreo': 2, 'Millie': 2, 'Betsy': 2, 'Moo Moo': 2, 'Milkshake': 2, 'Herman': 2, 'Florence': 2, 'Maggie': 2, 'Henrietta': 2}
limit=100
print(cows)
print(greedy_cow_transport(cows))

而不是 greedy_cow_transport 返回 5 个成员的 2 个列表,它返回 3 个不同的 5 3 2 个成员列表。请解释为什么会这样?我知道,我可能遗漏了一些微妙的细节,但我需要帮助。无法弄清楚错误。 谢谢。

【问题讨论】:

  • 你的代码应该将一个字典分成几部分,每个部分都有一个总值
  • 吸引我们的总是微妙的细节不是吗?
  • 非常小的旁注:您对weights 的整个初始化可以简化为weights = sorted(cows.values(), reverse=True)。即使不需要排序,具有重复 appends 的未修改值的显式循环也将简化为单个 .extend 调用(或者如果列表最初为空,则直接使用 list(someiterable) 构造它)。跨度>

标签: python list python-3.x for-loop


【解决方案1】:

问题在于循环for weight in weights:

循环正在遍历列表中的项目,但 Python 循环使用项目的位置来执行此操作。当您删除带有weights.remove(weight) 的项目时,列表会缩小,而位置会像往常一样增加 1。基本上,因为您在迭代列表时要从权重中删除项目,所以它会遍历所有其他项目 - 因此随着列表的缩小而不同的列表长度(您可以通过将所有权重设置为 1 来验证这一点;您会得到相同的结果与 2)

一个例子:

list = [1, 2, 3, 4, 5]
for item in list:
    print(item)
    list.remove(item)
    print(list)

# --> 1
# --> [2, 3, 4, 5]
# --> 3
# --> [2, 4, 5]
# --> 5
# --> [2, 4]

^^ 请注意它是如何只迭代所有其他项目的。正是您的weights 发生了什么。

一个简单的解决方法是让您的 for 循环遍历副本,同时从原始副本中删除:

list = [1, 2, 3, 4, 5]
for item in list.copy():
    print(item)
    list.remove(item)
    print(list)

# --> 1
# --> [2, 3, 4, 5]
# --> 2
# --> [3, 4, 5]
# --> 3
# --> [4, 5]
# --> 4
# --> [5]
# --> 5
# --> []

哇!看那幅作品真漂亮。对于看起来像这样的奶牛:

def greedy_cow_transport(cows,limit=10):
    weights = []
    for weight in cows.values():
        weights.append(weight)
    weights.sort(reverse=True)
    cows_copy = cows.copy()
    all_trips = []
    while (len(weights) > 0):
        avail_weight = limit
        curr_trip = []
        for weight in weights.copy():
            if weight <= avail_weight:
                for n, w in cows_copy.items(): # <--!!! THE CHANGE IS HERE
                    if weight == w:
                        curr_trip.append(n)
                        weights.remove(weight)
                        cows_copy.pop(n, None)
                        avail_weight -= w
                        break
        all_trips.append(curr_trip)
    return all_trips
cows = {'Lola': 2, 'Oreo': 2, 'Millie': 2, 'Betsy': 2, 'Moo Moo': 2, 'Milkshake': 2, 'Herman': 2, 'Florence': 2, 'Maggie': 2, 'Henrietta': 2}
print(greedy_cow_transport(cows))

# --> [['Oreo', 'Milkshake', 'Herman', 'Florence', 'Lola'], ['Maggie', 'Millie', 'Henrietta', 'Betsy', 'Moo Moo']]

瞧!希望你喜欢。

@ShadowRanger 补充说,在旧版本的 Python 中,list 没有复制方法,因此 list.copy() 的替代方法是使用像 list[:] 这样的空切片。

【讨论】:

  • 次要注意:如果您使用的是较旧的 Python(3.3 之前的版本),list 缺少 copy 方法(后来添加它是为了与 setdict 等内容保持一致)。如果您的代码可以在早期版本的 Python 上运行,for weight in weights[:]: 可以在任何版本上正常运行; “空”切片是用于浅拷贝任意序列的 Python 习语。
  • 除了创建列表的副本之外,您还可以以相反的顺序循环位置索引,即从后面。
  • 出色的添加ShadowRanger。我不知道正确的协议,我应该将您的补充添加到我的答案中还是留在您的评论中?
  • 您可以编辑并在答案底部留下注释,说明这个想法来自@ShadowRanger。大多数人并不真正阅读 cmets。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-01-25
  • 2013-11-17
  • 1970-01-01
  • 2016-02-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多