5

I have the following variables:

a = [1, 2, 3]
b = "de"  # <-- not a (usual) list !
c = 5     # <-- not a list !
d = [4, 5, 23, 11, 5]
e = ["dg", "kuku"]

Now I want to concat all a, b, c, d, e to one list:

[1, 2, 3, "de", 5, 4, 5, 23, 11, 5, "dg", "kuku"]

I have tried itertools.chain but it didn't work. Please advise how can I make the concatenation?

buhtz
  • 10,774
  • 18
  • 76
  • 149
SteveS
  • 3,789
  • 5
  • 30
  • 64

5 Answers5

4

chain works with iterables. What you mean is: concatenate these lists and raw values.

I see two steps:

def ensure_list(x):
  if isinstance(x, list):
    return x
  return [x]

lists = map(ensure_list, (a, b, c, d, e))

concatenated = list(itertools.chain.from_iterable(lists))
xtofl
  • 40,723
  • 12
  • 105
  • 192
  • 1
    I like your solution. Could be modified wit a lambda `map(lambda e: e if isinstance(e, list) else [e], (a, b, c, d, e))` to "save" first 5 lines. – buhtz May 18 '22 at 11:48
  • 3
    It could. I would advise against it, because you would have to add a comment to explain what the lambda does. Maybe the compromise lies in collapsing the function body into a single return statement: `return x is isinstance(x, list) else [x]`. – xtofl May 18 '22 at 11:54
4

You could define a function that takes an arbitrary number of arguments and iteratively constructs a list out of them depending on their type like this:

a = [1, 2, 3]
b = "de"  # <-- not a (usual) list !
c = 5     # <-- not a list !
d = [4, 5, 23, 11, 5]
e = ["dg", "kuku"]

def concat(*args):
    out = []
    for arg in args:
        if isinstance(arg, list):
            out.extend(arg)
        else:
            out.append(arg)
    return out

print(concat(a,b,c,d,e))

Outputs:

[1, 2, 3, 'de', 5, 4, 5, 23, 11, 5, 'dg', 'kuku']

Alternatively you could map over the list of args, ensure they're all a list, then use itertools.chain to combine the map object like this:

def concat(*args):
    return list(itertools.chain(*map(lambda x : x if isinstance(x, list) else [x], args)))

print(concat(a,b,c,d,e))

Outputs:

[1, 2, 3, 'de', 5, 4, 5, 23, 11, 5, 'dg', 'kuku']

And here's a much more opaque way of accomplishing the same thing just for fun in a list comprehension:

def concat(*args):
    return [x
            for arg in args
            for x in (arg if isinstance(arg, list) else [arg])]


print(concat(a,b,c,d,e))

Outputs:

[1, 2, 3, 'de', 5, 4, 5, 23, 11, 5, 'dg', 'kuku']

You could also create a generator with map that yields either the argument or the argument in a list and then sum it all together with a list (you probably shouldn't actually do this, but it's neat).
def concat(*args):
    return sum(map(lambda arg : arg if isinstance(arg,list) else [arg], args), [])

print(concat(a,b,c,d,e))

Outputs:

[1, 2, 3, 'de', 5, 4, 5, 23, 11, 5, 'dg', 'kuku']
0x263A
  • 1,807
  • 9
  • 22
2

You have to combine append() and extend() because one of your examples is not a list (b and c) but a single integer.

#!/usr/bin/env python3
a = [1, 2, 3]
b = "de"
c = 5
d = [4, 5, 23, 11, 5]
e = ["dg", "kuku"]

the_input = [a, b, c, d, e]

result = []
for element in the_input:
    if isinstance(element, list):
        result.extend(element)
    else:
        result.append(element)

print(result)

I am not aware of any chain like method to improve that example.

buhtz
  • 10,774
  • 18
  • 76
  • 149
  • 1
    Thanks for the answer, isn't there any chain method that "knows" to append all of them to one list? – SteveS May 18 '22 at 11:39
0

I wouldn't recommend this, but here's another way of doing it if you like list comprehensions or one-liners:

to_concat = [a, b, c]
concatenated_list = []

concatenated_list += [item for sublist in [[list_or_val] if not isinstance(list_or_val, list) else list_or_val for list_or_val in to_concat] for item in sublist]

Output with a, b, c = "de", [1, 2], ["dg", "kuku"] :

In [6]: concatenated_list
Out[6]: ['de', 1, 2, 'dg', 'kuku']

How does it work?

This part:

[[list_or_val] if not isinstance(list_or_val, list) else list_or_val for list_or_val in to_concat]

of the list comprehension creates a new list by transforming non-list values (like a in our case) into lists (so "de" becomes ["de"]). Let's call it list_of_lists. We now want to flatten list_of_lists to get our end result, and we can do so by the other part of the list comprehension:

[item for sublist in list_of_lists for item in sublist]

(More info here on the flattening if you're interested)

As I've said, I wouldn't recommend this solution. It's quite a mess to understand, and it probably has terrible performance, so it's not suited to larger workloads.

ImranD
  • 320
  • 1
  • 6
0

Great question - it lead to making something I will be putting in my utilities toolbox:

Custom generator - chainanything()

I would create the helper function as a generator rather than returning a list. This keeps it more flexible for usage and is usually a tiny bit faster. I usually do this if I have a function that returns a list.

def chainanything(*args, preservestrings=True, recursive=False):
    """
    Generator: yields the contents of a Sequence, or the given object if not a Sequence, one at a time
    
    preservestrings = False will lead to strings being yielded as individual characters. Default = True
    recursive = True will recursively flatten sequences. Default = False
    
    Note: preservestrings = False, recursive = False will only flatten strings which are not part of another Sequence.
    e.g.: 'abc' -> 'a','b','c' but ['ab','cd'] -> 'ab','cd'
    """
    args = [*args]
    for arg in args:
        if not isinstance(arg, Sequence):
            yield arg
        else:
            if preservestrings and isinstance(arg, str):
                yield arg
            elif recursive:
                yield from flatten(arg)
            else:
                yield from arg

this can then be used in it's standard form to provide your expected result:

def test_preservestring():
    # https://stackoverflow.com/questions/72288401/how-to-concat-lists-integers-and-strings-into-one-string/72288721#72288721
    a = [1, 2, 3]
    b = "de"  # <-- not a (usual) list !
    c = 5     # <-- not a list !
    d = [4, 5, 23, 11, 5]
    e = ["dg", "kuku"]
    assert [x for x in chainanything(a,b,c,d,e)] == [1, 2, 3, "de", 5, 4, 5, 23, 11, 5, "dg", "kuku"]

Or with join and map to answer the question in the title and concatenate them into a string:

def test_join():
    a = [1, 2, 3]
    b = "de"  # <-- not a (usual) list !
    c = 5     # <-- not a list !
    d = [4, 5, 23, 11, 5]
    e = ["dg", "kuku"]
    assert ''.join(map(str,chainanything(a,b,c,d,e))) == "123de54523115dgkuku"

The overall function came out a little longer than one line in order to handle strings in a logical way.

The flatten function recursively flattens sequences - it's another little helper generator I created for my toolbox:

def flatten(seq):
    """
    Recursively flattens a sequence (including strings!) and returns all elements in order left to right.
    E.g.: [1,2,[3,4,[5],6],7,[8,9]] -> [1,2,3,4,5,6,7,8,9]
    """
    for item in seq:
        if not isinstance(item, Sequence):
            yield item
        elif len(item) == 1 and item[0] == item: #eg item = 'a'
            yield item[0]
        else:
            yield from flatten(item)

You can grab the latest version of the helpers here: https://dev.azure.com/MusicalNinjas/_git/MikesMath