Enumerate does not work with 2d arrays yet range(len()) does? - python

I heard somewhere that we should all use enumerate to iterate through arrays but
for i in enumerate(array):
for j in enumerate(array[i]):
print(board[i][j])
doesn't work, yet when using range(len())
for i in range(len(array)):
for j in range(len(array[i)):
print(board[i][j])
it works as intended

use it like this:
for idxI, arrayI in enumerate(array):
for idxJ, arrayJ in enumerate(arrayI):
print(board[idxI][idxJ])

Like I wrote enumerate adds an extra counter to each element. Effectively turning you list of elements into a list of tuples.
Example
array = ['a', 'b','c','d']
print(list(enumerate(array)))
gives you this:
[(0, 'a'), (1, 'b'), (2, 'c'), (3, 'd')]
So in your case what you want to do it simply add the extra element when iterating over it
for i, item1 in enumerate(array):
for j,item2 in enumerate(array[i]):
print(board[i][j])
Issue was in your case is
for i in enumerate(array):
this i is not an integer but a tuple ('1','a') in my case. And you cant access a list element with an index value of a tuple.

When one uses for i in enumerate(array): it returns a collection of tuples. When working with enumerate, the (index, obj) is returned while range based loops just go through the range specified.
>>> arr = [1,2,3]
>>> enumerate(arr)
<enumerate object at 0x105413140>
>>> list(enumerate(arr))
[(0, 1), (1, 2), (2, 3)]
>>> for i in list(enumerate(arr)):
... print(i)
...
(0, 1)
(1, 2)
(2, 3)
>>>
One has to access the first element of the tuple to get the index in order to further index.
>>> board = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
>>> for idx1,lst in enumerate(board):
... for idx2,lst_ele in enumerate(lst): # could use enumerate(board[i])
... print(lst_ele,end=" ")
...
1 2 3 4 5 6 7 8 9
>>>
Sometimes you do not need both the index and the element so I do not think its always better to use enumerate. That being said, there are plenty of situations where its easier to use enumerate so you can grab the element faster without having to write element = array[idx].
See range() vs enumerate()
"Both are valid. The first solution [range-based] looks more similar to the problem description, while the second solution [enum-based] has a slight optimization where you don’t mutate the list potentially three times per iteration." - James Uejio

Related

Does enumerate over slice perform sublist materialization?

Does this:
for i,v in enumerate(lst[from:to]):
or this:
for i,v in enumerate(itertools.islice(lst,from,to)):
...make a copy of iterated sublist?
Assuming that lst is a regular Python list, and not a Numpy array, Pandas dataframe, or some custom class supporting slice indexing, then the slice [...:...] will create a new list, whereas itertools.islice does not.
As suggested in comments, you can see this for yourself by creating both enumerate objects and modifying the original list before consuming them:
>>> lst = [1, 2, 3, 4, 5]
>>> e1 = enumerate(lst[1:4])
>>> e2 = enumerate(itertools.islice(lst, 1, 4))
>>> del lst[2] # remove second element
>>> list(e1)
[(0, 2), (1, 3), (2, 4)] # shows content of original list
>>> list(e2)
[(0, 2), (1, 4), (2, 5)] # second element skipped
Also note that this does in fact have nothing to do with enumerate, which will create a generator in both cases (on top of whatever iterable was created before by the slice).
You could also just create the two variants of slices and check their types:
>>> type(lst[1:4])
list # a new list
>>> type(itertools.islice(lst, 1, 4))
itertools.islice # some sort of generator

Converting Dictionary to Dataframe with tuple as key

I have a dictionary like this
df_dict = {(7, 'hello'): {1}, (1, 'fox'): {2}}
I want to transform it into a dataframe where the first part of the tuple is the row header, and the second part of the tuple is the column header. I tried this:
doc_df = pd.DataFrame(df_dict, index=[df_dict.keys()[0]], columns = [df_dict.keys()[1]])
But I got the error TypeError: 'dict_keys' object does not support indexing
I want my dataframe to look like:
_ | fox | hello
1 | 2 | null
7 | null | 1
How do I index into the keys?
The reason you're getting the TypeError is that df_dict.keys() is an iterator which yields keys from the dict one by one. The elements it yields will be (7, 'hello') and (1, 'fox'), but it doesn't "know" that in advance. The iterator itself doesn't have any idea how many elements it has or what sort of structure those elements might have, and in particular, it doesn't have any way to access an element by index number.
Now, you can use the itertools.islice function to access a given-numbered element from an iterable, but it involves throwing away everything that comes beforehand. So that's not what you want.
The answer to the question you're asking, which is how you index into the keys, is to convert them into a list first:
l = list(df_dict.keys())
and then you can use l[0] and l[1] and so on.
But even that isn't what you're actually going to need for your application. The resulting list, in your example, would be
[(7, 'hello'), (1, 'fox')]
so l[0] will be (7, 'hello') and l[1] will be (1, 'fox') (or vice-versa, since you don't know which order the keys will come out in). What you actually want to access is (7, 1) and ('hello', 'fox'), for which you either need to use something like a list comprehension:
[x[0] for x in l] # (7, 1)
[x[1] for x in l] # ('hello', 'fox')
or you could convert it to a NumPy array and transpose that.
npl = numpy.array(l) # array([[7, 'hello'], [1, 'fox']])
nplT = npl.T # array([[7, 1], ['hello', 'fox']])
Now you can use nplT[0] and so on.

Compare i vs other items in a Python list

Good Day, I've googled this question and have found similar answers but not what I am looking for. I am not sure what the problem is called so I that doesn't help me and I am looking for an elegant solution.
How do I loop over a list, item at a time, and compare it to all other items in a list. For example, if I had a list
l = [1,2,3,4]
Each loop of the out would yield something like
1 vs [2,3,4]
2 vs [1,3,4]
3 vs [1,2,4]
4 vs [1,2,3]
One solution I've been playing with involves duplicating the list every iteration, finding the index of the item, deleting it from the duplicate list and compare the two. This route seems less ideal as you have to create a new list on every iteration.
You can use itertools.combiations to create all combinations of the length 3 from your list and then use set.defference method to get the difference element between the l and the combinations. but note that you need to convert your main list to a set object :
>>> from itertools import combinations
>>> l = {1,2,3,4}
>>> [(l.difference(i).pop(),i) for i in combinations(l,3)]
[(4, (1, 2, 3)), (3, (1, 2, 4)), (2, (1, 3, 4)), (1, (2, 3, 4))]
A simple approach would be to use two loops:
arr = [1,2,3,4]
for i in arr:
comp = []
for j in arr:
if i != j:
comp.append(j)
print(comp)
I guess you could use list comprehension. While still creating a new list every iteration, you don't need to delete an item each time:
l = [1,2,3,4]
for i in l:
temp = [item for item in l if item != i]
print temp
[2, 3, 4]
[1, 3, 4]
[1, 2, 4]
[1, 2, 3]

Python List indexing multiple ranges

Sorry if this has already been asked, I couldn't find it anywhere. Basically how do I get 2 separate ranges within a list in Python.
If I want the 1st, 2nd, 5th and 6th elements of a list I know I can do this,
l = range(0,15)
l[1:3]+l[5:7]
but this assumes that l is easy to write. However I am scrapping something from a webpage using BeautifulSoup4, so I'm using soup.find_all (which gives me a list), so I can't simply write out 2 lists, l and concatenate them.
I want an answer that is something like
l = range(0,15)
l[1:3,5:7]
(but of course without the error) :)
This might be what you want. itemgetter creates a function that retrieves the listed indices:
>>> import operator
>>> snip = operator.itemgetter(1,2,5,6)
>>> snip(range(15))
(1, 2, 5, 6)
>>> snip('abcdefg')
('b', 'c', 'f', 'g')
>>> snip([1,2,3,4,5,6,7,8])
(2, 3, 6, 7)
I would do this with a function:
def multi_range(l, *args):
output = []
for indices in args:
output += l[indices[0]:indices[1]]
return output
So the first argument would be the list, and the rest of the parameters are tuples with the indices you're looking to pull. It would work fine with a long list name:
long_list_name = range(0, 15)
print multi_range(long_list_name, (1, 3), (5, 7))
>>> [1, 2, 5, 6]
l = range(0, 15)
print([l[i] for i in [1,2, 5,6]])
Not sure why you think l[1:3]+l[5:7] is hard, find_all returns a normal python list like any other.
Or using map:
l = range(0, 15)
print(list(map(l.__getitem__,(1,2,5,6))))
Is this OK?
indices = [1, 2, 5, 6]
selected = [l[i] for i in indices]

How does this code snippet rotating a matrix work?

While looking for a pythonic way to rotate a matrix, I came across this answer. However there is no explanation attached to it. I copied the snippet here:
rotated = zip(*original[::-1])
How does it work?
>>> lis = [[1,2,3], [4,5,6], [7,8,9]]
[::-1] reverses the list :
>>> rev = lis[::-1]
>>> rev
[[7, 8, 9], [4, 5, 6], [1, 2, 3]]
now we use zip on all items of the rev, and append each returned tuple to rotated:
>>> rotated = []
>>> for item in zip(rev[0],rev[1],rev[2]):
... rotated.append(item)
...
>>> rotated
[(7, 4, 1), (8, 5, 2), (9, 6, 3)]
zip picks items from the same index from each of the iterable passed to it(it runs only up to the item with minimum length) and returns them as a tuple.
what is *:
* is used for unpacking all the items of rev to zip, so instead of manually typing
rev[0], rev[1], rev[2], we can simply do zip(*rev).
The above zip loop could also be written as:
>>> rev = [[7, 8, 9], [4, 5, 6], [1, 2, 3]]
>>> min_length = min(len(x) for x in rev) # find the min length among all items
>>> rotated = []
for i in xrange(min_length):
items = tuple(x[i] for x in rev) # collect items on the same index from each
# list inside `rev`
rotated.append(items)
...
>>> rotated
[(7, 4, 1), (8, 5, 2), (9, 6, 3)]
Complementary to the explanations by Ashwini and HennyH, here's a little figure to illustrate the process.
First, the [::-1] slice operator reverses the list of list, taking the entire list (thus the first two arguments can be omitted) and using a step of -1.
Second, the zip function takes a number of lists and effectively returns a new list with rows and columns reversed. The * says that the list of lists is unpacked into several lists.
As can be seen, these two operations combined will rotate the matrix.
For my explination:
>>> m = [['a','b','c'],[1,2,3]]
Which when pretty-printed would be:
>>> pprint(m)
['a', 'b', 'c']
[1, 2, 3]
Firstly, zip(*m) will create a list of all the columns in m. As demonstrated by:
>>> zip(*m)
[('a', 1), ('b', 2), ('c', 3)]
The way this works is zip takes n sequences and get's the i-th element of each one and adds it to a tuple. So translated to our matrix m where each row is represented by a list contained within m, we essentially pass in each row to zip, which then gets the 1st element from each row puts all of them into a tuple, then gets every 2nd element from each row etc... Ultimately getting every column in m i.e:
>>> zip(['row1column1','row1column2'],['row2column1','row2column2'])
[('row1column1', 'row2column1'), ('row1column2', 'row2column2')]
Notice that each tuple contains all the elements in a specific column
Now that would look like:
>>> pprint(zip(*m))
('a', 1)
('b', 2)
('c', 3)
So effectively, each column in m is now a row. Hoever it isn't in the correct order (try imagine in your head rotating m to get the matrix above, it can't be done). This why it's necessary to 'flip' the original matrix:
>>> pprint(zip(*m[::-1]))
(1, 'a')
(2, 'b')
(3, 'c')
Which results in a matrix which is the equivalent of m rotated - 90 degrees.

Categories