I want to sort a tuple of integers(in decreasing order) and I then want to save a tuple with the order of indices after sorting in a set. The following piece of code does the job:
my_set = set()
l = (1, 3, 6, 10, 15, 21)
my_set.add(list(zip(*sorted(enumerate(l), key=lambda x: x[1], reverse=True)))[0])
If I evaluate my_set I now have {(5, 4, 3, 2, 1, 0)}
I was trying to do the same with the following code(i for indices, v for values):
my_set.add(i for i, v in zip(*sorted(enumerate(l), key=lambda x: x[1], reverse=True)))
It doesn't work in the same way. The set becomes { <generator object <genexpr> at 0x7f82e49ce360>}
Why is it that if I feed the zip result into a list I can access the tuples inside but I can't use the other syntax?
Is there an alternative way of obtaining some tuple created by zip without having to feed into a list and then indexing into it?
Look at the output that zip returns.
>>> list(zip(*sorted(enumerate(l), key=lambda x: x[1], reverse=True)))
[(5, 4, 3, 2, 1, 0), (21, 15, 10, 6, 3, 1)]
It's always going to be a list of 2 tuples - one tuple being the argsorted indices, and the other being the actual sorted items.
First up, you don't realise this because you're hashing a generator inside a set... but when you decide to exhaust the generator, be prepared for a
ValueError: too many values to unpack (expected 2)
In summary, you're iterating over zip incorrectly. You don't even need to iterate over zip, if it's just the first tuple you're interested in.
What you should instead do, is use next;
>>> my_set.add(next(zip(*sorted(enumerate(l), key=lambda x: x[1], reverse=True))))
>>> my_set
{(5, 4, 3, 2, 1, 0)}
Which gets you just the first tuple.
Related
I'm writing this question despite the many answers on stackoverflow as the solutions did not work for my problem.
I have 2 Lists, List1 and List2. When I dict(zip(List1,List2)) the order of the elements inside the dictionary are disturbed.
print s_key
print value
sorted_dict = {k: v for k,v in zip(s_key,value)}
another_test = dict(zip(s_key,value))
print sorted_dict
print another_test
print zip(s_key,value))
Terminal :
[2, 1, 3]
[31, 12, 5]
{1: 12, 2: 31, 3: 5}
{1: 12, 2: 31, 3: 5}
[(2, 31), (1, 12), (3, 5)]
I was under the impression that the [(2, 31), (1, 12), (3, 5)] would be converted to a dict
Any help to understand where or what I'm doing wrong would help! Thanks!
a=[2, 1, 3]
b=[31, 12, 5]
from collections import OrderedDict
print(OrderedDict(zip(a,b)))
You cannot sort a dictionary, in your case if you wanted to display sorted key/values of your dictionary you can convert it to a list of tuples as you have and sort it by whichever element you want. In the code below it creates a list of tuples and sorts by the first element in the tuples:
l1,l2=[2, 1, 3],[31, 12, 5]
print ([(one,two) for (one,two) in
sorted(zip(l1,l2),key=lambda pair: pair[0])])
prints:
[(1, 12), (2, 31), (3, 5)]
shoutout to Sorting list based on values from another list? for the help
Either that or create a list of the dictionaries keys and sort the list then loop through the list and call each key
Or use ordered dict as others have pointed out
Given a dictionary like this:
dic = {(7, 3): 18.51, (1, 3): 18.751, (5, 6): 34.917, (9, 8): 18.9738}
I want to convert it to a list of tuples like this:
my_list = [(7, 3, 18.51), (1, 3, 18.751), (5, 6, 34.917), (9, 8, 18.9738)]
I could have used a loop but I wonder if there is a neat way to do so instead of loops.
Simply use list(..) on some generator:
my_list = list(key+(val,) for key,val in dic.items())
This works since:
list(..) takes as input an iterable and converts it to a list; and
key+(val,) for key,val dic.items() is a generator that takes a pair of key-values of dic and transforms it into a tuple appending the val to the key.
Since we use a generator for a list, we can simplify this with list comprehension:
my_list = [key+(val,) for key,val in dic.items()]
Finally mind that the order in which the tuples occur is not fixed this is because the order how a dict stores elements is not fixed as well.
Suppose a generator yields the below tuples one by one (from left to right)
(1, 2, 3), (2, 5, 6), (3, 7, 10), (4, 5, 11), (3, 5, 15), (4, 5, 9), (4, 6, 12)
...
and suppose I'd like to iterate as long as the predicate is true. Let that predicate be sum(yielded_value) < 20. Then the iterator will stop by (3, 5, 15). I can do it with, say:
list(itertools.takewhile(lambda x: sum(x) < 20, some_generator()))
Question, how do I write a similar expression with two predicates? Suppose I want:
list(itertools.takewhile(lambda x: sum(x) < 20 and first_value_of_tuple > 3, some_generator()))
(which, in this case, stop by (4, 6, 12).)
You can access to elements of each tuple with index.
list(itertools.takewhile(lambda x: sum(x) < 20 and x[0] > 3, some_generator()))
Since everything in itertools is lazily iterated, and you are using and for two predicates, you can simply use two takewhile iterators. Sometimes I find this more readable than putting both predicates in a single predicate function or lambda:
lessthan20 = itertools.takewhile(lambda x: sum(x) < 20, some_generator())
greaterthan3 = itertools.takewhile(lambda x: x[0] > 3, lessthan20)
list(greaterthan3)
It also makes it so that you don't have a single huge one liner if you need to add even more predicates in the future.
If you have additional predicates and need to access all the elements of your tuples you can also unpack them in your lambda function:
list(itertools.takewhile(lambda (x, y, z): x+y+z < 20 and x > 3 and y < 7 and z > 1, some_generator()))
This also asserts that all your tuples have length 3. If you get a tuple with 4 values, it fails hard, as opposed to continuing silently. Obviously only useful in some contexts.
I was trying to implement the reverse function of itertools.izip on Python 2.7.1. The thing is that I find a problem, and I don't have an explantion.
Solution 1, iunzip_v1 works perfectly. But solution 2. iunzip_v2, doesn't works as expected. Til now, I haven't found any relevant information about this problem, and reading the PEP about generators, it sound it should work, but it doesn't.
import itertools
from operator import itemgetter
def iunzip_v1(iterable):
_tmp, iterable = itertools.tee(iterable, 2)
iters = itertools.tee(iterable, len(_tmp.next()))
return tuple(itertools.imap(itemgetter(i), it) for i, it in enumerate(iters))
def iunzip_v2(iterable):
_tmp, iterable = itertools.tee(iterable, 2)
iters = itertools.tee(iterable, len(_tmp.next()))
return tuple((elem[i] for elem in it) for i, it in enumerate(iters))
result:
In [17]: l
Out[17]: [(0, 0, 0), (1, 2, 3), (2, 4, 6), (3, 6, 9), (4, 8, 12)]
In [18]: map(list, iunzip.iunzip_v1(l))
Out[18]: [[0, 1, 2, 3, 4], [0, 2, 4, 6, 8], [0, 3, 6, 9, 12]]
In [19]: map(list, iunzip.iunzip_v2(l))
Out[19]: [[0, 3, 6, 9, 12], [0, 3, 6, 9, 12], [0, 3, 6, 9, 12]]
Seems that iunzip_v2 is using the last value, so the generators aren't keeping the value while they are created inside the first generator.
I'm missing something and I don't know what is.
Thanks in advance if something can clarify me this situation.
UPDATE:
I've found the explanation here PEP-289, my first read was at PEP-255.
The solution I'm trying to implement is a lazy one, so:
zip(*iter) or izip(*...)
doesn't work for me, because *arg expand the argument list.
You're reinventing the wheel in a crazy way. izip is its own inverse:
>>> list(izip(*izip(range(10), range(10))))
[(0, 1, 2, 3, 4, 5, 6, 7, 8, 9), (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)]
But that doesn't quite answer your question, does it?
The problem with your nested generators is a scoping problem that happens because the innermost generators don't get used until the outermost generator has already run:
def iunzip_v2(iterable):
_tmp, iterable = itertools.tee(iterable, 2)
iters = itertools.tee(iterable, len(_tmp.next()))
return tuple((elem[i] for elem in it) for i, it in enumerate(iters))
Here, you generate three generators, each of which uses the same variable, i. Copies of this variable are not made. Then, tuple exhausts the outermost generator, creating a tuple of generators:
>>> iunzip_v2((range(3), range(3)))
(<generator object <genexpr> at 0x1004d4a50>, <generator object <genexpr> at 0x1004d4aa0>, <generator object <genexpr> at 0x1004d4af0>)
At this point, each of these generators will execute elem[i] for each element of it. And since i is now equal to 3 for all three generators, you get the last element each time.
The reason the first version works is that itemgetter(i) is a closure, with its own scope -- so every time it returns a function, it generates a new scope, within which the value of i does not change.
Ok this is a bit tricky. When you use a name like i the value it stands for is looked up just during runtime. In this code:
return tuple((elem[i] for elem in it) for i, it in enumerate(iters))
you return a number of generators, (elem[i] for elem in it) and each of them uses the same name i. When the function returns, the loop in tuple( .. for i in .. ) has ended and i has been set to it's final value (3 in your example). Once you evaluate these generators to lists, they all create the same values because they are using the same i.
Btw:
unzip = lambda zipped: zip(*zipped)
I'm trying to convert a list to a dictionary by using the dict function.
inpu = input.split(",")
dic = dict(inpu)
The above code is trying to get a string and split it on ',' and afterwards I use the dict function to convert the list to a dictionary.
However, I get this error:
ValueError: dictionary update sequence element #0 has length 6; 2 is required
Can anybody help?
dict expects an iterable of 2-element containers (like a list of tuples). You can't just pass a list of items, it doesn't know what's a key and what's a value.
You are trying to do this:
>>> range(10)
<<< [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> dict(range(10))
---------------------------------------------------------------------------
TypeError: cannot convert dictionary update sequence element #0 to a sequence
dict expects a list like this:
>>> zip(lowercase[:5], range(5))
<<<
[('a', 0),
('b', 1),
('c', 2),
('d', 3),
('e', 4)]
The first element in the tuple becomes the key, the second becomes the value.
>>> dict(zip(lowercase[:5], range(5)))
<<<
{'a': 0,
'b': 1,
'c': 2,
'd': 3,
'e': 4}
As listed on the Python Data structures Docs. The dict() constructor builds dictionaries directly from lists of key-value pairs stored as tuples.
so the inpu array must be of form ('key', 'value') at each position, for example
>>> dict([('sape', 4139), ('guido', 4127), ('jack', 4098)])
{'sape': 4139, 'jack': 4098, 'guido': 4127}
your input array is probably greater than 2 in size
A dictionary is a key-value pair which is why it says length-2 lists are needed to match keys to values. You are splitting the input into a flat list, which is why Python is complaining in this case - it doesn't know what to specify as keys and what to specify as values.