Подтвердить что ты не робот

Объединить списки, имеющие конкретный порядок слияния в питоническом порядке?

Я хотел бы построить список x из двух списков y и z. Я хочу, чтобы все элементы из y помещались в точку ypos. Например:

y = [11, 13, 15]
z = [12, 14]
ypos = [1, 3, 5]

Итак, x должен быть [11, 12, 13, 14, 15]

Другой пример:

y = [77]
z = [35, 58, 74]
ypos = [3]

Итак, x должен быть [35, 58, 77, 74]

Я написал функцию, которая делает то, что я хочу, но выглядит уродливо:

def func(y, z, ypos):
    x = [0] * (len(y) + len(z))
    zpos = list(range(len(y) + len(z)))
    for i, j in zip(y, ypos):
        x[j-1] = i
        zpos.remove(j-1)
    for i, j in zip(z, zpos):
        x[j] = i
    return x

Как записать его на pythonic?

4b9b3361

Ответ 1

Если списки очень длинные, многократное вызов insert может быть не очень эффективным. В качестве альтернативы вы можете создать два iterators из списков и создать список, получив элемент next от любого из итераторов в зависимости от того, находится ли текущий индекс в ypos (или set):

>>> ity = iter(y)
>>> itz = iter(z)
>>> syp = set(ypos)
>>> [next(ity if i+1 in syp else itz) for i in range(len(y)+len(z))]
[11, 12, 13, 14, 15]

Примечание: это приведет к вставке элементов из y в порядке их появления в y, т.е. первый элемент y вставляется с самым низким индексом в ypos, не обязательно при первом индексе в ypos. Если элементы y следует вставить в индекс соответствующего элемента ypos, то либо ypos должен быть в порядке возрастания (т.е. Первый индекс ypos также является самым низким), либо итератор y должен быть отсортирован по тому же порядку, что и индексы в ypos (впоследствии сам ypos не нужно сортировать, так как мы все равно превращаем его в set).

>>> ypos = [5,3,1]   # y and z being same as above
>>> ity = iter(e for i, e in sorted(zip(ypos, y)))
>>> [next(ity if i+1 in syp else itz) for i in range(len(y)+len(z))]
[15, 12, 13, 14, 11]

Ответ 2

Вы должны использовать list.insert, это то, для чего он был создан!

def func(y, z, ypos):
    x = z[:]
    for pos, val in zip(ypos, y):
        x.insert(pos-1, val)
    return x

и тест:

>>> func([11, 13, 15], [12, 14], [1,3,5])
[11, 12, 13, 14, 15]

Ответ 3

С большими списками может быть хорошей идеей работать с numpy.

Алгоритм

  • создайте новый массив размером y + z
  • вычислить координаты для значений z
  • присвойте y значения x в ypos
  • присвойте z значения x в zpos

Сложность должна быть O(n), при этом n является общим числом значений.

import numpy as np

def distribute_values(y_list, z_list, y_pos):
    y = np.array(y_list)
    z = np.array(z_list)
    n = y.size + z.size
    x = np.empty(n, np.int)
    y_indices = np.array(y_pos) - 1
    z_indices = np.setdiff1d(np.arange(n), y_indices, assume_unique=True)
    x[y_indices] = y
    x[z_indices] = z
    return x

print(distribute_values([11, 13, 15], [12, 14], [1, 3, 5]))
# [11 12 13 14 15]
print(distribute_values([77], [35, 58, 74], [3]))
# [35 58 77 74]

В качестве бонуса он также отлично работает, когда ypos не сортируется:

print(distribute_values([15, 13, 11], [12, 14], [5, 3, 1]))
# [11 12 13 14 15]
print(distribute_values([15, 11, 13], [12, 14], [5, 1, 3]))
# [11 12 13 14 15]

Производительность

Если n установлен в 1 миллион, этот подход немного быстрее, чем @tobias_k answer и в 500 раз быстрее @Joe_Iddon answer.

Списки были созданы следующим образом:

from random import random, randint
N = 1000000
ypos = [i+1 for i in range(N) if random()<0.4]
y = [randint(0, 10000) for _ in ypos]
z = [randint(0, 1000) for _ in range(N - len(y))

Вот результаты с %timeit и IPython:

%timeit eric(y, z, ypos)
131 ms ± 1.54 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

%timeit tobias(y, z, ypos)
224 ms ± 977 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)

%timeit joe(y,z, ypos)
54 s ± 1.48 s per loop (mean ± std. dev. of 7 runs, 1 loop each)

Ответ 4

Предполагая, что индексы ypos отсортированы, вот еще одно решение с использованием итераторов, хотя оно также поддерживает ypos неизвестной или бесконечной длины:

import itertools

def func(y, ypos, z):
    y = iter(y)
    ypos = iter(ypos)
    z = iter(z)
    next_ypos = next(ypos, -1)
    for i in itertools.count(start=1):
        if i == next_ypos:
            yield next(y)
            next_ypos = next(ypos, -1)
        else:
            yield next(z)

Ответ 5

Питонический путь

y = [11, 13, 15]
z = [12, 14]
ypos = [1, 3, 5]

x = z[:]

for c, n in enumerate(ypos):
    x.insert(n - 1, y[c])

print(x)

Выход

[11, 12, 13, 14, 15]

В функции

def func(y, ypos, z):
    x = z[:]
    for c,n in enumerate(ypos):
        x.insert(n-1,y[c])
    return x

print(func([11,13,15],[1,2,3],[12,14]))

outoput

[11, 12, 13, 14, 15]

Использование zip

y, z, ypos = [11, 13, 15], [12, 14], [1, 3, 5]

for i, c in zip(ypos, y):
    z.insert(i - 1, c)

print(z)

[out:]

> [11, 12, 13, 14, 15]