2

The goal is to insert a string after every other element in the list apart from the last one:

arr0 = ['aa','bb','cc','dd']

Goal

 ['aa','XX','bb', 'XX','cc','XX','dd']

This topic has been addressed in posts like this, but the lists of strings used are only one character in length which affects the list comprehension. I do not have enough reputation points to comment and ask for clarification.

I have implemented it with a for loop, but I was trying to practice with list-comprehension and would appreciate insight as to where I am going wrong with it. Currently getting a SyntaxError: invalid syntax.

Example and Current Implementation

arr0 = ['aa','bb','cc','dd'] # Goal ['aa','XX','bb', 'XX','cc','XX','dd']

# Stop for range
total = len(arr0)*2-1

for i in range(1, total, 2):
   arr0.insert(i, "XX")

# Returns ['aa', 'XX', 'bb', 'XX', 'cc', 'XX', 'dd']

List Comprehension Attempt:

[el for y in [[el, 'XX'] if idx != len(arr0)-1 else el for idx, el in enumerate(arr0)] for el in y if isinstance(y, list) else el]
Breakdown
[[el, 'XX'] if idx != len(arr0)-1 else el for idx, el in enumerate(arr0)]

# Returns
# [['aa', 'XX'], ['bb', 'XX'], ['cc', 'XX'], 'dd']

In the outer comprehension, I am trying to return it as a single list of the strings. I am trying to use isinstance to check if the element is a list or not (the last item being a string) and if not return simply the string.

Edit

I really appreciate the responses. I should have included this alternative case that I do encounter where I do not want elements inserted after a 'Note' element at the end, in which case I could not perform the slice. Is negative indexing with a step possible?

# Alternative scenario
arr1 = ['aa','bb','cc','dd', 'Note']

# ['aa','XX,'bb','XX,'cc','XX,'dd','Note']

2 Answers2

5

You can simply use a nested list comprehension and strip the last element:

>>> [e for i in arr0 for e in [i, "XX"]][:-1]
['aa', 'XX', 'bb', 'XX', 'cc', 'XX', 'dd']

You can also use a .split()/.join() trick (which is probably less performant):

>>> ",XX,".join(arr0).split(",")
['aa', 'XX', 'bb', 'XX', 'cc', 'XX', 'dd']

A fancier way is to use itertools.chain:

>>> from itertools import chain
>>> list(chain.from_iterable(zip(arr0, ["XX"]*len(arr0))))[:-1]
['aa', 'XX', 'bb', 'XX', 'cc', 'XX', 'dd']

Edit: For the alternative case added later to the question, it is possible to slice the input and manually append the last element to the output:

>>> arr1 = ['aa','bb','cc','dd', 'Note']
>>> [e for i in arr1[:-1] for e in [i, "XX"]][:-1] + [arr1[-1]]
['aa', 'XX', 'bb', 'XX', 'cc', 'XX', 'dd', 'Note']
Selcuk
  • 57,004
  • 12
  • 102
  • 110
  • If you add another import `from itertools import zip_longest` it might be shorter `chain.from_iterable(zip_longest(arr0, [], fillvalue="XX"))` – frost-nzcr4 Mar 31 '21 at 06:29
  • @frost-nzcr4 It is not shorter though. It would still leave a `"XX"` at the end that you'll need to strip out. – Selcuk Mar 31 '21 at 06:31
  • This part will shortened `["XX"]*len(arr0)`, anyway good answer) – frost-nzcr4 Mar 31 '21 at 06:33
  • Ah, true, it would certainly be cleaner, if not shorter. Care to post it as an alternative answer? – Selcuk Mar 31 '21 at 06:35
  • @Selcuk I was originally using the enumerate to be able to control how many times to create the [elem, 'XX'] due to the case in my edit. But the slicing definitely works for the other scenarios. – ExpertPomegranate Mar 31 '21 at 06:37
1

Just for fun, one more option to consider if you want all the logic within the list comprehension itself:

from itertools import zip_longest

initial_list = ['aa','bb','cc','dd'] # Goal ['aa','XX','bb', 'XX','cc','XX','dd']

padded_list = [
    value
    for item in zip_longest(initial_list, ["XX"] * (len(initial_list) - 1))
    for value in item
    if value
]

print(padded_list)

Output:

['aa', 'XX', 'bb', 'XX', 'cc', 'XX', 'dd']
rhurwitz
  • 2,557
  • 2
  • 10
  • 18