Grouping list items by modulo? - python

I created a list that includes 14 integers. I wanted to group them by modulo. I can group them but I dont want to duplicate numbers in same group.
for ex:
[13, 40, 42, 17, 43, 45, 45, 6, 7, 7, 46, 48, 22, 51]
output:
[[13], [40], [42], [17, 43], [45, 45, 6], [7, 7, 46], [48, 22], [51]]
but it should be:
[[13], [40], [42], [17, 43],[45],[45, 6],[7],[ 7, 46], [48, 22], [51]]
my code
def projection(val):
return val %13
player1_sorted = sorted(player1,key=projection)
print(player1_sorted)
player1_grouped = [list(it) for k, it in groupby(player1_sorted, projection)]

You can write a function to split the sub lists:
from itertools import groupby, chain
def chunker(it):
chunk = []
for x in it:
if chunk and x == chunk[-1]:
yield chunk
chunk = []
chunk.append(x)
if chunk:
yield chunk
[*chain(*([*chunker(g)] for _, g in groupby(player1, key=projection)))]
# [[13], [40], [42], [17, 43], [45], [45, 6], [7], [7, 46], [48, 22], [51]]

Related

Comparing two lists and performing an operation in Python

I have two lists I and i. I want to find for each element of i, how many values are less than in I and add the total number of such values to the specific i element. For example, element 15 in i has two values less than itself in I i.e. [8,11]. So 2 should be added to 15 and the combination stored in Values. I present the expected output.
I = [8, 11, 19, 37, 40, 42]
i=[15, 17, 27, 28, 31, 41]
The expected output is
New i=[17,19,30,31,34,46]
Values=[[8,11],[8,11],[8,11,19],[8,11,19],[8,11,19],[8,11,19,37,40]]
Assuming your list I is sorted, you can use bisect_left to get insertion point in your list I for each element in i and then slice the list. It uses binary search.
With that you can do:
from bisect import bisect_left
Values = [I[:bisect_left(I, e)] for e in i]
New_i = [e + len(Values[j]) for j, e in enumerate(i)]
print(Values):
[[8, 11], [8, 11], [8, 11, 19], [8, 11, 19], [8, 11, 19], [8, 11, 19, 37, 40]]
print(New_i):
[17, 19, 30, 31, 34, 46]
BTW I highly recommend not to use I and i for your variable names.
You can use two list comprehensions
>>> # for each element in i, add the elements in I to a list if they are smaller
>>> values = [[e for e in I if e < n] for n in i]
>>> # add the element of i to the number of elements in I that are smaller
>>> new_i = [sum(x) for x in zip(i, map(len, values))]
>>> values
[[8, 11], [8, 11], [8, 11, 19], [8, 11, 19], [8, 11, 19], [8, 11, 19, 37, 40]]
>>> new_i
[17, 19, 30, 31, 34, 46]
the other solutions are 100% correct, here's another solution but oldschool and more readable:
l1 = [15, 17, 27, 28, 31, 41]
l2 = [8, 11, 19, 37, 40, 42]
comp = []
for i in l1:
c = []
for j in l2:
if i > j:
i += 1
c.append(j)
comp.append(c)
print(l1)
print(comp)
input >>
l1 = [15, 17, 27, 28, 31, 41]
l2 = [8, 11, 19, 37, 40, 42]
output >>
[15, 17, 27, 28, 31, 41]
[[8, 11], [8, 11], [8, 11, 19], [8, 11, 19], [8, 11, 19], [8, 11, 19, 37, 40, 42]]
One way to do this is to use numpy which allows quick operations on lists by putting them in matrix form and is more optimized than operations in lists. An example of code could be:
import numpy as np
list_1 = [8, 11, 19, 37, 40, 42]
list_2 = [15, 17, 27, 28, 31, 41]
arr_1, arr_2 = np.array(list_1), np.array(list_2)
# Broadcast to 2D array where elem (i, j) is whether list_2[i] < list_1[j] or not
arr_sup = (arr_2[:, None] - arr_1[None, :]) > 0
# Add sum of rows to list_2 to create the new list
new_list = (np.sum(arr_sup, axis=1) + list_2).tolist()
# Get indexes (i, j) where arr_sup[i, j] is True
idx_sup = np.where(arr_sup)
values = []
for i, j in zip(idx_sup[0], idx_sup[1]): # browse i and j together
if len(values) < i + 1:
# Add a new list for each new i
values.append([])
# Add value j of list 1 to i-th list in values
values[i].append(list_1[j])
print(new_list) # [17, 19, 30, 31, 34, 46]
print(values) # [[8, 11], [8, 11], [8, 11, 19], [8, 11, 19], [8, 11, 19], [8, 11, 19, 37, 40]]
It works even if the lists are not sorted.

NumPy Array Fill Rows Downward By Indexed Sections

Let's say I have the following (fictitious) NumPy array:
arr = np.array(
[[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
[13, 14, 15, 16],
[17, 18, 19, 20],
[21, 22, 23, 24],
[25, 26, 27, 28],
[29, 30, 31, 32],
[33, 34, 35, 36],
[37, 38, 39, 40]
]
)
And for row indices idx = [0, 2, 3, 5, 8, 9] I'd like to repeat the values in each row downward until it reaches the next row index:
np.array(
[[1, 2, 3, 4],
[1, 2, 3, 4],
[9, 10, 11, 12],
[13, 14, 15, 16],
[13, 14, 15, 16],
[21, 22, 23, 24],
[21, 22, 23, 24],
[21, 22, 23, 24],
[33, 34, 35, 36],
[37, 38, 39, 40]
]
)
Note that idx will always be sorted and have no repeat values. While I can accomplish this by doing something like:
for start, stop in zip(idx[:-1], idx[1:]):
for i in range(start, stop):
arr[i] = arr[start]
# Handle last index in `idx`
start, stop = idx[-1], arr.shape[0]
for i in range(start, stop):
arr[i] = arr[start]
Unfortunately, I have many, many arrays like this and this can become slow as the size of the array gets larger (in both the number of rows as well as the number of columns) and the length of idx also increases. The final goal is to plot these as a heatmaps in matplotlib, which I already know how to do. Another approach that I tried was using np.tile:
for start, stop in zip(idx[:-1], idx[1:]):
reps = max(0, stop - start)
arr[start:stop] = np.tile(arr[start], (reps, 1))
# Handle last index in `idx`
start, stop = idx[-1], arr.shape[0]
arr[start:stop] = np.tile(arr[start], (reps, 1))
But I am hoping that there's a way to get rid of the slow for-loop.
Try np.diff to find the repetition for each row, then np.repeat:
# this assumes `idx` is a standard list as in the question
np.repeat(arr[idx], np.diff(idx+[len(arr)]), axis=0)
Output:
array([[ 1, 2, 3, 4],
[ 1, 2, 3, 4],
[ 9, 10, 11, 12],
[13, 14, 15, 16],
[13, 14, 15, 16],
[21, 22, 23, 24],
[21, 22, 23, 24],
[21, 22, 23, 24],
[33, 34, 35, 36],
[37, 38, 39, 40]])

Generating a hierarchy from a Python list

I am using Python 3.8.
The following list contains integers and I need to generate a hierarchy from this list:
list1 = [[15, 1], [22, 1], [23, 1], [121, 15], [101, 22], [105, 23], [106, 23], [108, 23], [155, 121], [120, 108], [19, 2], [25, 5], [33, 8], [35, 8], [28, 25], [29, 28]]
I need this result (output could be a list, e.g. [[[1, 15, 22, 23], [15, 121], [121, 155], [22, 101], [23, 105, 106, 108], [108, 120]], [2, 19], [[5, 25], [25, 28], [28, 29]], [8, 33, 35]]):
1 ---- 15 ---- 121 ---- 155
\---- 22 ---- 101
\--- 23 ---- 105
\----- 106
\---- 108 ---- 120
2 ---- 19
5 ---- 25 ---- 28 ----- 29
8 ---- 33
\---- 35
The hierarchy does not contain any duplicated items. Also first items of the lists in the list1 do not contain repeated/duplicated elements, but second items of the lists in the list1 contain.
How could this hierarchy be generated?
Note: I could do this by using some code but it could be very long and CPU cost could be high (actual list is very long).
You can try using this recursive function, it is a bit verbose and can be re written using list comprehensions. need is your expected output.
list1 = [[15, 1], [22, 1], [23, 1], [121, 15], [101, 22], [105, 23], [106, 23], [108, 23], [155, 121], [120, 108], [19, 2], [25, 5], [33, 8], [35, 8], [28, 25], [29, 28]]
need = [[[1, 15, 22, 23], [15, 121], [121, 155], [22, 101], [23, 105, 106, 108], [108, 120]], [2, 19], [[5, 25], [25, 28], [28, 29]], [8, 33, 35]]
graph = {}
for y, x in list1:
graph.setdefault(x, []).append(y)
def form_graph(graph):
seen = set()
def form(k, v):
if k in seen:
return []
res = [[k]]
res[-1].extend(v)
seen.add(k)
for i in v:
if i in graph:
res.extend(form(i, graph[i]))
return res
result = []
for k, v in graph.items():
temp = form(k, v)
if temp:
if len(temp) == 1:
temp = temp[0]
result.append(temp)
return result
Output
print(form_graph(graph))
[[[1, 15, 22, 23], [15, 121], [121, 155], [22, 101], [23, 105, 106, 108], [108, 120]], [2, 19], [[5, 25], [25, 28], [28, 29]], [8, 33, 35]]
print(need == form_graph(graph))
True
Upvote and accept the answer if you find this useful.
You can use a breadth-first search:
from collections import deque, defaultdict
list1 = [[15, 1], [22, 1], [23, 1], [121, 15], [101, 22], [105, 23], [106, 23], [108, 23], [155, 121], [120, 108], [19, 2], [25, 5], [33, 8], [35, 8], [28, 25], [29, 28]]
def get_levels():
q, r, d = deque(sorted({(b, b) for a, b in list1 if all(k != b for k, _ in list1)})), [], defaultdict(list)
while q:
r.append(((n:=q.popleft())[0], [n[-1], *(l:=[a for a, b in list1 if b == n[-1]])]))
q.extend([(n[0], i) for i in l])
for a, b in r:
if len(b) > 1:
d[a].append(b)
return [i for b in d.values() for i in ([b] if len(b) > 1 else b)]
print(get_levels())
Output:
[[[1, 15, 22, 23], [15, 121], [22, 101], [23, 105, 106, 108], [121, 155], [108, 120]], [2, 19], [[5, 25], [25, 28], [28, 29]], [8, 33, 35]]

Numpy select elements with a condition along axis

I have a 2D numpy array x as:
[ [ 1, 2, 3],
[ 4, 5, 6],
[ 7, 8, 9],
[10, 11, 12],
[13, 14, 15],
[16, 17, 18],
[19, 20, 21],
[22, 23, 24],
[25, 26, 27],
[28, 29, 30],
[31, 32, 33],
[34, 35, 36],
[37, 38, 39],
[40, 41, 42],
[43, 44, 45],
[46, 47, 48],
[49, 50, 51],
[52, 53, 54],
[55, 56, 57],
[58, 59, 60] ]
I want to extract the arguments of rows for which any element in the row is less than 25. So, what I need for output is [0,1,2,3,4,5,6,7] for just the rows but using np.where(x<35) is giving me the list of 2D arguments for all possible values. In other words, I what I want are the arguments of all the rows of x where at least one element is less than 25, but what I am getting are the arguments of all the elements of x that are less than 25.
What should I do? Is there a specific function for this or should I just select the unique values from the returned list of arguments?
One way would be this:
import numpy as np
# x is your array
x1 = (x < 25).sum(axis = 1)
rows = np.where(x1 > 0)[0]
The row indices are in rows as array([0, 1, 2, 3, 4, 5, 6, 7]).
You can also use nonzero as:
rows = np.nonzero((x < 25).sum(axis = 1))[0]

Find sequences in list of ranges

I have a random length list that contains ranges info:
list = [
[[7, 12], [6, 12], [38, 44], [25, 30], [25, 29]],
[[0, 5], [1, 5], [2, 5], [12, 16], [13, 16], [20, 23], [29, 33], [30, 33]],
[[5, 7], [6, 8], [7, 9], [8, 10], [9, 11], [10, 12], [16, 18], [17, 19], [18, 20], [23, 25], [24, 26], [25, 27], [26, 28], [27, 29], [33, 35], [34, 36], [35, 37], [36, 38], [37, 39], [38, 40], [39, 41], [40, 42], [41, 43], [42, 44]]
]
For example, first element [[7, 12], [6, 12], [38, 44], [25, 30]] contains 4 ranges 7-12, 6-12, 38-44 and 25-30 etc.
I need to find the all possible chains (a chain is an array of consecutive ranges where ending of first range == beginning of next range) of length given list length, given that I could and should take only one range from each row in the exact order of rows.
So, for this example list:
The chains would be
[[6, 12], [12, 16], [16, 18]],
[[7, 12], [12, 16], [16, 18]],
[[25, 30], [30, 33], [33, 35]]
and [[25, 29], [29, 33], [33, 35]]
Right now I am stuck on working with more than three length list, could not come up with recursive solution.
You can use itertools.product to iterate over all possible chains (all combinations of 1 range from each "row"),
then filter them by a simple function that checks if a specific chain is legal.
try this:
from itertools import product
def check_chain(chain):
prev_end = chain[0][1]
for start, end in chain[1:]:
if start != prev_end:
return False
prev_end = end
return True
all_candidate_chains = product(*list)
result = [[*chain] for chain in all_candidate_chains if check_chain(chain)]
print(result)
Output:
[[[7, 12], [12, 16], [16, 18]], [[6, 12], [12, 16], [16, 18]], [[25, 30], [30, 33], [33, 35]]]
EDIT:
can also use zip and all to replace check_chain with a 1-liner:
from itertools import product
result = [[*chain] for chain in product(*list) if all(end1 == start2 for (_, end1), (start2, _) in zip(chain, chain[1:]))]
print(result)
You can do this without looking at all the permutation. Start at with the last item and make a dictionary where the keys are the first value in the dictionary. Then work backward through the list and lookup the previous key based on the second value of the tuple adding to the array as you go:
In the end you'll have a dictionary keyed to the first value in the tuples of the first list. You can just flatten the values at this point.
Here I added one more pair [12,9] to the middle list so I could see it work with branching paths:
from collections import defaultdict
from itertools import chain
l = [
[[7, 12], [6, 12], [38, 44], [25, 30]],
[[0, 5], [1, 5], [2, 5], [12, 16], [12, 9],[13, 16], [20, 23], [29, 33], [30, 33]],
[[5, 7], [6, 8], [7, 9], [8, 10], [9, 11], [10, 12], [16, 18], [17, 19], [18, 20], [23, 25], [24, 26], [25, 27], [26, 28], [27, 29], [33, 35], [34, 36], [35, 37], [36, 38], [37, 39], [38, 40], [39, 41], [40, 42], [41, 43], [42, 44]]
]
d = defaultdict(list)
for k, v in l[-1]:
d[k].append([[k,v]])
for sub in reversed(l[:-1]):
ds = defaultdict(list)
for k, v in sub:
if v in d:
ds[k].extend([[k,v], *v2] for v2 in d[v] )
d = ds
list(chain.from_iterable(d.values()))
Output:
[[[7, 12], [12, 16], [16, 18]],
[[7, 12], [12, 9], [9, 11]],
[[6, 12], [12, 16], [16, 18]],
[[6, 12], [12, 9], [9, 11]],
[[25, 30], [30, 33], [33, 35]]]

Categories