Python snippet with a nested range() function - python

I'm trying to understand the following snippet of python code:
lst = [[c for c in range(r)] for r in range(3)] #line1
for x in lst: #line2
for y in x: #line3
if y < 2: #line4
print('*', end='') #line5
I know what the functions like range(3) mean by its own, but I don't get the context. It's a bit complicated to see this nested piece of code. The first line with the 'lst' is most confusing. Why is the first line producing the following output:
[[], [0], [0, 1]]
and how does line2 and line3 works together? Thanks in advance for your answer. Every idea is welcome!

Re
"The first line with the 'lst' is most confusing.":
Wherever you see [ ...for...] you have what's called a "list comprehension." This is a way to build up a list based on a one-line-loop description of the elements. For example:
list1 = [letter for letter in 'abcd']
and
list2 = []
for letter in 'abcd':
list2.append(letter)
yield identical lists list1 and list2
In your case, you have two sets of [] and two for statements, so you have a list comprehension inside a list comprehension: so the result is not just a list but a nested list.
Re
"and how does line2 and line3 works together?"
Line2 iterates through all the items in your list lst.
But each of those items is also a list, because you have a nested list. So line3 iterates through each item in that inner list.

In that nested list comprehension, r begins with a value of 0, so the inside list evaluates to [c for c in range(0)], which is []. When r is 1, it evaluates to [c for c in range(1)], which is [0]. When c is 2, you have [c for c in range(2)], which is [0,1]. When these are generated within the outer list comprehension, these are returned as list elements of the list. So you have a list of lists in lst.
The for loop then iterates over each of these lists in line 2. Line 3 then iterates over the integer elements within each list.

lst = [[c for c in range(r)] for r in range(3)]
this is a nested list comprehension.
But here it could be simplified as we don't need a list of lists, just a list of range objects so we can iterate on them. So
lst = [range(r) for r in range(3)]
is way simpler.
And while we're at it, why creating a list comprehension at all? Just remove it, and use a classic loop
for r in range(3):
for y in range(r):
if y < 2:
print('*', end='')

The snippet creates a LIST of LISTS where size (and item's value actually) depends on the index in the first level list.
The snippet is the same as
result = []
for k in range(3):
result.append([])
for v in range(k):
result[k].append(v)
print(result)
=>>>
[[], [0], [0, 1]]

The first line is equivalent to
lst = [list(range(r)) for r in range(3)]
It is a list comprehension which generates a list containing list up to r-1. list(range(r)) generates [0, 1, 2, ..., r-1], and because the r variable gets the values 0, 1 and 2 in the list comprehension, it means it will generate a list with the lists [] (r=0), [0], (r=1) and [0, 1] (r=2).
Line 2 iterates over the lists of lst, and line 3 iterates over the values of each list. Therefore, these two lines jointly iterate over all the numbers in lst.

Related

Save list number within a list only if it contains elements in python

I have list of lists such as :
my_list_of_list=[['A','B','C','E'],['A','B','C','E','F'],['D','G','A'],['X','Z'],['D','M'],['B','G'],['X','Z']]
as you can see, the list 1 and 2 share the most elements (4). So, I keep a list within my_list_of_list only if the 4 shared elements (A,B,C or E) are present within that list.
Here I then save within the list_shared_number[], only the lists 1,2,3 and 6 since the other does not contain (A,B,C or E).
Expected output:
print(list_shared_number)
[0,1,2,5]
Probably sub optimal because I need to iterate 3 times over lists but it's the expect result:
from itertools import combinations
from functools import reduce
common_elements = [set(i).intersection(j)
for i, j in combinations(my_list_of_list, r=2)]
common_element = reduce(lambda i, j: i if len(i) >= len(j) else j, common_elements)
list_shared_number = [idx for idx, l in enumerate(my_list_of_list)
if common_element.intersection(l)]
print(list_shared_number)
# Output
[0, 1, 2, 5]
Alternative with 2 iterations:
common_element = {}
for i, j in combinations(my_list_of_list, r=2):
c = set(i).intersection(j)
common_element = c if len(c) > len(common_element) else common_element
list_shared_number = [idx for idx, l in enumerate(my_list_of_list)
if common_element.intersection(l)]
print(list_shared_number)
# Output
[0, 1, 2, 5]
You can find shared elements by using list comprehension. Checking if index 0 and index 1:
share = [x for x in my_list_of_list[0] if x in my_list_of_list[1]]
print(share)
Assume j is each item so [j for j in x if j in share] can find shared inner elements. if the length of this array is more than 0 so it should include in the output.
So final code is like this:
share = [x for x in my_list_of_list[0] if x in my_list_of_list[1]]
my_list = [i for i, x in enumerate(my_list_of_list) if len([j for j in x if j in share]) > 0]
print(my_list)
You can use itertools.combinations and set operations.
In the first line, you find the intersection that is the longest among pairs of lists. In the second line, you iterate over my_list_of_list to identify the lists that contain elements from the set you found in the first line.
from itertools import combinations
comparison = max(map(lambda x: (len(set(x[0]).intersection(x[1])), set(x[0]).intersection(x[1])), combinations(my_list_of_list, 2)))[1]
out = [i for i, lst in enumerate(my_list_of_list) if comparison - set(lst) != comparison]
Output:
[0, 1, 2, 5]
Oh boy, so mine is a bit messy, however I did not use any imports AND I included the initial "finding" of the two lists which have the most in common with one another. This can easily be optimised but it does do exactly what you wanted.
my_list_of_list=[['A','B','C','E'],['A','B','C','E','F'],['D','G','A'],['X','Z'],['D','M'],['B','G'],['X','Z']]
my_list_of_list = list(map(set,my_list_of_list))
mostIntersects = [0, (None,)]
for i, IndSet in enumerate(my_list_of_list):
for j in range(i+1,len(my_list_of_list)):
intersects = len(IndSet.intersection(my_list_of_list[j]))
if intersects > mostIntersects[0]: mostIntersects = [intersects, (i,j)]
FinalIntersection = set(my_list_of_list[mostIntersects[1][0]]).intersection(my_list_of_list[mostIntersects[1][1]])
skipIndexes = set(mostIntersects[1])
for i,sub_list in enumerate(my_list_of_list):
[skipIndexes.add(i) for char in sub_list
if i not in skipIndexes and char in FinalIntersection]
print(*map(list,(mostIntersects, FinalIntersection, skipIndexes)), sep = '\n')
The print provides this :
[4, (0, 1)]
['E', 'C', 'B', 'A']
[0, 1, 2, 5]
This works by first converting the lists to sets using the map function (it has to be turned back into a list so i can use len and iterate properly) I then intersect each list with the others in the list of lists and count how many elements are in each. Each time i find one with a larger number, i set mostIntersections equal to the len and the set indexes. Once i go through them all, i get the lists at the two indexes (0 and 1 in this case) and intersect them to give a list of elements [A,B,C,E] (var:finalIntersection). From there, i just iterate over all lists which are not already being used and just check if any of the elements are found in finalIntersection. If one is, the index of the list is appended to skipIndexes. This results in the final list of indexes {indices? idk} that you were after. Technically the result is a set, but to convert it back you can just use list({0,1,2,5}) which will give you the value you were after.

New to learning python; why is the print for this for-loop different when I use a list comprehension? how do i make the loop be the same?

from my understanding shouldn't the print for X be the same on both? the answer i need is the one that comes from the list-comprehension, which is a new list where every element is -1 from the original list. But the for-loop one only gives 1 element, I also don't know how that element is calculated. Printing x just gives the last element of the list. I'm sure i'm doing something wrong but i'm not sure how to get a list from just using the for-loop. WHAT IS CONFUSING ME is that if the print(x) is part of the for loop it will print the elements of the desired list I need, but NOT in a list, which means the math I wrote works as intended, right?
list= [1,2,3,4,5]
#loop
x=[]
for i in list:
x=[i-1]
print(x)
#list comprehension
x=[i-1 for i in list]
print(x)
#confusing part where this print will be the same as the comprehension but not in a list form
x=[]
for i in list:
x=[i-1]
print(x)
First thing, list is a protected keyword. You should be using list_ at least (that's the naming convention if you really need to use list as the name).
The second iterates element by element, and prints each of the elements, what you want is in the loop to set each of the elements one by one, and then print x (not inside the loop).
list_= [1,2,3,4,5]
x=[]
for i in list_:
x.append(i-1)
print(x)
You should append like this:
lst= [1,2,3,4,5]
#loop
x=[]
for i in lst:
x.append(i-1)
print(x)
#output: [0, 1, 2, 3, 4]
#list comprehension
x=[i-1 for i in lst]
print(x)
#output: [0, 1, 2, 3, 4]

Filtering a list of two-element sublists with a list comprehension

I have a list of lists
input = [[2,13],[5,3],[10,8],[13,4],[15,0],[17,10],[20,5],[25,9],[28,7],[31,0]]
I want to write a list comprehension where for the [a,b] pairs above I get the pairs where b > a. In the above example that would be [2,13].
My attempt
x = [[item[i],[j]] for item in inputArray if j>i]
produces a NameError
NameError: name 'j' is not defined`
The problem with your attempt is that you never tell Python what i and j are supposed to be. The check j > i cannot be computed and the list [item[i],[j]] can't be built without that information.
You can issue
>>> inp = [[2,13],[5,3],[10,8],[13,4],[15,0],[17,10],[20,5],[25,9],[28,7],[31,0]]
>>> [[a, b] for a, b in inp if b > a]
[[2, 13]]
This solution does not produce a NameError because for a, b in inp tells Python to iterate over the elements of inp (two-element sublists) and in each iteration assign the name a to the first element of a sublist and the name b to the second element.
I used the name inp instead of input because the latter is already taken by a builtin function for getting user input.
Explanation of the list comprehension
The comprehension is equivalent to
>>> result = []
>>> for a, b in inp:
... if b > a:
... result.append([a, b])
...
>>> result
[[2, 13]]
Every two-element list in inp is unpacked into the variables a and b. If the filter condition b > a is True, then a list [a, b] is built and included in the final result.
If you don't want to use unpacking, you can also index into the sublists of inp like this:
>>> [sub[:] for sub in inp if sub[1] > sub[0]]
[[2, 13]]
Taking a full slice of sub via sub[:] ensures that like in the other solutions presented so far, the filtered result stores (shallow) copies of the sublists of inp. If copying it not necessary, you can omit the [:].
This code does not produce a NameError because for sub in inp tells Python to iterate over inp and in each iteration assign the name sub to the next sublist. In addition, explicit numbers (0 and 1) are used for the indices.
Personally, I prefer the solution with unpacking. It is easier to read and will run even if the elements of inp don't support indexing, but are iterables from which two elements can be extracted.
You should unpack each pair into the i, j variables, and then you can compare them:
x = [[i, j] for i,j in inputList if j > i]
(note I have renamed inputArray, inputList)
Or without unpacking:
x = [item for item in inputList if item[1] > item[0]]

how to extract numbers from each position of a python list of lists

I have a list :
N=[[0,3,4], [0,1,2,9,3], [0,3]]
How do i get it so use ths list to get another list with each list item being a number for each positon of the list items in N so that the new list looks like:
newList=[[0,0,0], [3,1,3], [4,2] ,[9], [3]]
so he first item in newList is a sublist that contains the first number in N[0], the first number in N[1], and the first number in N[2]. The next sublist in N will do the same just for the second positions.
Could make use of izip_longest, then filter out the default values, eg:
from itertools import izip_longest
N=[[0,3,4], [0,1,2,9,3], [0,3]]
new_list = [[el for el in items if el is not None] for items in izip_longest(*N)]
# [[0, 0, 0], [3, 1, 3], [4, 2], [9], [3]]
Try the list comprehension
newList= [reduce(lambda x,y: x + [y[i]] if i<len(y) else x, N, [])
for i in range(max(map(len,N)))]
The for just iterates i upto the length of the longest sublist in N, using map with len to construct a temporary list of the lengths of the lists in N to calculate the max length.
The reduce builds up each sublist in the result by picking out the ith element of the corresponding input sublist -- but only if that sublist is long enough.
A weird and inneficient way (but maybe easy to understand would be):
Get maximum length of the lists in N
Iterate all those lists from 0 to that maximum.
If you got an error, that's because your original list (the one that came from N) doesn't have that item
N=[[0,3,4], [0,1,2,9,3], [0,3]]
newList = []
max_items = max([len(lst) for lst in N])
for n_lst in N:
for i in range(max_items):
if len(newList) <= i:
newList.append([])
try:
newList[i].append(n_lst[i])
except IndexError:
pass
print "new List: %s" % newList
Another approach, that maybe makes the code cleaner is initializing newList to a list with 5 items, since the longest list on your matrix N ([0,1,2,9,3]) has 5 elements. That way you know that your result (the newList variable) is going to have 5 lists:
N=[[0,3,4], [0,1,2,9,3], [0,3]]
newList = []
max_items = max([len(lst) for lst in N])
newList=list([] for _ in range(max_items))
for i in range(max_items):
for n_lst in N:
if len(n_lst) > i:
newList[i].append(n_lst[i])
print "new List: %s" % newList
Note that instead of catching an IndexException, you can also check if the len() of the list you're evaluating is > i
EDIT:
Just saw John Clements response (https://stackoverflow.com/a/22901233/289011) which is by far the cleaner.
How about the following:
# original list
N = [[0,3,4], [0,1,2,9,3], [0,3]]
# create a new list of `n` lists (n being the length of the longest sublist in N)
newList = [[] for i in xrange(max([len(l) for l in N]))]
# iterate over each list
# enumerate - use the index to append into correct target list
for l in N:
for i, val in enumerate(l):
newList[i].append(val)
print newList
Yields:
[[0, 0, 0], [3, 1, 3], [4, 2], [9], [3]]
Hope this helps! :)
Here is a solution that does not use list comprehensions, but also does not require a pre-processing step to determine maximum list length.
N = [[0,3,4], [0,1,2,9,3], [0,3]]
newList = []
progress = True
i = 0;
while (progress):
progress = False
sublist = []
for list in N:
if len(list) <= i:
continue
else:
sublist.append(list[i])
progress = True
if not progress:
break
newList.append(sublist)
i = i+1
print(N)
print(newList)

Making a list of lists in a For Loop

How do you make a list of lists within a for loop?
Here is what I have coded right now:
a = 0
xy=[[[],[]],[[],[]],[[],[]],[[],[]],[[],[]],[[],[]],[[],[]],[[],[]],[[],[]],[[],[]],[[],[]],[[],[]]]
for liness in range(len(NNCatelogue)):
a=0
for iii in range(len(NNCatelogue[liness])):
while a < len(catid):
if catid[a]==NNCatelogue[liness][iii]:
xyiii = (catid[a], a)
xy.append(xyiii)
a += 1
The output that I get is a lengthy list of pairs, as expected. It looks somewhat like the following:
[...,('C-18-1262', 30908),
('C-18-1264', 30910),
('C-18-1265', 30911),
('C-18-1267', 30913),
('C-18-1250', 30896),
('C-18-1254', 30900),...]
I would like to turn this list of pairs into a list of lists of pairs though. There are 1268 iterations, and the length of each list should be 12. (So 1268 lists with 12 elements in each of them). Any ideas for how to approach this when in a loop?
Something like this, perhaps. Note that I am using iteration over the lists directly to save a lot of unnecessary indexing.
xy = []
for line in NNCatelogue:
l = []
for c in line:
for a, ca in enumerate(catid):
if ca == c:
l.append((ca, a))
xy.append(l)
If you're using the inner loop just to search for the category index, as I suspect you are, a dictionary may be a useful addition to avoid the inner loop.
I have a few friendly suggestions right off the bat:
First of all, the a=0 at the very beginning is redundant. You do the
same thing twice with the a=0 inside of the first for loop.
Second, why are you declaring a huge framework of list elements for xy at
the top? You can always append() what you need as you go
along.
Finally, your while loop is just a simple for loop:
for n in range(len(catid)):
You can make a list of lists using list expansions like so:
list_of_lists = [[j for j in range(0, 3)] for _ in range(0, 3)]
Which outputs a 3x3 list:
[ [0, 1, 2],
[0, 1, 2],
[0, 1, 2]
]

Categories