简体   繁体   中英

Merge lists in Python by placing every nth item from one list and others from another?

I have two lists, list1 and list2 .

Here len(list2) << len(list1) .

Now I want to merge both of the lists such that every nth element of final list is from list2 and the others from list1 .

For example:

list1 = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']

list2 = ['x', 'y']

n = 3

Now the final list should be:

['a', 'b', 'x', 'c', 'd', 'y', 'e', 'f', 'g', 'h']

What is the most Pythonic way to achieve this?

I want to add all elements of list2 to the final list, final list should include all elements from list1 and list2 .

Making the larger list an iterator makes it easy to take multiple elements for each element of the smaller list:

list1 = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
list2 = ['x', 'y'] 
n = 3

iter1 = iter(list1)
res = []
for x in list2:
    res.extend([next(iter1) for _ in range(n - 1)])
    res.append(x)
res.extend(iter1)

>>> res
['a', 'b', 'x', 'c', 'd', 'y', 'e', 'f', 'g', 'h']

This avoids insert which can be expensive for large lists because each time the whole list needs to be re-created.

To preserve the original list, you could try the following:

result = copy.deepcopy(list1)
index = n - 1
for elem in list2:
    result.insert(index, elem)
    index += n

result

['a', 'b', 'x', 'c', 'd', 'y', 'e', 'f', 'g', 'h']

Using the itertools module and the supplementary more_itertools package , you can construct an iterable solution a couple different ways. First the imports:

import itertools as it, more_itertools as mt

This first one seems the cleanest, but it relies on more_itertools.chunked() .

it.chain(*mt.roundrobin(mt.chunked(list1, n-1), list2))

This one uses only more_itertools.roundrobin() , whose implementation is taken from the itertools documentation, so if you don't have access to more_itertools you can just copy it yourself.

mt.roundrobin(*([iter(list1)]*(n-1) + [list2]))

Alternatively, this does nearly the same thing as the first sample without using any more_itertools -specific functions. Basically, grouper can replace chunked , but it will add None s at the end in some cases, so I wrap it in it.takewhile to remove those. Naturally, if you are using this on lists which actually do contain None , it will stop once it reaches those elements, so be careful.

it.takewhile(lambda o: o is not None,
   it.chain(*mt.roundrobin(mt.grouper(n-1, list1), list2))
)

I tested these on Python 3.4, but I believe these code samples should also work in Python 2.7.

What about the below solution? However I don't have a better one...

>>> list1 = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
>>> list2 = ['x', 'y']
>>> n = 2
>>> for i in range(len(list2)):
...     list1.insert(n, list2[i])
...     n += 3
...     
... 
>>> list1
['a', 'b', 'x', 'c', 'd', 'y', 'e', 'f', 'g', 'h']

n is 2 because the index of third element in a list is 2, since it starts at 0.

list(list1[i-1-min((i-1)//n, len(list2))] if i % n or (i-1)//n >= len(list2) else list2[(i-1)//n] for i in range(1, len(list1)+len(list2)+1))

Definitely not pythonic, but I thought it might be fun to do it in a one-liner. More readable (really?) version:

list(
    list1[i-1-min((i-1)//n, len(list2))]
        if i % n or (i-1)//n >= len(list2)
        else
    list2[(i-1)//n]
        for i in range(1, len(list1)+len(list2)+1)
)

Basically, some tinkering around with indexes and determining which list and which index to take next element from.

Yet another way, calculating the slice steps:

list1 = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
list2 = ['x', 'y'] 
n = 3

res = []
m = n - 1
start, end = 0, m
for x in list2:
    res.extend(list1[start:end])
    res.append(x)
    start, end = end, end + m
res.extend(list1[start:])

>>> res
['a', 'b', 'x', 'c', 'd', 'y', 'e', 'f', 'g', 'h']
list1 = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
list2 = ['x', 'y']
n = 3
new = list1[:]

for index, item in enumerate(list2):
    new[n * (index + 1) - 1: n * (index + 1) - 1] = item

print(new)

I admire @David Z's use of more_itertools . Updates to the tools can simplify the solution:

import more_itertools as mit

n = 3
groups = mit.windowed(list1, n-1, step=n-1)
list(mit.flatten(mit.interleave_longest(groups, list2)))
# ['a', 'b', 'x', 'c', 'd', 'y', 'e', 'f', 'g', 'h']

Summary: list2 is being interleaved into groups from list1 and finally flattened into one list.

Notes

  1. groups : n-1 size sliding windows, eg [('a', 'b'), ('c', 'd'), ('e', 'f'), ('g', 'h')]
  2. interleave_longest is presently equivalent to roundrobin
  3. None is the default fillvalue. Optionally remove with filter(None, ...)

Maybe here is another solution, slice the list1 the correct index then add the element of list2 into list1 .

>>> list1 = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
>>> list2 = ['x', 'y'] 
>>> n = 3
>>> for i in range(len(list2)):
...     list1 = list1[:n*(i+1) - 1] + list(list2[i]) + list1[n*(i+1)-1:]
... 
>>> list1
['a', 'b', 'x', 'c', 'd', 'y', 'e', 'f', 'g', 'h']

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM