为什么我不能从列表中删除所需的元素

我正在编写一段代码,并且必须对给定的列表进行一些排序。

prices = [5, 11, 3, 50, 60, 90]
k = 2
all_posible_sales = []
i=0
for buy in prices[i:len(prices)]:
    for sell in prices[i:len(prices)]:
        a = tuple((buy, sell))
        all_posible_sales.append(a)
    i += 1

for data in all_posible_sales:
    if data[1] - data[0] < 0 or data[1] - data[0] == 0:
        all_posible_sales.remove(data)
print(all_posible_sales)

这段代码的作用是连接所有可能的销售(2 个嵌套for循环),并删除差异为负值的变体(最终for循环)。

当我检查输出时,我发现了一个非常不愉快的事情:元组(11, 3)在那里,按照我的逻辑肯定不存在

data[1] - data[0] < 0 | 3 - 11 < 0 (TRUE)

这个值有什么问题,我做错了什么吗?

回答

Introduction

Neither of the posted answers actually addresses the issues in your code. The problem is that you don't remove items from a list that you are iterating forward over. To begin to see why, print the tuples that are actually being removed:

(5, 5)
(5, 3)
(11, 11)
(3, 3)
(50, 50)
(60, 60)
(90, 90)

Notice that (11, 3) never gets checked. This happens because list iterators work based on index. Every time you remove an item, all following items shift back by one, but the iterator keeps incrementing regardless. Here is an example from the head of your starting all_possible_sales list:

  1. Start with the iterator at index 0:
    [(5, 5), (5, 11), (5, 3), ...
       ^
      i=0
    
  2. Discard the item, since 5 <= 5. Notice that the data shifts back, but the iterator stays at the same position in the list:
    [(5, 11), (5, 3), (5, 50), ...
       ^
      i=0
    
  3. Step for the next iteration of the loop:
    [(5, 11), (5, 3), (5, 50), ...
                ^
               i=1
    

Hopefully you can see how you end up skipping over (5, 11), and many elements after that (in fact just about every other element).

Solutions

Now let's look at some solutions. I start with some nearly cosmetic changes, and work up to completely overhauling your code, even more than the other answers recommend.

Backwards Iteration

When you iterate over a list backwards, removals do not affect the indices that you have not traversed. Lists have a reverse iterator, which means that calling reversed does not copy anything, and is therefore cheap in time and memory.

The simplest solution is therefore to replace the second loop with:

for data in reversed(all_posible_sales):

Copying the Input

If you make a copy of the input list, its elements will point to the same objects, but removing items from the original list will not affect the iterator over the copy. This is more expensive than reversing the original list because it actually allocates a second list.

This solution can be written in at least three different ways:

  1. for data in list(all_posible_sales):
  2. for data in all_posible_sales.copy():
  3. for data in all_posible_sales[:]:

Not Including Unnecessary Elements

As the other answers suggest, the best way is to exclude elements that don't belong, rather than removing them later. A partial answer to this approach is adjusting your loops so that you don't create tuple of the form (buy, buy):

for ibuy in range(len(prices) - 1):
    buy = prices[ibuy]
    for isell in range(ibuy + 1, len(prices)):
        sell = prices[isell]

Conditional in Loop

The simplest way to exclude items from the list is never to include them to begin with:

for ibuy in range(len(prices) - 1):
    buy = prices[ibuy]
    for isell in range(ibuy + 1, len(prices)):
        sell = prices[isell]
        if buy < sell:
            all_posible_sales.append((buy, sell))
print(all_posible_sales)

List Comprehensions

Any nested set of for loops that has nothing but a conditional and a list append can be written more efficiently, if not more legibly, as a list comprehension. In this particular case, it will involve a bit more indexing:

all_posible_sales = [(prices[ibuy], prices[isell]) for ibuy in range(len(prices) - 1) for isell in range(ibuy + 1, len(prices)) if prices[ibuy] < prices[isell]]

Notice that it's like writing the loops and conditional in the exact same order as before, but all on one line and without the colons. The only difference is that the contents of the append call goes at the beginning.

Numpy

Stuff like this with numbers is very well suited for numpy. If you are doing any sort of numerical processing, you will almost inevitably end up importing numpy, parts of scipy or pandas. Your code will be cleaner and faster.

If you turn prices into a numpy array, you can use the function np.triu_indices to find the same pairs as your loop goes over. You can then concatenate the selection into an Nx2 array of buy and sell prices:

import numpy as np

prices = np.array(prices)
ibuy, isell = np.triu_indices(len(prices), k=1)
all_possible_sales = np.stack((prices[ibuy], prices[isell]), axis=-1)[prices[ibuy] < prices[isell]]

Notes

  • You don't need to state len(prices) explicitly in your index: prices[i:] will take all the elements to the end, and prices[i:-1] will take all the elements until one from the end, etc.
  • The expression data[1] - data[0] < 0 or data[1] - data[0] == 0 can be written much more succinctly as data[1] - data[0] <= 0. Better yet (and I would argue, more straightforwardly) is the comparison data[1] <= data[0].
  • The expression tuple((buy, sell)) is exactly equivalent to just (buy, sell). I find the latter less confusing to read.
  • The expression for sell in prices[i:len(prices)]: makes a full copy of a segment of prices at each iteration of the outer loop. It is likely much cheaper to iterate over a range object, and index into prices.

以上是为什么我不能从列表中删除所需的元素的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>