List to group of tuples - python

I have a list like
A = [1,10,50,100,500]
I need to group by 2 numbers with proper sequence. Output is like this,
B = [(1,9),(10,49),(50,99),(100,499)]
I have tried via yield:
def group(lst, n):
for i in range(0, len(lst), n):
val = lst[i:i+n]
if len(val) == n:
yield tuple(val)
print(list(group([1,10,50,100,500], 2)))

You can simply zip the sequence with itself (without the first item):
A = [1,10,50,100,500]
def group(lst):
for i, j in zip(A, A[1:]): # pairwise items
yield (i, j-1) # decrement second item by 1
>>> list(group(A))
[(1, 9), (10, 49), (50, 99), (100, 499)]
Or use it as list-comprehension without intermediate function:
>>> [(i, j-1) for i, j in zip(A, A[1:])]
[(1, 9), (10, 49), (50, 99), (100, 499)]

You can use list comprehension with islice and zip to iterate pairwise over the list:
>>> from itertools import islice
>>> A = [1,10,50,100,500]
>>> [(x, y - 1) for x, y in zip(A, islice(A, 1, None))]
[(1, 9), (10, 49), (50, 99), (100, 499)]
In above islice returns an iterator that starts from the second element on A. islice is used instead of normal slicing so that the original list doesn't need to be copied:
>>> s = list(islice(A, 1, None))
>>> s
[10, 50, 100, 500]
Then zip is used to create iterable pairs of items from the original list and iterator:
>>> pairs = list(zip(A, s))
>>> pairs
[(1, 10), (10, 50), (50, 100), (100, 500)]
And finally list comprehension iterates over the pairs to create the result:
>>> [(x, y - 1) for x, y in pairs]
[(1, 9), (10, 49), (50, 99), (100, 499)]

Related

Efficiently slice iterable by another iterable

So I'm trying to slice an iterable to the same length of another iterable. For context I was answering this question to get the sum of values grouped by key essentially and I think I can do this more efficiently
from itertools import groupby
x = [(5, 65), (2, 12), (5, 18), (3, 35), (4, 49), (4, 10), (1, 27), (1, 1), (4, 71), (2, 41), (2, 17), (1, 25), (2, 62), (5, 65), (4, 5), (1, 51), (1, 13), (5, 92), (2, 62), (5, 81)]
keys, values = map(iter, zip(*sorted(x)))
print([sum(next(values) for _ in g) for _, g in groupby(keys)])
#[117, 194, 35, 135, 321]
I believe the next(values) for _ in g can be done functionally or more concisely. Essentially in pseudocode:
#from this
sum(next(values) for _ in g)
#to this
sum(values[length of g])
I know the above won't work but all I can think of is using zip because it only iterates to the end of the smallest iterable. Although, when I tried that it's consuming more than the group is long. (Also it isn't very readable) See below what I tried:
print([sum(next(zip(*zip(values, g)))) for _, g in groupby(keys)])
#[117, 217, 10, 219, 92]
I've tried searching for this with no results unless I'm not searching the right thing.
I've thought of other solutions such as using islice but I would need the length of g and thats another messy solution. Another being I could just use operator.itemgetter but if I could figure out how to do what I am doing more concisely then maybe I can use it in other solutions too.
You don't have to separate the keys and values at all. It can be handled by the key functions:
from operator import itemgetter as ig
[sum(map(ig(1), g)) for _, g in groupby(sorted(x), key=ig(0))]
You could use ilen from more-itertools and then islice:
[sum(islice(values, ilen(g))) for _, g in groupby(keys)]
Or with zip, but the group first:
[sum(x for _, x in zip(g, values)) for _, g in groupby(keys)]
Don't know how "efficient" these are for you, as you only showed very small data and I'm not sure how you'd generalize it (in particular, how long your groups are).
Maybe what you are asking can be accomplished by the following class. The class groupby_other takes an iterable it1 and an iterable of iterables it2: it2_0, it2_1, ... and yields groups of elements from it1 with lengths equal to it2_0, it2_1 and so on, until one of it1 or it2 is exhausted.
class groupby_other:
"""
Make an iterator that returns groups from iterable1. Each group has the same
number of elements as each element in iterable2.
>>> y = [1,2,3,4,5]
>>> z = [[1,1], [4,4,4]]
>>> [list(x) for x in groupby_other(y,z)]
[[1, 2], [3, 4, 5]]
Grouping is terminated when one of the iterables is exhausted
>>> y = [1,2,3,4,5]
>>> z = [[1,1], [4,4,4], [4,5]]
>>> [list(x) for x in groupby_other(y,z)]
[[1, 2], [3, 4, 5]]
>>> z = [[1,1], [4,4,4]]
>>> [list(x) for x in groupby_other(y,z)]
[[1, 2], [3, 4, 5]]
>>> z = [[1,1], [4,4]]
>>> [list(x) for x in groupby_other(y,z)]
[[1, 2], [3, 4]]
>>> [list(x) for x in groupby_other([],z)]
[]
>>> [list(x) for x in groupby_other(y,[])]
[]
"""
def __init__(self, iterable1, iterable2, key=None):
self.it1 = iter(iterable1)
self.it2 = iter(iterable2)
self.current_it = None
self.done = False
def __iter__(self):
return self
def __next__(self):
if self.done:
raise StopIteration
current_group = iter(next(self.it2)) # Exit on StopIteration
current_item = next(self.it1)
return self._grouper(current_item, current_group)
def _grouper(self, current_item, current_group):
try:
next(current_group)
yield current_item
for _ in current_group:
yield next(self.it1)
except StopIteration:
self.done=True
return
Then you can do:
>>> [sum(x) for x in groupby_other(values, (g for _, g in groupby(keys)))]
[117, 194, 35, 135, 321]

Is there a pythonic way to iterate over two lists one element at a time?

I have two lists: [1, 2, 3] and [10, 20, 30]. Is there a way to iterate moving one element in each list in each step? Ex
(1, 10)
(1, 20)
(2, 20)
(2, 30)
(3, 30)
I know zip moves one element in both lists in each step, but that's not what I'm looking for
Is it what you expect:
def zip2(l1, l2):
for i, a in enumerate(l1):
for b in l2[i:i+2]:
yield (a, b)
>>> list(zip2(l1, l2))
[(1, 10), (1, 20), (2, 20), (2, 30), (3, 30)]
def dupe(l):
return [val for val in l for _ in (0,1)]
list(zip(dupe([1,2,3]), dupe([10,20,30])[1:]))
# [(1, 10), (1, 20), (2, 20), (2, 30), (3, 30)]
One with zip and list comprehension.
For good measure, here's a solution that works with arbitrary iterables, not just indexable sequences:
def frobnicate(a, b):
ita, itb = iter(a), iter(b)
flip = False
EMPTY = object()
try:
x, y = next(ita), next(itb)
yield x, y
except StopIteration:
return
while True:
flip = not flip
if flip:
current = y = next(itb, EMPTY)
else:
current = x = next(ita, EMPTY)
if current is EMPTY:
return
yield x, y

How to get the highest 4 tuple values?

I am trying to get the highest 4 values in a list of tuples and put them into a new list. However, if there are two tuples with the same value I want to take the one with the lowest number.
The list originally looks like this:
[(9, 20), (3, 16), (54, 13), (67, 10), (2, 10)...]
And I want the new list to look like this:
[(9,20), (3,16), (54, 13), (2,10)]
This is my current code any suggestions?
sorted_y = sorted(sorted_x, key=lambda t: t[1], reverse=True)[:5]
sorted_z = []
while n < 4:
n = 0
x = 0
y = 0
if sorted_y[x][y] > sorted_y[x+1][y]:
sorted_z.append(sorted_y[x][y])
print(sorted_z)
print(n)
n = n + 1
elif sorted_y[x][y] == sorted_y[x+1][y]:
a = sorted_y[x]
b = sorted_y[x+1]
if a > b:
sorted_z.append(sorted_y[x+1][y])
else:
sorted_z.append(sorted_y[x][y])
n = n + 1
print(sorted_z)
print(n)
Edit: When talking about lowest value I mean the highest value in the second value of the tuple and then if two second values are the same I want to take the lowest first value of the two.
How about groupby?
from itertools import groupby, islice
from operator import itemgetter
data = [(9, 20), (3, 16), (54, 13), (67, 10), (2, 10)]
pre_sorted = sorted(data, key=itemgetter(1), reverse=True)
result = [sorted(group, key=itemgetter(0))[0] for key, group in islice(groupby(pre_sorted, key=itemgetter(1)), 4)]
print(result)
Output:
[(9, 20), (3, 16), (54, 13), (2, 10)]
Explanation:
This first sorts the data by the second element's value in descending order. groupby then puts them into groups where each tuple in the group has the same value for the second element.
Using islice, we take the top four groups and sort each by the value of the first element in ascending order. Taking the first value of each group, we arrive at our answer.
You can try this :
l = [(9, 20), (3, 16), (54, 13), (67, 10), (2, 10)]
asv = set([i[1] for i in l]) # The set of unique second elements
new_l = [(min([i[0] for i in l if i[1]==k]),k) for k in asv]
OUTPUT :
[(3, 16), (2, 10), (9, 20), (54, 13)]

List to list of tuples conversion

I am brand new to Python. I can change a list to a tuple, for example
li = [1]
tuple(li)
I am trying to create a tuple that gives the item and its position, so a tuple that would come out (1, 0). I have no idea how to get started.
If you want a tuple of tuples, you can use a generator expression like this;
li = [4, 5, 6]
tuple((li[i], i) for i in range(len(li)))
Or with enumerate;
tuple((v, k) for k,v in enumerate(li))
Both will return;
((4, 0), (5, 1), (6, 2))
If you want a list of tuples, you can use a list comprehension expression like this;
[(li[i], i) for i in range(len(li))]
or with enumerate;
[(v,k) for k, v in enumerate(li)]
Both will return;
[(4, 0), (5, 1), (6, 2)]
Use enumerate which does exactly what you need, just with elements and indexes flipped:
> li = [2, 4, 6]
> [(x, i) for i, x in enumerate(li)]
[(2, 0), (4, 1), (6, 2)]
If you want a just one tuple you could do this:
li = (list[1], 1)
The brackets here are the literal syntax for tuples.
If you wanted to do it for all the elements of the list, you could use a list comprehension:
lis = [(list[i], i) for i in range(len(list))]
Which would create a list of tuples were each tuple has the element and its index.
One possible way is to use enumerate,
li = [10, 20, 30]
list(enumerate(li))
prints
[(0, 10), (1, 20), (2, 30)]
If you want the output to be in (item, position) order, you can use,
[(v, k) for k,v in enumerate(li)]
zip with range is another option:
In [4]: li = [2,4,6]
In [5]: zip(li, range(len(li)))
Out[5]: [(2, 0), (4, 1), (6, 2)]

Attempting Python list comprehension with two variable of different ranges

I'm trying to generate a list quickly with content from two different arrays of size n and n/2. As an example:
A = [70, 60, 50, 40, 30, 20, 10, 0]
B = [1, 2, 3, 4]
I wish to generate something like
[(A[x], B[y]) for x in range(len(A)) for y in range(len(B))]
I understand the second for statement is the nested for loop after the "x" one. I'm trying to get the contents of the new array to be
A[0], B[0]
A[1], B[1]
A[2], B[2]
A[3], B[3]
A[4], B[0]
A[5], B[1]
A[6], B[2]
A[7], B[3]
Could anyone point me in the right direction?
Don't use nested loops; you are pairing up A and B, with B repeating as needed. What you need is zip() (to do the pairing), and itertools.cycle() (to repeat B):
from itertools import cycle
zip(A, cycle(B))
If B is always going to be half the size of A, you could also just double B:
zip(A, B + B)
Demo:
>>> from itertools import cycle
>>> A = [70, 60, 50, 40, 30, 20, 10, 0]
>>> B = [1, 2, 3, 4]
>>> zip(A, cycle(B))
[(70, 1), (60, 2), (50, 3), (40, 4), (30, 1), (20, 2), (10, 3), (0, 4)]
>>> zip(A, B + B)
[(70, 1), (60, 2), (50, 3), (40, 4), (30, 1), (20, 2), (10, 3), (0, 4)]
For cases where it is not known which one is the longer list, you could use min() and max() to pick which one to cycle:
zip(max((A, B), key=len), cycle(min((A, B), key=len))
or for an arbitrary number of lists to pair up, cycle them all but use itertools.islice() to limit things to the maximum length:
inputs = (A, B) # potentially more
max_length = max(len(elem) for elem in inputs)
zip(*(islice(cycle(elem), max_length) for elem in inputs))
Demo:
>>> from itertools import islice
>>> inputs = (A, B) # potentially more
>>> max_length = max(len(elem) for elem in inputs)
>>> zip(*(islice(cycle(elem), max_length) for elem in inputs))
[(70, 1), (60, 2), (50, 3), (40, 4), (30, 1), (20, 2), (10, 3), (0, 4)]
[(A[x % len(A)], B[x % len(B)]) for x in range(max(len(A), len(B)))]
This will work whether or not A is the larger list. :)
Try using only one for loop instead of two and having the second wrap back to 0 once it gets past its length.
[(A[x], B[x%len(B)]) for x in range(len(A))]
Note that this will only work if A is the longer list. If you know B will always be half the size of A you can also use this:
list(zip(A, B*2))

Categories

Resources