简体   繁体   中英

Why can't I delete the needed element from the list

I am working on a piece of code, and have to do some sorting stuff with the given list.

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)

This code does is concatenating all possible sales (the 2 nested for loops), and removing the variants whose difference is a negative value (the final for loop).

When I check the output, there I find a very unpleasant thing: the tuple (11, 3) is in there, which must not be there following my logic

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

What is the matter with this value, have I done something wrong?

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 callingreversed 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 .

Do not remove from the list while you are iterating over it, unless you know what you are doing. Use a list comprehension instead:

all_posible_sales = [s for s in all_posible_sales if s[0] < s[1]]
print(all_posible_sales)
# [(5, 11), (5, 50), (5, 60), (5, 90), (11, 50), (11, 60), (11, 90), (3, 50), (3, 60), (3, 90), (50, 60), (50, 90), (60, 90)]

Instead of adding elements to list and then removing, you can just add only valid ones into the list by doing this:

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)]:
        if sell - buy > 0:
            a = tuple((buy, sell))
            all_posible_sales.append(a)

Also read this one to see how to remove items from list successfully for future application.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM