Related
guys see the code and the output i got
import itertools as it
ranks = ['A', 'K', 'Q', 'J', '10', '9', '8', '7', '6', '5', '4', '3', '2']
suits = ['H', 'D', 'C', 'S']
cards = it.product(ranks,suits)
l = []
for i in range(5):
l.append(it.islice(cards,2))
print(list(zip(*l)))
[(('A', 'H'), ('A', 'D'), ('A', 'C'), ('A', 'S'), ('K', 'H')), (('K', 'D'), ('K', 'C'), ('K', 'S'), ('Q', 'H'), ('Q', 'D'))]
This is the output i got
but shouldnt the output be like below
[(('A', 'H'), ('A','C') ,('K', 'H'),('K', 'C'),('Q', 'H')) , (('A', 'D'), ('A', 'S'), ('K', 'D'), ('K', 'S') , ('Q', 'D'))] this is the expected output
i dont know why zip function is working like this here , can someone help please
To get the desired result, you need to convert the islice object to list l.append(list(it.islice(cards,2))):
import itertools as it
ranks = ['A', 'K', 'Q', 'J', '10', '9', '8', '7', '6', '5', '4', '3', '2']
suits = ['H', 'D', 'C', 'S']
cards = it.product(ranks,suits)
l = []
for i in range(5):
l.append(list(it.islice(cards,2))) # here
print(list(zip(*l)))
Prints:
[(('A', 'H'), ('A', 'C'), ('K', 'H'), ('K', 'C'), ('Q', 'H')), (('A', 'D'), ('A', 'S'), ('K', 'D'), ('K', 'S'), ('Q', 'D'))]
This is rather difficult topic but the key idea is that islice and zip function are 'lazy', which means they are efficient. (These functions access elements one-by-one and cannot go backwards. In comparison, constructing a whole list in advance is expensive operation)
You have made "l" a list of 5 iterators.
It doesn't imply that first element of "l" would represent the first 2 tuples(1st&2nd) of the product, and second element of "l" would represent the next 2 tuples(3rd&4th) etc. They are just 5 iterators on the product 'cards'.
Now zip function is executed on 5 elements of list "l".
To compute the first zipped element, lazy 5 iterators are sequentially executed.
First iterator(=l[0]) takes the first element of 'cards' (=AH)
Second iterator(=l[1]) takes the second element of 'cards' (=AD)
.. and so on.
That explains your output.
Now here's another interesting idea.
what if you put a print("cards: ", list(card)) line
after the initialization of variable 'cards'? would it change the output of the code?
It's just a print statement so intuitively it shouldn't change the behavior of program apart from printing some line. But it does.
import itertools as it
ranks = ['A', 'K', 'Q', 'J', '10', '9', '8', '7', '6', '5', '4', '3', '2']
suits = ['H', 'D', 'C', 'S']
cards = it.product(ranks,suits)
print("cards: ", list(cards)) ''' comment this line to get your original output,
uncomment to make list(zip(*l)) return [] '''
l = []
for i in range(5):
l.append(it.islice(cards,2))
print(l)
print("zip: ", list(zip(*l)))
why is list(zip(*l)) empty list now?
While printing 'list(cards)' we used the iterator on 'cards' until it reached the StopIteration. This is because we made the whole list of it.
since we used it, the next(iter(cards)) will raise StopIteration error.
Therefore, when elements of list "l" try to iterate over 'cards', they can't because it's already at the end of iteration. (StopIteration)
What we should learn from this is that iterators are very tricky to use, but they are powerful when used correctly. Just like pointers and memory allocations are dangerous but powerful, same goes for iterator and generators. When you reuse iterator of an object over and over(in this case iterator on cards was reused a lot), it is good to make a solid list of the results and save it, rather than leave it as a floating, dangerous iterators.
I'm trying to generate an array of strings (or any other data structure that might be more useful for my task, but I can't think of anything else) in Python.
The program I'm working on has several sets of radio buttons. For example a set of "Block"/"Alternate" and "Single"/"Duplicate".
Examples on how the array of strings should look when they are activated:
Block, Single:
list = ['A', 'B', 'C', '1', '2', '3']
Alternating, Single:
list = ['A', '1', 'B', '2', 'C', '3']
Alternating, Duplicate:
list = ['A', 'A', '1', '1', 'B', 'B', '2', '2', 'C', 'C', '3', '3']
Those are only several examples, the program has way more, but the concept is the same.
I need to read this array of strings and use it as a schema of sorts to further select some data from my Pandas Dataframe.
How would I go about generating this array without writing an if clause for every single possible combination?
I tried to solve this using Jupyter Notebook and some widgets for the user interaction. I don' know how you get this informations but to replicate the same behaviour I am using this.
In [1]:
import ipywidgets as widgets # Widgets for user interactions
## Change the list as you want
my_list = ['A', 'B', 'C', '1', '2', '3']
single_duplicate = widgets.ToggleButtons(
description='Do you want to Duplicate the list ?',
options=['Single', 'Duplicate'],
value='Single',
style={'description_width': 'initial'}
)
single_duplicate
Out [1]:
In [2]:
block_alter = widgets.ToggleButtons(
description='Do you want to Alternate the list?',
options=['Block', 'Alternate'],
value='Block',
style={'description_width': 'initial'}
)
block_alter
Out [2]:
Then define the function to manipulate the list with the user input
In [3]:
def get_array(single_duplicate, block_alter, my_list):
temporary_list = []
## Answer to Single or Duplicate
if single_duplicate == 'Duplicate':
for elem in my_list:
temporary_list.append(elem)
temporary_list.append(elem)
else:
temporary_list = my_list
## Answer to Block or Aternate ?
new_list = []
if block_alter == 'Alternate':
half = int(len(temporary_list)/2)
for i in range(half):
new_list.append(temporary_list[i])
new_list.append(temporary_list[i + half])
else:
new_list = temporary_list
return new_list
Use get_array(single_duplicate.value, block_alter.value, my_list) to see the output with the user's choses
Here some exemples
In [3]:
print(get_array('Single', 'Block', my_list))
print(get_array('Duplicate', 'Block', my_list))
print(get_array('Single', 'Alternate', my_list))
print(get_array('Duplicate', 'Alternate', my_list))
Out [3]:
['A', 'B', 'C', '1', '2', '3']
['A', 'A', 'B', 'B', 'C', 'C', '1', '1', '2', '2', '3', '3']
['A', '1', 'B', '2', 'C', '3']
['A', '1', 'A', '1', 'B', '2', 'B', '2', 'C', '3', 'C', '3']
>>> import itertools
>>> a = ['1', '2', '3', '4', '5']
>>> b = ['a', 'b', 'c', 'd', 'e', 'f']
>>> list(itertools.chain.from_iterable(zip(a,b)))
['1', 'a', '2', 'b', '3', 'c', '4', 'd', '5', 'e']
As you can see, I have two asymmetrical list and I want to mix them like above. The problem is it ignore the last item.
Expected:
['1', 'a', '2', 'b', '3', 'c', '4', 'd', '5', 'e', 'f']
Actual:
['1', 'a', '2', 'b', '3', 'c', '4', 'd', '5', 'e']
Since you're using itertools in the first place, I assume you want this to work on any iterables, not just lists, and ideally without eagerly listifying them first. Otherwise, just do this:
list(itertools.chain.from_iterable(zip(a,b))) + a[len(b):] + b[len(a):]
The zip_longest function almost does what you want out of the box, but it inserts a fillvalue (default None) for each slot once the shorter iterable runs out. If your values are all truthy, you can just filter those out with if i as in Ajax1234's answer, or filter with None as the predicate, but if your values can be anything in Python, even None, the only way to do it gets pretty clunky:
_sentinel = object()
[elem for elem in itertools.chain.from_iterable(itertools.zip_longest(a, b, fillvalue=_sentinel))
if elem is not _sentinel]
But you can look at how zip_longest works and do the same thing yourself, only generating "incomplete" tuples instead of "filled-in" tuples, and then call it like this:
list(itertools.chain.from_iterable(zip_longest_nofill(a, b)))
Although making a variant of the zip_longest code from the docs that's easy enough to explain in an SO answer is a bit challenging, so maybe it's better to use an explicit loop:
def zip_longest_nofill(*args):
empty = object()
its = [iter(arg) for arg in args]
while True:
vals = (next(it, empty) for it in its)
tup = tuple(val for val in vals if val is not empty)
if not tup:
return
yield tup
I think this version is a lot easier to understand (although it was actually a bit harder to write…)
Of course if the only thing you're ever going to use zip_longest_nofill for is to implement your flattened_zip_nofill, it's even easier to just inline it into the flattening part, at which point you end up with basically the two-liner in the last section.
Instead of chain, use zip_longest:
import itertools
a = ['1', '2', '3', '4', '5']
b = ['a', 'b', 'c', 'd', 'e', 'f']
new_results = [i for b in itertools.zip_longest(a, b) for i in b if i is not None]
Output:
['1', 'a', '2', 'b', '3', 'c', '4', 'd', '5', 'e', 'f']
Just manually append the remaining:
def mix(a, b):
c = list(itertools.chain.from_iterable(zip(a,b)))
c += a[len(b)] + b[len(a):]
return c
One liner:
mix = lambda a, b: list(itertools.chain.from_iterable(zip(a,b))) + a[len(b)] + b[len(a):]
This should work but it's not very elegant
lst = []
for i in range(temp = max(len(a), len(b))):
if i < len(a): lst.append(a[i])
if i < len(b): lst.append(b[i])
lst
You can try itertools zip_longest:
a = ['1', '2', '3', '4', '5']
b = ['a', 'b', 'c', 'd', 'e', 'f']
import itertools
output=[]
for i in itertools.zip_longest(a,b):
if i[0]==None:
output.append(i[1])
else:
output.extend([i[0],i[1]])
print(output)
output:
['1', 'a', '2', 'b', '3', 'c', '4', 'd', '5', 'e', 'f']
Say, I have a two 2D list like below:
[[('a', '1'), ('a', '12'), ('a', '3')], [('b', '21'), ('b', '31')], [ ('c', '11')]]
The output I want to achieve is:
Output_list=[['1','12','3'], ['21','31'], ['11']]
The main complexity here is I want to achieve the output through a single list comprehension.
One of my attempts was:
print [a for innerList in fin_list1 for a,b in innerList]
Output:
['1', '12', '3', '21', '31', '11']
But, as you can see, though I have successfully retrieve the second elements of each tuple, i failed to retain my inner list structure.
We start with this:
>>> l = [[('a', '1'), ('a', '12'), ('a', '3')], [('b', '21'), ('b', '31')], [ ('c', '11')]]
Initially you tried to do something along these lines:
>>> [y for sublist in l for x, y in sublist]
['1', '12', '3', '21', '31', '11']
The mistake here is that this list comprehension is one-dimensional, i.e. all the values will be just integers instead of lists themselves
In order to make this two-dimensional, our values need to be lists. The easiest way to do this is by having our value expression be a nested list comprehension that iterates over the elements of the sublists of the original list:
>>> [[y for x, y in sublist] for sublist in l]
[['1', '12', '3'], ['21', '31'], ['11']]
Technically this is two list comprehensions, but obviously a list comprehension can be replaced by map as explained in Roberto's answer.
First, let's assign the data:
>>> data = [
[('a', '1'), ('a', '12'), ('a', '3')],
[('b', '21'), ('b', '31')],
[('c', '11')],
]
You can use itemgetter to create a function that gets one element from the tuple:
>>> from operator import itemgetter
>>> first = itemgetter(0)
>>> second = itemgetter(1)
Now you can transform inner lists by using map:
>>> [map(first, row) for row in data]
[['a', 'a', 'a'], ['b', 'b'], ['c']]
>>> [map(second, row) for row in data]
[['1', '12', '3'], ['21', '31'], ['11']]
You could also create first and second without using itemgetter:
def first(xs):
return xs[0]
def second(xs):
return xs[1]
the_list = [[('a', '1'), ('a', '12'), ('a', '3')], [('b', '21'), ('b', '31')], [ ('c', '11')]]
new_list = [[x[1] for x in y] for y in the_list]
print new_list
Output:
[['1', '12', '3'], ['21', '31'], ['11']]
I have the list:
list_mix = [['1','2','3'],['a','b','c'], ['d','e','f'], ['g','h','i']]
The first list must be merged with other lists in the list_mis. The result should be:
['1','2','3','a','b','c']
['1','2','3','d','e','f']
['1','2','3','g','h','i']
The following code gives me "TypeError: list indices must be integers, not list":
for item in list_mix[1:]:
print (list_mix[0] + list_mix[item])
Any solution without external libraries would be appreciated.
item is the sublist already, not an index. Just use it directly:
for item in list_mix[1:]:
print (list_mix[0] + item)
The Python for statement is a Foreach loop construct, assigning each element from list_mix[1:] to item in turn.
Demo:
>>> list_mix = [['1','2','3'],['a','b','c'], ['d','e','f'], ['g','h','i']]
>>> for item in list_mix[1:]:
... print (list_mix[0] + item)
...
['1', '2', '3', 'a', 'b', 'c']
['1', '2', '3', 'd', 'e', 'f']
['1', '2', '3', 'g', 'h', 'i']
Use a list comprehension add every sublist to sublist 0 of list_mix, use list_mix[1:] to start at the element after ['1','2','3'].
[list_mix[0] + x for x in list_mix[1:]]
[['1', '2', '3', 'a', 'b', 'c'], ['1', '2', '3', 'd', 'e', 'f'], ['1', '2', '3', 'g', 'h', 'i']]