I have a nested list
x = [['a', 'b', 'c'], ['d'], ['e', 'f', ['g', ['h', 'i']]]]
I want to do all possible permutations of elements in sublists without going beyond corresponding sublist.
The expected output are variations of something like this:
[['c', 'b', 'a'], ['d'], ['f', 'e', ['g', ['i', 'h']]]]
[['d'], ['a', 'b', 'c'], ['f', 'e', [['h', 'i'], 'g']]]
Each element must be kept is kept in it's square bracket.
I Worte this generator:
def swap(x):
if isinstance(x, list):
res = np.random.choice(x, len(x), replace = False)
return [list(map(ff, res))]
else:
return x
It gives random variants of expected result, but i need to collect them all. How could I do it? Should I do:
my_list = []
for i in range(10000): # not necessary 10000, any huge number
my_list.append(ff(yy1))
And then apply unique function to my_list to select unique ones, or there is another option?
The isinstance()+itertools.permutations() is a good direction, just you need a product of them, and some tracking which permutation applies to what part of the tree(?) (I was thinking along generating all possible traversals of a tree):
import itertools
def plan(part,res):
if isinstance(part,list) and len(part)>1:
res.append(itertools.permutations(range(len(part))))
for elem in part:
plan(elem,res)
return res
def remix(part,p):
if isinstance(part,list) and len(part)>1:
coll=[0]*len(part)
for i in range(len(part)-1,-1,-1):
coll[i]=remix(part[i],p)
mix=p.pop()
return [coll[i] for i in mix]
else:
return part
def swap(t):
plans=itertools.product(*plan(t,[]))
for p in plans:
yield remix(t,list(p))
for r in swap([['a', 'b', 'c'], ['d'], ['e', 'f', ['g', ['h', 'i']]]]):
print(r)
plan() recursively finds all "real" lists (which have more than one element), and creates itertools.permutations() for them.
swap() calls plan(), and then combines the permutations into one single compound megapermutation using itertools.product()
remix() creates an actual object for a single megapermutation step. It is a bit complicated because I did not want to fight with tracking tree-position, instead remix() works backwards, going to the very last list, and swizzling it with the very last component of the current plan, removing it from the list.
It seems to work, though your example is a bit long, with simpler inputs it has manageable output:
for r in swap([['a', ['b', 'c']], ['d'], 'e']):
print(r)
[['a', ['b', 'c']], ['d'], 'e']
[['a', ['c', 'b']], ['d'], 'e']
[[['b', 'c'], 'a'], ['d'], 'e']
[[['c', 'b'], 'a'], ['d'], 'e']
[['a', ['b', 'c']], 'e', ['d']]
[['a', ['c', 'b']], 'e', ['d']]
[[['b', 'c'], 'a'], 'e', ['d']]
[[['c', 'b'], 'a'], 'e', ['d']]
[['d'], ['a', ['b', 'c']], 'e']
[['d'], ['a', ['c', 'b']], 'e']
[['d'], [['b', 'c'], 'a'], 'e']
[['d'], [['c', 'b'], 'a'], 'e']
[['d'], 'e', ['a', ['b', 'c']]]
[['d'], 'e', ['a', ['c', 'b']]]
[['d'], 'e', [['b', 'c'], 'a']]
[['d'], 'e', [['c', 'b'], 'a']]
['e', ['a', ['b', 'c']], ['d']]
['e', ['a', ['c', 'b']], ['d']]
['e', [['b', 'c'], 'a'], ['d']]
['e', [['c', 'b'], 'a'], ['d']]
['e', ['d'], ['a', ['b', 'c']]]
['e', ['d'], ['a', ['c', 'b']]]
['e', ['d'], [['b', 'c'], 'a']]
['e', ['d'], [['c', 'b'], 'a']]
24 permutations, as expected
Not particularly pythonic, but I would approach it by finding permutations of the indexes, as seen here:
from itertools import permutations
mylist= [[1], [1,2], [1,2,3]]
combinations = list(permutations([i for i in range(len(mylist))]))
print(combinations)
for item in combinations:
print([mylist[item[i]] for i in range(len(mylist))])
Output:
[(0, 1, 2), (0, 2, 1), (1, 0, 2), (1, 2, 0), (2, 0, 1), (2, 1, 0)]
[[1], [1, 2], [1, 2, 3]]
[[1], [1, 2, 3], [1, 2]]
[[1, 2], [1], [1, 2, 3]]
[[1, 2], [1, 2, 3], [1]]
[[1, 2, 3], [1], [1, 2]]
[[1, 2, 3], [1, 2], [1]]
Have you considered using itertools?
There are explicit combination and permutation tools available
From the docs:
itertools.permutations(iterable[, r])
Return successive r length
permutations of elements in the iterable.
If r is not specified or is None, then r defaults to the length of the
iterable and all possible full-length permutations are generated.
Permutations are emitted in lexicographic sort order. So, if the input
iterable is sorted, the permutation tuples will be produced in sorted
order.
Elements are treated as unique based on their position, not on their
value. So if the input elements are unique, there will be no repeat
values in each permutation.
itertools.combinations(iterable, r)
Return r length subsequences of elements from the input iterable.
Combinations are emitted in lexicographic sort order. So, if the input
iterable is sorted, the combination tuples will be produced in sorted
order.
Elements are treated as unique based on their position, not on their
value. So if the input elements are unique, there will be no repeat
values in each combination.
Related
I'm building an application in Python where I need to define the following sort of function:
generate_replacements(['a', 'b', ['c', ['e', 'f']]], 1)
The expected output is all possible versions of the input list where just one element has been replaced
[
[1, 'b', ['c', ['e', 'f']]],
['a', 1, ['c', ['e', 'f']]],
['a', 'b', 1],
['a', 'b', [1, ['e', 'f']]],
['a', 'b', ['c', 1]],
['a', 'b', ['c', [1, 'f']]],
['a', 'b', ['c', ['e', 1]]]
]
I can see that recursion is the way to go, but I'm really stuck figuring out how to even best start this.
You can generate the replacements from the list, then if you notice you are replacing a list, also pass that list back through the function recursively. This is made a bit simpler if you use a generator:
def generate_replacements(l, rep):
for i in range(len(l)):
yield l[0:i] + [rep] + l[i+1: ]
if isinstance(l[i], list):
yield from (l[0:i] + [rec] + l[i+1: ]
for rec in generate_replacements(l[i], rep))
list(generate_replacements(['a', 'b', ['c', ['e', 'f']]], 1))
This give:
[[1, 'b', ['c', ['e', 'f']]],
['a', 1, ['c', ['e', 'f']]],
['a', 'b', 1],
['a', 'b', [1, ['e', 'f']]],
['a', 'b', ['c', 1]],
['a', 'b', ['c', [1, 'f']]],
['a', 'b', ['c', ['e', 1]]]]
I want to filter a list of lists for duplicates. I consider two lists to be a duplicate of each other when they contain the same elements but not necessarily in the same order. So for example
[['A', 'B', 'C'], ['C', 'B', 'A'], ['D', 'B', 'A']]
should become
[['A', 'B', 'C'], ['D', 'B', 'A']]
since ['C', 'B', 'A'] is a duplicate of ['A', 'B', 'C'].
It does not matter which one of the duplicates gets removed, as long as the final list of lists does not contain any duplicates anymore. And all lists need to keep the order of there elements. So using set() may not be an option.
I found this related questions:
Determine if 2 lists have the same elements, regardless of order? ,
How to efficiently compare two unordered lists (not sets)?
But they only talk about how to compare two lists, not how too efficiently remove duplicates. I'm using python.
using dictionary comprehension
>>> data = [['A', 'B', 'C'], ['C', 'B', 'A'], ['D', 'B', 'A']]
>>> result = {tuple(sorted(i)): i for i in data}.values()
>>> result
dict_values([['C', 'B', 'A'], ['D', 'B', 'A']])
>>> list( result )
[['C', 'B', 'A'], ['D', 'B', 'A']]
You can use frozenset
>>> x = [['A', 'B', 'C'], ['C', 'B', 'A'], ['D', 'B', 'A']]
>>> [list(s) for s in set([frozenset(item) for item in x])]
[['A', 'B', 'D'], ['A', 'B', 'C']]
Or, with map:
>>> [list(s) for s in set(map(frozenset, x))]
[['A', 'B', 'D'], ['A', 'B', 'C']]
If you want to keep the order of elements:
data = [['A', 'B', 'C'], ['C', 'B', 'A'], ['D', 'B', 'A']]
seen = set()
result = []
for obj in data:
if frozenset(obj) not in seen:
result.append(obj)
seen.add(frozenset(obj))
Output:
[['A', 'B', 'C'], ['D', 'B', 'A']]
Do you want to keep the order of elements?
from itertools import groupby
data = [['A', 'B', 'C'], ['C', 'B', 'A'], ['D', 'B', 'A']]
print([k for k, _ in groupby(data, key=sorted)])
Output:
[['A', 'B', 'C'], ['A', 'B', 'D']]
In python you have to remember that you can't change existing data but you can somehow append / update data.
The simplest way is as follows:
dict = [['A', 'B', 'C'], ['C', 'B', 'A'], ['D', 'B', 'A']]
temp = []
for i in dict:
if sorted(i) in temp:
pass
else:
temp.append(i)
print(temp)
cheers, athrv
I have a list:
['A', 'B', 'C', ['D', ['E', 'F'], 'G'], 'H']
and I want to turn this into:
[['E', 'F'], ['D', 'G'], ['A', 'B', 'C', 'H']]
So basically I want the sublist on the deepest level of the list to come first in the new list and then counting down the level the remaining sublists.
This should work with any nested list.
If there are two sublists on the same level, then it doesn't really matter which one comes first.
['A', 'B', 'C', ['D', ['E', 'F'], 'G'], ['H', 'I', 'J']]
[['E', 'F'], ['D', 'G'], ['H', 'I', 'J'], ['A', 'B', 'C', 'H']] #this is fine
[['E', 'F'], ['H', 'I', 'J'], ['D', 'G'], ['A', 'B', 'C', 'H']] #this too
I thought of first using a function to determine on what level the deepest sublist is, but then again I don't know how to access items in a list based on their level or if that's even possible.
Been tinkering around this for far too long now and I think my head just gave up, hope someone can assist me with this problem!
You can use a recursive generator function:
def sort_depth(d, c = 0):
r = {0:[], 1:[]}
for i in d:
r[not isinstance(i, list)].append(i)
yield from [i for j in r[0] for i in sort_depth(j, c+1)]
yield (c, r[1])
def result(d):
return [b for _, b in sorted(sort_depth(d), key=lambda x:x[0], reverse=True) if b]
print(result(['A', 'B', 'C', ['D', ['E', 'F'], 'G'], 'H']))
print(result(['A', 'B', 'C', ['D', ['E', 'F'], 'G'], ['H', 'I', 'J']]))
print(result([[1, [2]], [3, [4]]]))
Output:
[['E', 'F'], ['D', 'G'], ['A', 'B', 'C', 'H']]
[['E', 'F'], ['D', 'G'], ['H', 'I', 'J'], ['A', 'B', 'C']]
[[2], [4], [1], [3]]
Here is a relatively straight-forward solution:
def sort_depth(d):
def dlists(obj, dep=0):
for x in filter(list.__instancecheck__, obj):
yield from dlists(x, dep-1)
yield [x for x in obj if not isinstance(x, list)], dep
return [x for x, y in sorted(dlists(d), key=lambda p: p[1])]
>>> [*sort_depth([[1, [2]], [3, [4]]])]
[[2], [4], [1], [3], []]
>>> [*sort_depth(['A', 'B', 'C', ['D', ['E', 'F'], 'G'], 'H'])]
[['E', 'F'], ['D', 'G'], ['A', 'B', 'C', 'H']]
The approach:
Collect all the sublists and annotate them with their (negative) nesting level, e.g. (['E', 'F'], -2)
Sort them by their nesting level
Extract the lists back from the sorted data
I have the following list: [['F', 'G', 'C'], ['S', 3, 7], ['C', 3, 'D']]
But I want to have: [['F', 'G', 'C'], ['S'], ['C', 'D']]
the elements in the list are all str objects. so basically this question is asking how can i get python to recognize a number even if it is cast as a string?
You need a nested list comprehension and isinstance():
>>> l = [['F', 'G', 'C'], ['S', 3, 7], ['C', 3, 'D']]
>>> [[item for item in sublist if not isinstance(item, int)] for sublist in l]
[['F', 'G', 'C'], ['S'], ['C', 'D']]
If you need to handle digits inside strings also, str.isdigit() would help:
>>> l = [['F', 'G', 'C'], ['S', '3', '7'], ['C', '3', 'D']]
>>> [[item for item in sublist if not item.isdigit()] for sublist in l]
[['F', 'G', 'C'], ['S'], ['C', 'D']]
I have a dictionary that looks like this:
my_dict = {(1,0): ['A', 'B'],
(1,1): [[['E'], [['A', 'B'], ['C', 'D']]]],
(1,2): [],
(2,1): [[['E'], [['A', 'B'], ['C', 'F']]], [['S'], [[[['E'], [['A', 'B'], ['C', 'D']]]], [[['G'], [['H', 'J'], [[['E'], [['A', 'B'], ['C', 'F']]]]]]]]]]
}
How do I retrieve the first sublist I find that begins with ['S']? For the example above I would like to get:
answer = [['S'], [[[['E'], [['A', 'B'], ['C', 'D']]]], [[['G'], [['H', 'J'], [[['E'], [['A', 'B'], ['C', 'F']]]]]]]]]
I don't know how deep the 'S' is nested.
EDIT:
I attempted to do it recursively as follows:
def recursive_check(longlist):
for a_list in longlist:
if 'S' in a_lis:
return a_lis
else:
rec_check(a_list)
I received the following error:
RuntimeError: maximum recursion depth exceeded
EDIT: The list may look different and be nested differently every time.
def first_s(lst):
if not (isinstance(lst, list) and lst):
return None
if lst[0] == ['S']:
return lst
else:
for x in lst:
y = first_s(x)
if y:
return y
Using your my_dict:
>>> print first_s(my_dict.values())
[['S'], [[[['E'], [['A', 'B'], ['C', 'D']]]], [[['G'], [['H', 'J'], [[['E'], [['A', 'B'], ['C', 'F']]]]]]]]]
To get that list:
answer = my_dict[(2,1)][1]
It first gets the dictionary value with key of (2, 1), then takes that value (which is a list) and gets its item at index 1, which is the second item in the list (after 0).
>>> my_dict = {(1,0): ['A', 'B'],
... (1,1): [[['E'], [['A', 'B'], ['C', 'D']]]],
... (1,2): [],
... (2,1): [[['E'], [['A', 'B'], ['C', 'F']]], [['S'], [[[['E'], [['A', 'B'], ['C', 'D']]]], [[['G'], [['H', 'J'], [[['E'], [['A', 'B'], ['C', 'F']]]]]]]]]]
... }
>>> my_dict[(2,1)][1]
[['S'],
[[[['E'], [['A', 'B'], ['C', 'D']]]],
[[['G'], [['H', 'J'], [[['E'], [['A', 'B'], ['C', 'F']]]]]]]]]
(By the way, in your example, you are missing a comma after (1,2): []
...Update: which has now been fixed :) )
You can print element of s like my_dict[(2,1)][1][0] .
def nest(a,x):
m=False
for i in a:
if type(i)==list:
m=nest(i,x)
else:
if i==x:
return True
return m
def our_fun(my_dict):
for i in my_dict:
for j in my_dict[i]:
if nest(j,'S')==True:
return my_dict[i]
I checked for 'S' recursively.