I wrote some Python code using the enumerate function.
A = [2,3,5,7]
for i, x in enumerate(A):
# calculate product with each element to the right
for j, y in enumerate(A, start=i+1):
print(x*y)
I expected it to calculate 6 products: 2*3, 2*5, 2*7, 3*5, 3*7, 5*7
Instead, it calculated all possible 16 products. What's going on?
The start parameter of enumerate solely influences the first value of the yielded tuple (i.e. i and j), it does not influence at which index the enumeration starts. As the manual puts it, enumerate is equivalent to this:
def enumerate(sequence, start=0):
n = start
for elem in sequence:
yield n, elem
n += 1
What you want is this:
for i, x in enumerate(A):
for y in A[i + 1:]:
print(x * y)
The question here is firstly what enumerate did, and secondly why you're using it. The base function of enumerate is to convert an iterable of the form (a,b,c) to an iterable of the form ((start,a), (start+1,b), (start+2,c)). It adds a new column which is typically used as an index; in your code, this is i and j. It doesn't change the entries contained in the sequence.
I believe the operation you were intending is a slice, extracting only part of the list:
for i, x in enumerate(A):
for y in A[i+1:]:
print(x*y)
If it is important not to copy the list (it rarely is), you can replace A[i+1:] with itertools.islice(A, i+1, len(A)).
A side note is that the start argument may be useful in the outer loop in this code. We're only using i+1, not i so we may as well use that value as our index:
for nextindex, x in enumerate(A, 1):
for y in A[nextindex:]:
print(x*y)
Related
I have an array that I am looping over and I want to compare each element to the element next to it, and if it is larger say, then I want to do something with its index. It's clear to me that enumeration would help in this case; however I am running into an 'index out of range error':
array = [1,2,3,4]
for index,i in enumerate(array):
if array[index]>array[index+1]:
...
While I know there are other ways of doing this,
is there a way I can make the above work with enumerate? I tried to do enumerate(array)-1 ; knowing this would not work. But anything of this sort that would fix the indexing error? Thanks
I know we can easily do the above with simply using 'i' from the for loop, but just curious if I can manipulate enumeration here.
You can just shorten the range:
for i, val in enumerate(array[:-1]):
if val > array[i+1]:
# do stuff
If you don't need the index, you can use zip to the same effect:
for prev, crnt in zip(array, array[1:]):
if prev > crnt:
# do stuff not requiring index
The slicing requires O(n) extra space, if you don't want that, you can use your original approach without enumerate, but a simple range:
for i in range(len(array)-1):
if array[i] > array[i+1]:
# ...
Use zip and slice:
for i, j in zip(array, array[1:]):
print(f'i: {i} - j: {j}')
Output:
i: 1 - j: 2
i: 2 - j: 3
i: 3 - j: 4
Doing this way will set index at 0 and i at 1. A list is starting at 0 in Python. So at i=3, you are looking at array[i+1]=array[4] which does not exist ! That is why the program is saying 'index out of range error'.
Here is what I suggest if you want to stick with lists:
array = [1,2,3,4]
for i in range(len(array)-1):
if array[i]>array[i+1]:
...
If you want to manipulate the index, then it will be the current index in your loop (i). Maybe I have not understood your issue correctly but I suggest you to use numpy if you want to work with array-like objects.
Charles
What logic do you need to apply for the last element in the list?
You can use the range function instead of enumerate.
If you don't need to implement business logic to last element, then use below:
array = [1,2,3,4]
l = len(array)
for i in range(l-1):
if array[i]>array[i+1]:
...
If you do need to implement business logic to last element then use below:
array = [1,2,3,4]
l = len(array)
for i in range(l):
if i==l-1:
implemet last elemt logic
else:
if array[i]>array[i+1]:
....
I have read in several places that it is bad practice to modify an array/list during iteration. However many common algorithms appear to do this. For example Bubble Sort, Insertion Sort, and the example below for finding the minimum number of swaps needed to sort a list.
Is swapping list items during iteration an exception to the rule? If so why?
Is there a difference between what happens with enumerate and a simple for i in range(len(arr)) loop in this regard?
def minimumSwaps(arr):
ref_arr = sorted(arr)
index_dict = {v: i for i,v in enumerate(arr)}
swaps = 0
for i,v in enumerate(arr):
print("i:", i, "v:", v)
print("arr: ", arr)
correct_value = ref_arr[i]
if v != correct_value:
to_swap_ix = index_dict[correct_value]
print("swapping", arr[to_swap_ix], "with", arr[i])
# Why can you modify list during iteration?
arr[to_swap_ix],arr[i] = arr[i], arr[to_swap_ix]
index_dict[v] = to_swap_ix
index_dict[correct_value] = i
swaps += 1
return swaps
arr = list(map(int, "1 3 5 2 4 6 7".split(" ")))
assert minimumSwaps(arr) == 3
An array should not be modified while iterating through it, because iterators cannot handle the changes. But there are other ways to go through an array, without using iterators.
This is using iterators:
for index, item in enumerate(array):
# don't modify array here
This is without iterators:
for index in range(len(array)):
item = array[index]
# feel free to modify array, but make sure index and len(array) are still OK
If the length & index need to be modified when modifying an array, do it even more "manually":
index = 0
while index < len(array):
item = array[index]
# feel free to modify array and modify index if needed
index += 1
Modifying items in a list could sometimes produce unexpected result but it's perfectly fine to do if you are aware of the effects. It's not unpredictable.
You need to understand it's not a copy of the original list you ar iterating through. The next item is always the item on the next index in the list. So if you alter the item in an index before iterator reaches it the iterator will yield the new value.
That means if you for example intend to move all items one index up by setting item at index+1 to current value yielded from enumerate(). Then you will end up with a list completely filled with the item originally on index 0.
a = ['a','b','c','d']
for i, v in enumerate(a):
next_i = (i + 1) % len(a)
a[next_i] = v
print(a) # prints ['a', 'a', 'a', 'a']
And if you appending and inserting items to the list while iterating you may never reach the end.
In your example, and as you pointed out in a lot of algorithms for e.g. combinatoric and sorting, it's a part of the algorithm to change the forthcoming items.
An iterator over a range as in for i in range(len(arr)) won't adapt to changes in the original list because the range is created before starting and is immutable. So if the list has length 4 in the beginning, the loop will try iterate exactly 4 times regardless of changes of the lists length.
# This is probably a bad idea
for i in range(len(arr)):
item = arr[i]
if item == 0:
arr.pop()
# This will work (don't ask for a use case)
for i, item in enumerate(arr):
if item == 0:
arr.pop()
I was solving this leetcode problem - https://leetcode.com/problems/how-many-numbers-are-smaller-than-the-current-number/
I solved it easily by using nested for loops but list comprehensions have always intrigued me. Ive spent a lot of time to make that one liner work but I always get some syntax error.
here's the solution:
count = 0
ans = []
for i in nums:
for j in nums:
if i > j:
count = count + 1
ans.append(count)
count = 0
return ans
these were the ones so far I think shouldve worked:
return [count = count + 1 for i in nums for j in nums if i > j]
return [count for i in nums for j in nums if i > j count = count + 1]
return [count:= count + 1 for i in nums for j in nums if i > j]
Ill also be happy if there's some resource or similar to put it together, Ive been searching the python docs but didnt find something that'll help me
I will transform the code step by step in order to show the thought process.
First: we don't care what the value of count is afterward, but we need it to be 0 at the start of each inner loop. So it is simpler logically to set it there, rather than outside and then also at the end of the inner loop:
ans = []
for i in nums:
count = 0
for j in nums:
if i > j:
count = count + 1
ans.append(count)
return ans
Next, we focus on the contents of the loop:
count = 0
for j in nums:
if i > j:
count = count + 1
ans.append(count)
A list comprehension is not good at math; it is good at producing a sequence of values from a source sequence. The transformation we need to do here is to put the actual elements into our "counter" variable1, and then figure out how many there are (in order to append to ans). Thus:
smaller = []
for j in nums:
if i > j:
smaller.append(j)
ans.append(len(smaller))
Now that the creation of smaller has the right form, we can replace it with a list comprehension, in a mechanical, rule-based way. It becomes:
smaller = [j for j in nums if i > j]
# ^ ^^^^^^^^^^^^^ ^^^^^^^^
# | \- the rest of the parts are in the same order
# \- this moves from last to first
# and then we use it the same as before
ans.append(len(smaller))
We notice that we can just fold that into one line; and because we are passing a single comprehension argument to len we can drop the brackets2:
ans.append(len(j for j in nums if i > j))
Good. Now, let's put that back in the original context:
ans = []
for i in nums:
ans.append(len(j for j in nums if i > j))
return ans
We notice that the same technique applies: we have the desired form already. So we repeat the procedure:
ans = [len(j for j in nums if i > j) for i in nums]
return ans
And of course:
return [len(j for j in nums if i > j) for i in nums]
Another popular trick is to put a 1 in the output for each original element, and then sum them. It's about the same either way; last I checked the performance is about the same and I don't think either is clearer than the other.
Technically, this produces a generator expression instead. Normally, these would be surrounded with () instead of [], but a special syntax rule lets you drop the extra pair of () when calling a function with a single argument that is a generator expression. This is especially convenient for the built-in functions len and sum - as well as for any, all, max, min and (if you don't need a custom sort order) sorted.
Hmm, three people write sum solutions but every single one does sum(1 for ...). I prefer this:
[sum(j < i for j in nums) for i in nums]
Instead of trying to advance an external counter, try adding ones to your list and then sum it:
for example:
nums = [1,2,3,4,5]
target = 3
print(sum(1 for n in nums if n < target))
Using counter inside the list comprehension creates the challenge of resetting it's value, each iteration of the first loop.
This can be avoided by filtering, and summing, in the second loop:
You use the first loop to iterate over the values of nums array.
return [SECOND_LOOP for i in nums]
You use the second loop, iterating over all elements of nums array. You filter in the elements that are smaller than i, the current element in the first loop, with if i < j, and evaluating 1 for each of them. Finally, you sum all the 1s generated:
sum(1 for j in nums if i > j)
You get the number of values that meet the requirements, by the list comprehension of the first loop:
return [sum(1 for j in nums if i > j) for i in nums]
This solution has been checked & validated in LeetCode.
You need a slightly different approach for the inner loop than a list comprehension. Instead of repeatedly appending a value to a list you need to repeatedly add a value to a variable.
This can be done in a functional way by using sum and a generator expression:
count = 0
# ...
for j in nums:
if i > j:
count = count + 1
can be replaced by
count = sum(1 for j in nums if i > j)
So that we now have this:
ans = []
for i in nums:
count = sum(1 for j in nums if i > j)
ans.append(count)
return ans
This pattern can in fact be replaced by a list comprehension:
return [sum(1 for j in nums if i > j) for i in nums]
Alternative Solution
We can also use the Counter from collections:
class Solution:
def smallerNumbersThanCurrent(self, nums):
count_map = collections.Counter(nums)
smallers = []
for index in range(len(nums)):
count = 0
for key, value in count_map.items():
if key < nums[index]:
count += value
smallers.append(count)
return smallers
I'm not sure about the space complexity of these two selection sort implementations:
def selection_sort(lst):
n = len(lst)
for i in range(n):
m_index = i
for j in range(i+1,n):
if lst[m_index] > lst[j]:
m_index = j
swap(lst, i, m_index)
return None
and this one:
def selection_sort2(lst):
n = len(lst)
for i in range(n):
m = min(lst[i:n])
m_index = lst.index(m) #find the index of the minimum
lst[i], lst[m_index] = lst[m_index], lst[i]
return None
and, regarding the second code, where are the previous slices being saved, once m gets a new slice?
Thanks!
The first point to make is that your second function contains a bug in its use of index. Running this:
def selection_sort2(lst):
n = len(lst)
for i in range(n):
m = min(lst[i:n])
m_index = lst.index(m) #find the index of the minimum
lst[i], lst[m_index] = lst[m_index], lst[i]
return
l = [5,4,1,3,4]
selection_sort2(l)
print(l)
prints out
[1, 3, 5, 4, 4]
This is because you have misunderstood the index function. What it does is to find the first occurrence of the supplied value (here m) in the supplied list (here lst). So what your code is doing is first of all to create a slice and find its min. Then the slice goes out of scope and is garbage collected. Then you find the value in the whole list (in the wrong place in this example).
We can fix this by restricting the index to the slice, though bear in mind that this is not good code, as I will explain next.
m_index = lst.index(m,i) #find the index of the minimum
With this change, the function works, but it has two problems. The first is that the slicing does (as you suspected) create a copy and so doubles the memory requirement of the code. But the second problem is that once you find the minimum value, you then pointlessly iterate through the slice a second time to find the index of the place where you found the minimum, so also doubling the run time.
The copying can be fixed by replacing the slice with a generator expression. So instead of a slice we just produce the values one at a time.
Then we can arrange to find the index of the minimum by carrying it along with the value in a tuple. Then minimising the tuples provides us with the index at the same time. The resulting code looks like this:
def selection_sort2(lst):
n = len(lst)
for i in range(n):
m,m_index = min((lst[j],j) for j in range(i,n))
lst[i], lst[m_index] = lst[m_index], lst[i]
return
However, this code is functionally more or less the same as your first example and probably not any clearer - so why change?
I'm in trouble creating a combination of elements from list.
What i would like to do is to create a recursive function in Python which returns a combination of elements for example list a = [1,2,3,4,5,6,7,8] and a result will be combinations [1,2,3,4],[1,3,4,5],[1,4,5,6],[1,2,4,5] etc. For 8 elements it should return 70 combinations (if i did my math right). Although the best option would be that the combinations don't repeat.
I tried to code it, but what i get is only [1,2,3,4],[1,3,4,5] etc but not combination [1,5,7,8]
I know there is a special function but i'd like to do it recursively. Any suggestions?
nimed = ["A","B","C","D","E","F","G","H"]
def kombinatsioonid(listike,popitav):
if len(listike) < 4:
return
tyhi = []
for c in range(len(listike)):
tyhi.append(listike[c])
listike.pop(popitav)
print(tyhi)
kombinatsioonid(listike,popitav)
kombinatsioonid(nimed,1)
This can be done in this way :
def combination(l,n, mylist=[]):
if not n: print(mylist)
for i in range(len(l)):
mylist.append(l[i])
combination(l[i+1:], n-1, mylist)
mylist.pop()
l = ["A","B","C","D","E","F","G","H"]
n=4
combination(l, n)
For each element x in a, generate all k-1 combinations from the elements right to it, and prepend x to each one. If k==0, simply return one empty combination, thus exiting the recursion:
def combs(a, k):
if k == 0:
return [[]]
r = []
for i, x in enumerate(a):
for c in combs(a[i+1:], k - 1):
r.append([x] + c)
#print '\t' * k, k, 'of', a, '=', r
return r
Uncomment the "print" line to see what's going on.
As a side note, it's better to use English variable and function names, just for the sake of interoperability (your very question being an example).