Good day to all.

There is a list:

lst = [1, [2, 3], 4, [[6, 7]]] 

It needs to be brought to this form:

 lst = [1, 2, 3, 4, 6, 7] 

You can not use branching and cycles, only standard methods. The problem is simple, but it blew my brain, can more experienced comrades help?

I use Python 3.5

4 answers 4

just for fun =)

 flatten = lambda lst :eval('['+str(lst).replace('[','').replace(']','')+']') flat_list = flatten(lst) 

But it is better not to use eval() in your code, read more here.


Well or so (you can specify several types):

 flatten = lambda lst: isinstance(lst, (int, str, float, bool)) and [lst] or sum(map(flatten, lst), []) lst = ["a", [2.3, True], 'abc', [[6, 7]]] flat_list = flatten(lst) print(flat_list) ['a', 2.3, True, 'abc', 6, 7] 
  • one
    You can use ast.literal_eval instead of eval here. - jfs
  • It is worth mentioning explicitly that even by super low standards of solutions that cannot be used "branching and loops", manipulating the code as a simple string combined with eval() is a dirty dirty hack (after use, private places may fall off) —you are warned. - jfs

For starters, here is a version that uses branches and loops so that the expected behavior is clear:

 from collections import Iterable def flatten_gen(nested, isatom=lambda x: not isinstance(x, Iterable)): for item in nested: if isatom(item): yield item else: yield from flatten_gen(item) 

Example:

 lst = [1, [2, 3], 4, [[6, 7]]] print(list(flatten_gen(lst))) # -> [1, 2, 3, 4, 6, 7] 

where isatom() predicate defines what is an atom (non-breaking object) from the point of view of this method. The implication is that "not atoms" can be passed into a loop to get the objects of which they are composed.

The algorithm is straightforward: going through the elements of the transferred list, we give the atoms as they are, and for composite objects, we call the generator recursively.

Since strings work with loops by default, if you want to treat them as atoms in your case, pass isatom explicitly:

 isatom = lambda x: isinstance(x, str) or not instance(x, Iterable) 

If only integers are considered atoms, then isatom = lambda x: isinstance(x, int) .


Recursive version without explicit branching, cycles, using iterable unpacking syntax :

 def flatten(nested): try: first, *rest = nested except TypeError: # not an iterable return [nested] except ValueError: # empty return [] return flatten(first) + flatten(rest) 

The code cuts off the first item from the specified nested list and connects the flat list obtained from it using the recursive call flatten(first) with the flat list received by calling the rest of the input flatten(rest) list.

If it is not possible to cut off the first element due to the fact that the input is empty or the scalar is given (not an enumerated value — a number for example), then an empty list is returned or this scalar is inside the list, respectively.

If the input contains a nested list with numbers, then at each level the task is reduced until the function is called with the simplest parameters: a number or an empty list, which leads to the return of specific values ​​and collecting the final result when lifting through the stack of calls.

This implementation does not work with strings without adaptation, since 'a' == 'a'[0] (a string of one character is equal to its first character) - this leads to infinite recursion in this case.

The code is not optimal in performance (for simplicity), but there are no if / else branches and loops.


Here is a similar, but more efficient, version that uses the generator:

 def flatten_gen(nested): try: it = iter(nested) except TypeError: # not an iterable yield nested return try: first = next(it) except StopIteration: # empty return yield from flatten_gen(first) yield from flatten_gen(it) 

The generator tries to get its iterator using iter(nested) and if a scalar is entered (for example, an integer), then TypeError thrown TypeError and the generator returns this scalar ( yield ). next(it) returns the first item from the iterator if it is not empty. Then scalars are recursively generated for the first element and the remaining elements in the it iterator.


If you can use loops from the library code, like a sum(map(flatten, lst, [])) solution from the @borisrozumnuk answer , you can improve the flatten_gen() generator using itertools.chain() :

 from itertools import chain def flatten_gen(nested): try: it = iter(nested) except TypeError: # not an iterable yield nested else: yield from chain.from_iterable(map(flatten_gen, it)) 

Since recursion is not used in this case to implement the cycle, the number of calls is half as much.


If you want, you can write in the form of a single expression:

 def flatten(nested): return (isinstance(nested, int) and [nested] # an int or nested # empty and flatten(nested[0]) + flatten(nested[1:])) # non-empty list 

What works, thanks to the short-circuit behavior and / or logical operators. It is not clear whether this should be considered as branching.

If nested is an integer, then the code returns this number wrapped in a list ( [nested] ). If nested not an integer (but a list), then it is immediately returned if it is empty ( len(nested) == 0 means that bool(nested) is False ). If nested not an empty list, then the flat list union returned by the recursive calls for the first item and the remainder of the list — as well as in the solutions above in this answer.


If we move further towards the unreadability of the code, then the branch can be replaced by indexing lists and lambda to delay execution:

 def flatten(nested): return [ lambda: [ # list (not an int) lambda: flatten(nested[0]) + flatten(nested[1:]), # non-empty list lambda: [] # empty ][not nested](), lambda: [nested] # an int ][isinstance(nested, int)]() 

The general idea: True == 1 and False == 0 in Python, so the expression [on_false, on_true][condition]() calls the on_false function if condition false and on_true true.

    You need a recursive traversal of list items. For example:

     lst = [1, [2, 3], 4, [[6, 7]]] def lst_to_flat(S): if S == []: return S if isinstance(S[0], list): return lst_to_flat(S[0]) + lst_to_flat(S[1:]) return S[:1] + lst_to_flat(S[1:]) print(lst_to_flat(lst)) 
    • The author also wrote that the branching can not be used. - Xander
    • if S == []: ’s better not to do it this way, much more pitonychay like this: if not S - Alexander Druze
     lst = [1, [2, 3], 4, [[6, 7]]] q = str(lst) a = q.replace("[",'') b = a.replace("]",'') g = b.replace(",",'') y = g.replace(" ",'') s = list(map(int, list( y ))) print (s) 

    He advised a senior comrade. This code will not work if the initial array contains strings, numbers, and boolean values, but for a specific example, it will work. Senior-zero and jfs answers are the best (IMHO).

    • My comment to another answer is also applicable here . If you want to work with strings and you are only interested in whole numbers, you can simplify the solution: list(map(int, re.findall(r'\d+', str(lst)))) - jfs
    • Something dubious answer. Immediately even with numbers in which more than one digit will not work? - Qwertiy ♦
    • @Qwertiy is correct, although g.split() instead of g.replace(" ", '') will easily remove this restriction. (If we leave aside the fact, rationality with the code as with a simple string without a structure to work). - jfs
    • There is no need to minus the answer — the code works for the example in question. - jfs