substitute elements in the nested list by their counting numbers - python

I have a nested list:
x=[[[0, 1, 2], [0, 1, 2]], [[0, 1], [0, 1, 2]], [[0]], [[0, 1], [0, 1, 2, 3, 4]]]
Now, the goal is to get a nested list with the same structure but with elements replaced by their "global" counting numbers. So, the desired output should look like this:
y=[[[0, 1, 2], [3, 4, 5]], [[6, 7], [8, 9, 10]], [[11]], [[12, 13], [14, 15, 16, 17, 18]]]
I fight with it for the last couple of hours but without success.
Ideally, I'd like to have a universal solution being able to work with an arbitrary depth of nesting.
Any help would be very much appreciated. Thank you in advance!

Here's a recursive solution that does the replacement in-place and relies on the type of the element being replaced. The idea is to keep track of the "global counter" and pass it into the recursive calls so that it knows what to replace elements with:
x = [[[0, 1, 2], [0, 1, 2]], [[0, 1], [0, 1, 2]], [[0]], [[0, 1], [0, 1, 2, 3, 4]]]
def replace(lst, i):
for j in range(len(lst)):
if isinstance(lst[j], list):
lst[j], i = replace(lst[j], i)
else:
lst[j] = i
i += 1
return lst, i - 1
replace(x, 0)
print(x)
# [[[0, 1, 2], [3, 4, 5]], [[6, 7], [8, 9, 10]], [[11]], [[12, 13], [14, 15, 16, 17, 18]]]

Here's another recursive solution. It uses itertools.count and builds a new list. Personally, I like to avoid integer indexing when possible for readability.
from itertools import count
def structured_enumerate(lst, counter=None):
'enumerate elements in nested list, preserve structure'
result = []
if counter is None:
counter = count()
for x in lst:
if isinstance(x, list):
result.append(structured_enumerate(x, counter))
else:
result.append(next(counter))
return result
Demo:
>>> x = [[[0, 1, 2], [0, 1, 2]], [[0, 1], [0, 1, 2]], [[0]], [[0, 1], [0, 1, 2, 3, 4]]]
>>> structured_enumerate(x)
[[[0, 1, 2], [3, 4, 5]],
[[6, 7], [8, 9, 10]],
[[11]],
[[12, 13], [14, 15, 16, 17, 18]]]
~edit~
Here's an attempt at a generic solution that works with any iterable, indexable or not, where you can specifiy iterable types to exclude from iteration.
from itertools import count
def structured_enumerate(iterable, dontiter=(str,), counter=None):
'enumerate elements in nested iterable, preserve structure'
result = []
if counter is None:
counter = count()
for x in iterable:
# check if x should be iterated
try:
iter(x)
is_iterable = True
except TypeError:
is_iterable = False
# strings of length zero and one are a special case
if isinstance(x, str) and len(x) < 2:
is_iterable = False
if is_iterable and not isinstance(x, dontiter):
subresult = structured_enumerate(x, dontiter, counter)
result.append(subresult)
else:
result.append(next(counter))
return result
Demo:
>>> fuzzy = [{0, 0}, '000', [0, [0, 0]], (0,0), 0]
>>> structured_enumerate(fuzzy)
[[0, 1], 2, [3, [4, 5]], [6, 7], 8]
>>> structured_enumerate(fuzzy, dontiter=())
[[0, 1], [2, 3, 4], [5, [6, 7]], [8, 9], 10]
>>> structured_enumerate(fuzzy, dontiter=(tuple, set))
[0, [1, 2, 3], [4, [5, 6]], 7, 8]

Related

How to sort liberally-deep list of lists and integers?

Let's say I have a list like that:
[[5, [2, 1], 3], [3, [8, 7], [2, 1, 3], 2], [[3, 2, 1], 3, -1]]
(each list can store unspecified number of integers and lists in any order).
Rules for sorting:
integers (if any) should be before any list
integers are sorted normally (from lower to higher value)
list is "smaller" than another when the first element is lower, if the same we consider the second element, the third, and so on.
If the list has the same values as another but less, it's smaller. [1, 2] < [1, 2, 3]
So the final result should be:
[[-1, 3, [1, 2, 3]], [2, 3, [1, 2, 3], [7, 8]], [3, 5, [1, 2]], ]
How to implement that kind of sorting? I've tried to pass recursive function as sort key in sorted, but mostly ended with TypeError (< not supported for int and list).
Another example:
[1, 4, 3, [[5, 7, 1, 2], 2, 5, 10, 2], 8, [2, 5, [5, 3, 3]], [2, 5, 10, 2, [1, 2, 5]]]
After sorting :
[1, 3, 4, 8, [2, 2, 5, 10, [1, 2, 5]], [2, 2, 5, 10, [1, 2, 5, 7]], [2, 5, [3, 3, 5]]]
One way to compare sibling structures (i.e., deeply nested values inside the same list) correctly, safely and efficiently, is to decorate everything and keep it decorated until all the sorting is done. Then undecorate everything at the end. This doesn't modify the given structure but builds a sorted new one:
def deepsorted(x):
def decosorted(x):
if isinstance(x, int):
return 0, x
return 1, sorted(map(decosorted, x))
def undecorated(x):
islist, x = x
if islist:
return list(map(undecorated, x))
return x
return undecorated(decosorted(x))
Another way is to not rely on natural comparisons but use an old-style cmp function instead. This sorts in-place:
from functools import cmp_to_key
def deepsort(x):
def cmp(a, b):
if isinstance(a, int):
if isinstance(b, int):
return a - b
return -1
if isinstance(b, int):
return 1
diffs = filter(None, map(cmp, a, b))
return next(diffs, len(a) - len(b))
key = cmp_to_key(cmp)
def sort(x):
if isinstance(x, list):
for y in x:
sort(y)
x.sort(key=key)
sort(x)
Testing code (Try it online!):
from copy import deepcopy
tests = [
([[5, [2, 1], 3], [3, [8, 7], [2, 1, 3], 2], [[3, 2, 1], 3, -1]],
[[-1, 3, [1, 2, 3]], [2, 3, [1, 2, 3], [7, 8]], [3, 5, [1, 2]], ]),
([1, 4, 3, [[5, 7, 1, 2], 2, 5, 10, 2], 8, [2, 5, [5, 3, 3]], [2, 5, 10, 2, [1, 2, 5]]],
[1, 3, 4, 8, [2, 2, 5, 10, [1, 2, 5]], [2, 2, 5, 10, [1, 2, 5, 7]], [2, 5, [3, 3, 5]]]),
([[1, 2], [1, [2]]],
[[1, 2], [1, [2]]]),
([[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]],
[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]),
([[[1, 2]], [[1, [2]]]],
[[[1, 2]], [[1, [2]]]]),
([[[1, [2]]], [[1, 2]]],
[[[1, 2]], [[1, [2]]]]),
([[1, 3], [1, 2]],
[[1, 2], [1, 3]]),
]
for given, wanted in tests:
copy = deepcopy(given)
deepsort(copy)
print(deepsorted(given) == wanted,
copy == wanted)
Quick draft:
def big_brain_sort(li: list) -> list:
list_with_int = []
list_with_list = []
for el in li:
if isinstance(el, int):
list_with_int.append(el)
elif isinstance(el, list):
list_with_list.append(big_brain_sort(el))
sorted_list_int = sorted(list_with_int)
for lista in sorted(list_with_list, key=lambda x: (big_brain_sort(x), len(x))):
sorted_list_int.append(lista)
return sorted_list_int

List comprehension: muti-dimension items for each loop

I have a function (test_func) and I want to create a list:
def test_func(i):
return [[i, i+1], [i**2, i**3]]
output=[test_func(i) for i in range(5)]
The results are as follows:
[[[0, 1], [0, 0]],
[[1, 2], [1, 1]],
[[2, 3], [4, 8]],
[[3, 4], [9, 27]],
[[4, 5], [16, 64]]]
How to get the following results:
[[0, 1], [0, 0], [1, 2], [1, 1], [2, 3], [4, 8], [3, 4], [9, 27], [4, 5], [16, 64]]
Sincerely thank you,
Try this:
output=[v for i in range(5) for v in test_func(i)]
This produces:
[[0, 1], [0, 0], [1, 2], [1, 1], [2, 3], [4, 8], [3, 4], [9, 27], [4, 5], [16, 64]]
The second argument for the sum function is what you sum across - so in this case providing [], the sum is performed across lists (extending them, basically). This operation corresponds to flattening the list.
def test_func(i):
return [[i, i+1], [i**2, i**3]]
output = sum([test_func(i) for i in range(5)], [])
Without a function you could use itertools.chain.from_iterable
from itertools import chain
list(chain.from_iterable((([i, i+1], [i**2, i**3]) for i in range(5))))
which gives:
[[0, 1],
[0, 0],
[1, 2],
[1, 1],
[2, 3],
[4, 8],
[3, 4],
[9, 27],
[4, 5],
[16, 64]]
flat_out = [item for sublist in output for item in sublist]
[[0, 1],
[0, 0],
[1, 2],
[1, 1],
[2, 3],
[4, 8],
[3, 4],
[9, 27],
[4, 5],
[16, 64]]
or
def test_func(i):
return [[i, i+1], [i**2, i**3]]
output= [k for i in range(5) for k in test_func(i) ]
output

recursion on each element of array

I have an integer S and a collection A of integers. I need to find a set of integers from the collection where the sum of those integers is equal to S. It could be 1 integer or 50 - it doesn't matter.
I'm trying to do this like this:
I have an array res and an array grp
res starts with [0], grp is the initially given collection, and S is the sum we're trying to find, S is global
my function takes (res, grp)
I want to do this : (and example)
-
and stop when the sum of the res elements is equal to S
but I suck with recursion and I have no idea what I should be doing
this is my code
S = 7
grp = [0,5,6,4,3]
def sumFinder(res,grp):
if grp == []:
return grp #in case no pair was found [] is returned
if sum(res) == S:
return res
for i in range (0,len(grp)):
print(res)
print(grp[i])
res += [grp[i]]
newgrp = grp[:i]
newgrp += grp[i+1:]
return sumFinder(res,newgrp)
print(sumFinder([0],grp))
UPDATE:
thank you everyone for you answers
thank you englealuze for giving me a beter idea about approaching the problem thanks o you i got to this:
1 - this is for finding the first combination and returning it (this was my goal)
grp = [1,0,1,0,1,2,6,2,3,5,6,7,8,9,2,1,1,9,6,7,4,1,2,3,2,2]
S = 55
grps = []
def findCombination(comb,grp):
for i in range (0,len(grp)):
comb += [grp[i]]
newgrp = grp[:i]
newgrp += grp[i+1:]
if sum(comb) == S:
return comb
if newgrp not in grps:
grps.append(newgrp)
res = findCombination([],newgrp)
if res != None:
return res
print(findCombination([],grp))
2- this is for finding all the combinations: (this is the problem englealuze talked about, but i didn't understand his method that well even though it seems better)
grp = [1,0,1,1,9,6,7,4,1,2,3,2,2]
S = 12
grps = []
combinations = []
def findAllCombinations(comb,grp):
global combinations
for i in range (0,len(grp)):
comb += [grp[i]]
newgrp = grp
newgrp = grp[:i]
newgrp += grp[i+1:]
if sum(comb) == S and tuple(comb) not in combinations:
combinations.append(tuple(comb))
if newgrp not in grps:
grps.append(newgrp)
findAllCombinations([],newgrp)
findAllCombinations([],grp)
print(combinations)
my only problem now is that when S > 50 (in the first one), it takes longer to find the answer
what advice could you guys give me to improve both algorithms?
In stead of just providing code, I will show you how to think about this problem and how to tackle this type of problems in a general sense.
First, let us rephrase your question. In fact what you want is for a given set of numbers, find the combinations within the set which fulfill certain condition. So, you can decompose your question into 2 distinct steps.
Find all combinations of your set
Filter out the combinations that fulfill certain conditions
Let us think about how to solve the first task recursively. Remember, if a problem can be solved in a recursive way, it generally means there are some recursive patterns within your data and it usually can be solved in a very simple and clear way. If you end up with a messy recursive solution, it pretty much means you are in a wrong direction.
Let us see the pattern of your data first. If you have a very small set (1, 2), then the combinations out of this set are
1
2
1, 2
Let us increase one member to the set, (1, 2, 3). For this bigger set, all combiantions are
1 | 1, 3
2 | 2, 3
1, 2 | 1, 2, 3
| 3
Let us look at even bigger set (1, 2, 3, 4). The possible combinations are
1 1, 3 | 1, 3, 4
2 2, 3 | 2, 3, 4
1, 2 1, 2, 3 | 1, 2, 3, 4
3 | 3, 4
| 4
Now you see something interesting, the combinations of one bigger set is the combinations of the smaller set plus the additional element appending to every previous combination and plus the additional element itself.
Assume you already got the solution of all combinations of set with certain size, the solution of a bigger set can be derived from this solution. This naturally forms a recursion. You can translate this plain English directly to a recursive code as below
# assume you have got all combinations of a smaller set -> combinations(smaller(L))
# the solution of your bigger set can be directly derived from it with known new element
def combinations(L):
if L == []:
return []
else:
return next_solution(combinations(smaller(L)), newitem(L))
Notice how we decompose out task of solving a larger problem to solving smaller problems. You need the helper functions as below
# in your case a smaller set is just the new set drop one element
def smaller(L):
return L[1:]
# the new element would be the first element of new set
define newitem(L):
return L[0]
# to derive the new solution from previous combinations, you need three parts
# 1. all the previous combinations -> L
# 2. new item appending to each previous combination -> [i + [newelement] for i in L]
# 3. the new item itself [[newelement]]
def next_solution(L, newelement):
return L + [i + [newelement] for i in L] + [[newelement]]
Now we know how to get all combinations out of a set.
Then to filter them, you cannot just insert the filter directly into your recursive steps, since we rely on our previous solution to build up the result list recursively. The simple way is to filter the list while we obtain the full result of all combinations.
You will end up with a solution as this.
def smaller(L):
return L[1:]
def newitem(L):
return L[0]
def next_solution(L, newelement):
return L + [i + [newelement] for i in L] + [[newelement]]
def filtersum(L, N, f=sum):
return list(filter(lambda x: f(x)==N, L))
def combinations(L):
if L == []:
return []
else:
return next_solution(combinations(smaller(L)), newitem(L))
def filter_combinations(L, N, f=filtersum):
return f(combinations(L), N)
print(filter_combinations([0,5,6,4,3], 7))
# -> [[3, 4], [3, 4, 0]]
You can save some computations by filter out in each recursive call the combinations with sum bigger than your defined value, such as
def combinations(L):
if L == []:
return []
else:
return next_solution(list(filter(lambda x: sum(x) <=5, combinations(smaller(L)))), newitem(L))
print(combinations([1,2,3,4]))
# -> [[4], [3], [3, 2], [2], [4, 1], [3, 1], [3, 2, 1], [2, 1], [1]]
In fact there will be different ways to do recursion, depends on the way how you decompose your problem to smaller problems. There existed some smarter ways, but the approach I showed above is a typical and general approach for solving this type of problems.
I have example of solving other problems with this way
Python: combinations of map tuples
Below codes work (remove 'directly return' in the loop, changed to conditional 'return').
But I don't think it is a good solution. You need to improve your algorithm.
PS: This codes also will only return one match instead of all.
S = 7
grp = [0,3,6,4,6]
result = []
def sumFinder(res,grp, result):
for i in range (0,len(grp)):
temp = list(res) #clone res instead of direct-reference
if grp == [] or not temp:
return grp #in case no pair was found [] is returned
if sum(temp) == S:
result.append(tuple(temp))
return temp
temp.append(grp[i])
newgrp = grp[:i] + grp[i+1:]
sumFinder(list(temp),newgrp, result)
sumFinder([0], grp, result)
print result
Test Cases:
S = 7
grp = [0,3,6,4,6]
result = [(0, 0, 3, 4), (0, 0, 4, 3), (0, 3, 0, 4), (0, 3, 4), (0, 4, 0, 3), (0, 4, 3)]
[Finished in 0.823s]
Can you let me know where did you find this problem? I love to solve this type of problem, Bdw here is my approach:
a=[[[0],[0,5,6,4,3]]]
s=7
def recursive_approach(array_a):
print(array_a)
sub=[]
for mm in array_a:
array_1 = mm[0]
array_2 = mm[1]
if sum(array_2)==s:
return "result is",array_2
else:
track = []
for i in range(len(array_2)):
c = array_2[:]
del c[i]
track.append([array_1 + [array_2[i]], c])
sub.append(track)
print(sub)
return recursive_approach(sub[0])
print(recursive_approach(a))
output:
[[[0], [0, 5, 6, 4, 3]]]
[[[[0, 0], [5, 6, 4, 3]], [[0, 5], [0, 6, 4, 3]], [[0, 6], [0, 5, 4, 3]], [[0, 4], [0, 5, 6, 3]], [[0, 3], [0, 5, 6, 4]]]]
[[[0, 0], [5, 6, 4, 3]], [[0, 5], [0, 6, 4, 3]], [[0, 6], [0, 5, 4, 3]], [[0, 4], [0, 5, 6, 3]], [[0, 3], [0, 5, 6, 4]]]
[[[[0, 0, 5], [6, 4, 3]], [[0, 0, 6], [5, 4, 3]], [[0, 0, 4], [5, 6, 3]], [[0, 0, 3], [5, 6, 4]]], [[[0, 5, 0], [6, 4, 3]], [[0, 5, 6], [0, 4, 3]], [[0, 5, 4], [0, 6, 3]], [[0, 5, 3], [0, 6, 4]]], [[[0, 6, 0], [5, 4, 3]], [[0, 6, 5], [0, 4, 3]], [[0, 6, 4], [0, 5, 3]], [[0, 6, 3], [0, 5, 4]]], [[[0, 4, 0], [5, 6, 3]], [[0, 4, 5], [0, 6, 3]], [[0, 4, 6], [0, 5, 3]], [[0, 4, 3], [0, 5, 6]]], [[[0, 3, 0], [5, 6, 4]], [[0, 3, 5], [0, 6, 4]], [[0, 3, 6], [0, 5, 4]], [[0, 3, 4], [0, 5, 6]]]]
[[[0, 0, 5], [6, 4, 3]], [[0, 0, 6], [5, 4, 3]], [[0, 0, 4], [5, 6, 3]], [[0, 0, 3], [5, 6, 4]]]
[[[[0, 0, 5, 6], [4, 3]], [[0, 0, 5, 4], [6, 3]], [[0, 0, 5, 3], [6, 4]]], [[[0, 0, 6, 5], [4, 3]], [[0, 0, 6, 4], [5, 3]], [[0, 0, 6, 3], [5, 4]]], [[[0, 0, 4, 5], [6, 3]], [[0, 0, 4, 6], [5, 3]], [[0, 0, 4, 3], [5, 6]]], [[[0, 0, 3, 5], [6, 4]], [[0, 0, 3, 6], [5, 4]], [[0, 0, 3, 4], [5, 6]]]]
[[[0, 0, 5, 6], [4, 3]], [[0, 0, 5, 4], [6, 3]], [[0, 0, 5, 3], [6, 4]]]
('result is', [4, 3])

How to get list of indexes of all occurences of the same value in list of lists?

Having a list of lists,
mylist = [[1, 3, 4], [3, 6, 7], [8, 0, -1, 3]]
I need a method to get list containing indexes of all elements of some certain value, '3' for example. So for value of '3' such list of indexes should be
[[0, 1], [1, 0], [2, 3]]
Thanks in advance.
Update: we can have several instances of sought-for value ('3' in our case) in same sublist, like
my_list = [[1, 3, 4], [3, 6, 7], [8, 0, -1, 3, 3]]
So desired output will be [[0, 1], [1, 0], [2, 3], [2, 4]].
I suppose that my solution is slightly naive, here it is:
my_list = [[1, 3, 4], [3, 6, 7], [8, 0, -1, 3, 3]]
value = 3
list_of_indexes = []
for i in range(len(my_list)):
for j in range(len(my_list[i])):
if my_list[i][j] == value:
index = i, j
list_of_indexes.append(index)
print list_of_indexes
>>[(0, 1), (1, 0), (2, 3), (2, 4)]]
It will be great to see more compact solution
Assuming the value appears once in each sublist, you could use the following function, which makes use of the built in enumerate function and a list comprehension:
my_list = [[1, 3, 4], [3, 6, 7], [8, 0, -1, 3]]
def func(my_list, x):
return [[idx, sublist.index(x)] for idx, sublist in enumerate(my_list)]
answer = func(my_list, 3)
print(answer)
Output
[[0, 1], [1, 0], [2, 3]]
Easy way,
def get_val(mylist, val):
for ix, item in enumerate(mylist):
yield [ix, item.index(val)]
mylist = [[1, 3, 4], [3, 6, 7], [8, 0, -1, 3]]
val = list(get_val(mylist, 3))
print(val)
Output:
[[0, 1], [1, 0], [2, 3]]

Number list with no repeats and ordered

This code returns a list [0,0,0] to [9,9,9], which produces no repeats and each element is in order from smallest to largest.
def number_list():
b=[]
for position1 in range(10):
for position2 in range(10):
for position3 in range(10):
if position1<=position2 and position2<=position3:
b.append([position1, position2, position3])
return b
Looking for a shorter and better way to write this code without using multiple variables (position1, position2, position3), instead only using one variable i.
Here is my attempt at modifying the code, but I'm stuck at implementing the if statements:
def number_list():
b=[]
for i in range(1000):
b.append(map(int, str(i).zfill(3)))
return b
On the same note as the other itertools answer, there is another way with combinations_with_replacement:
list(itertools.combinations_with_replacement(range(10), 3))
Simply use list comprehension, one way to do it:
>>> [[x,y,z] for x in range(10) for y in range(10) for z in range(10) if x<=y and y<=z]
[[0, 0, 0], [0, 0, 1], [0, 0, 2], [0, 0, 3], [0, 0, 4], [0, 0, 5], [0, 0, 6],
[0, 0, 7], [0, 0, 8], [0, 0, 9], [0, 1, 1], [0, 1, 2], [0, 1, 3], [0, 1, 4], [0, 1, 5], [0, 1, 6], [0, 1, 7], [0, 1, 8], [0, 1, 9], [0, 2, 2], [0, 2, 3],
[0, 2, 4], [0, 2, 5], [0, 2, 6], [0, 2, 7], [0, 2, 8], [0, 2, 9], [0, 3, 3],
[0, 3, 4], [0, 3, 5], [0, 3, 6], [0, 3, 7], [0, 3, 8],....[6, 8, 8], [6, 8, 9],
[6, 9, 9], [7, 7, 7], [7, 7, 8], [7, 7, 9], [7, 8, 8], [7, 8, 9], [7, 9, 9],
[8, 8, 8], [8, 8, 9], [8, 9, 9], [9, 9, 9]]
Here's a simpler way than doing the checks, but which is still IMO worse than combinations_with_replacement:
[(a, b, c) for a in range(10)
for b in range(a, 10)
for c in range(b, 10)]
Namely, instead of filtering values after production you just only produce those values you want in the first place.
You can use itertools.product() to eliminate nested loops:
>>> filter(lambda i: i[0] <= i[1] <= i[2],
... itertools.product(range(10), range(10), range(10)))
Or better with list comprehensions:
>>> numbers = itertools.product(range(10), range(10), range(10))
>>> [(a, b, c) for a, b, c in numbers if a <= b <= c]
I think it is worthwhile to point out that the original code is weird and can be rewritten easily to be simpler:
def number_list2():
b=[]
for position1 in range(10):
for position2 in range(position1, 10):
for position3 in range(position2, 10):
if position1<=position2 and position2<=position3:
b.append([position1, position2, position3])
return b
There are better solutions here, but this one is the stepping stone to getting to them.
This code could be done pretty easily with recursion, without using itertools.
n - being the length of the tuple
m - being the upper bound of each value
The Code:
def non_decreasing(n, m):
if n==0:
return []
if n==1:
return [[i] for i in range(1,m+1)]
return [[i] + t for t in non_decreasing(n-1, m) for i in range(1,t[0]+1)]
The result is the output of non_decreasing(3,9)

Categories

Resources