Python Concatenate Neighboring String Elements in List

Viewed 306

Is there an elegant way (most likely using a list comprehension) to concatenate all neighboring string elements in an list?

I have a list where there is no functional difference between multiple strings in a row and all of those strings concatenated into a single string, but both for readability and for equivalence testing, I would like to concatenate these together. There can be other non-string elements in the list that can break up the strings. These need to remain between the concatenated groups of strings.

For example, I could have

rule = ["a", "b", C(), "d", "ef", "g"]

and instead, I want

rule = ["ab", C(), "defg"]
4 Answers

You can accomplish this using itertools.groupby and chain.

from itertools import groupby, chain
isstr = lambda x: isinstance(x, basestring)
# on Python 3: lambda x: isinstance(x, str)

rule = ["a", "b", C(), "d", "ef", "g"]
list(chain.from_iterable(
    # join string groups into single-element sequence,
    # otherwise just chain the group itself
    (''.join(group), ) if group_isstr else group
    for group_isstr, group in groupby(rule, isstr)
))
['ab', <__main__.C object at 0x108dfdad0>, 'defg']

itertools.groupby is the usual answer for combining elements based on a common characteristic. In this case, we group on the type of the element, and when the type is str, we collapse it, otherwise we produce results from the group directly. As a "one-liner", you could do:

rule = ["a", "b", C(), "d", "ef", "g"]
rule = [x for cls, grp in itertools.groupby(rule, type)
          for x in ((''.join(grp),) if cls is str else grp)]

Assuming C is a class with a default __repr__, you'd get output that looks like this:

['ab', <__main__.C at 0x1d572c98588>, 'defg']

In this case, the "outer" loop of the listcomp is producing the shared type and an iterator of the elements with that type. When the type is str, we make a one-element tuple of the combined string to "iterate" (it's only one element, so we only iterate once); when it's not str, we produce the elements of the group one by one without further processing.

You can use itertools.groupby:

import itertools
class C:
   pass
rule = ["a", "b", C(), "d", "ef", "g"]
s = [(a, list(b)) for a, b in itertools.groupby(rule, type)]
new_s = [''.join(b) if all(isinstance(c, str) for c in b) else b[0] for a, b in s]

Output:

['ab', <__main__.C instance at 0x101419998>, 'defg']

In a basic way you can use this function:

def concat_str(lst):
    newlst = []
    newstr = ""
    length = len(lst)
    for index, elem in enumerate(lst):
        if(type(elem) is str):
            newstr = newstr + elem
            if(index == length -1):
                newlst.append(newstr)
        else:
            if(newstr):
                newlst.append(newstr)
            newlst.append(elem)
            newstr = ""
    return newlst
Related