Объединить два массива и сортировать

Для двух отсортированных массивов, таких как:

a = array([1,2,4,5,6,8,9])

b = array([3,4,7,10])

Я хотел бы, чтобы результат был:

c = array([1,2,3,4,5,6,7,8,9,10])

или

c = array([1,2,3,4,4,5,6,7,8,9,10])

Я знаю, что могу сделать следующее:

c = unique(concatenate((a,b))

Мне просто интересно, есть ли более быстрый способ сделать это, поскольку массивы, с которыми я имею дело, имеют миллионы элементов.

Любая идея приветствуется. Благодаря

Ответ 1

Поскольку вы используете numpy, я сомневаюсь, что bisec помогает вам вообще... Поэтому вместо этого я бы предложил две более мелкие вещи:

  • Не используйте np.sort, вместо этого используйте метод c.sort(), который сортирует массив на месте и избегает копирования.
  • np.unique должен использовать np.sort, который отсутствует. Поэтому вместо использования np.unique выполните логику вручную. IE. сначала выполните сортировку (на месте), затем выполните метод np.unique вручную (проверьте также его код на Python), с flag = np.concatenate(([True], ar[1:] != ar[:-1])), с которым unique = ar[flag] (с сортировкой ar). Чтобы быть немного лучше, вы должны, вероятно, сделать операцию флага на месте, т.е. flag = np.ones(len(ar), dtype=bool), а затем np.not_equal(ar[1:], ar[:-1], out=flag[1:]), что позволяет избежать в основном одной полной копии flag.
  • Я не уверен в этом. Но .sort имеет 3 разных алгоритма, так как ваши массивы, возможно, уже почти отсортированы, изменение метода сортировки может привести к разнице в скорости.

Это сделало бы все, что было бы близко к тому, что вы получили (не делая уникального заранее):

def insort(a, b, kind='mergesort'):
    # took mergesort as it seemed a tiny bit faster for my sorted large array try.
    c = np.concatenate((a, b)) # we still need to do this unfortunatly.
    c.sort(kind=kind)
    flag = np.ones(len(c), dtype=bool)
    np.not_equal(c[1:], c[:-1], out=flag[1:])
    return c[flag]

Ответ 2

Вставка элементов в середину array является очень неэффективной операцией, так как они плоские в памяти, поэтому вам нужно сдвигать все, всякий раз, когда вы вставляете другой элемент. В результате вы, вероятно, не хотите использовать bisect. Сложность этого будет около O(N^2).

Ваш текущий подход O(n*log(n)), так что намного лучше, но он не идеален.

Вставка всех элементов в хеш-таблицу (например, set) - это что-то. Это займет время O(N) для uniquify, но тогда вам нужно отсортировать, что займет O(n*log(n)). Все еще не очень.

Реальное решение O(N) включает выделение массива, а затем заполнение его одним элементом за раз, взяв наименьшую часть ваших входных списков, т.е. слияние. К сожалению, ни numpy, ни Python, похоже, не имеют такой вещи. Решением может быть запись в Cython.

Это выглядело бы смутно следующим образом:

def foo(numpy.ndarray[int, ndim=1] out,
        numpy.ndarray[int, ndim=1] in1, 
        numpy.ndarray[int, ndim=1] in2):

        cdef int i = 0
        cdef int j = 0
        cdef int k = 0
        while (i!=len(in1)) or (j!=len(in2)):
            # set out[k] to smaller of in[i] or in[j]
            # increment k
            # increment one of i or j

Ответ 3

Когда вам интересно о таймингах, всегда лучше всего timeit. Ниже я перечислил подмножество различных методов и их таймингов:

import numpy as np
import timeit
import heapq



def insort(a, x, lo=0, hi=None):
    if hi is None: hi = len(a)
    while lo < hi:
        mid = (lo+hi)//2
        if x < a[mid]: hi = mid
        else: lo = mid+1
    return lo, np.insert(a, lo, [x])

size=10000
a = np.array(range(size))
b = np.array(range(size))

def op(a,b):
    return np.unique(np.concatenate((a,b)))

def martijn(a,b):
    c = np.copy(a)
    lo = 0
    for i in b:
        lo, c = insort(c, i, lo)
    return c

def martijn2(a,b):
    c = np.zeros(len(a) + len(b), a.dtype)
    for i, v in enumerate(heapq.merge(a, b)):
        c[i] = v

def larsmans(a,b):
    return np.array(sorted(set(a) | set(b)))

def larsmans_mod(a,b):
    return np.array(set.union(set(a),b))


def sebastian(a, b, kind='mergesort'):
    # took mergesort as it seemed a tiny bit faster for my sorted large array try.
    c = np.concatenate((a, b)) # we still need to do this unfortunatly.
    c.sort(kind=kind)
    flag = np.ones(len(c), dtype=bool)
    np.not_equal(c[1:], c[:-1], out=flag[1:])
    return c[flag]

Результаты:

martijn2     25.1079499722
OP       1.44831800461
larsmans     9.91507601738
larsmans_mod     5.87612199783
sebastian    3.50475311279e-05

Мой конкретный вклад здесь larsmans_mod, который позволяет избежать создания 2 наборов - он только создает 1 и при этом сокращает время выполнения почти в два раза.

ИЗМЕНИТЬ удален martijn, поскольку он был слишком медленным, чтобы конкурировать. Также протестирован на несколько больших массивов (отсортированный) вход. Я также не проверял правильность вывода...

Ответ 4

В дополнение к другому ответу на использование bisect.insort, если вы не довольны производительностью, вы можете попробовать использовать blist module с bisect. Это должно улучшить производительность.

Традиционная list сложность вставки O(n), а blist сложность при вставке O(log(n)).

Кроме того, вы, похоже, сортируете массивы. Если это так, вы можете использовать функцию merge из heapq mudule, чтобы использовать тот факт, что оба массива предварительно настроены. Такой подход потребует дополнительных затрат из-за разбиения нового массива в памяти. Возможно, это будет возможность рассмотреть, так как эта сложность времени решения O(n+m), а решения с insort - это O(n*m) сложность (n элементов * m вставок)

import heapq

a = [1,2,4,5,6,8,9]
b = [3,4,7,10]


it = heapq.merge(a,b) #iterator consisting of merged elements of a and b
L = list(it) #list made of it
print(L)

Вывод:

[1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 10]

Если вы хотите удалить повторяющиеся значения, вы можете использовать groupby:

import heapq
import itertools

a = [1,2,4,5,6,8,9]
b = [3,4,7,10]


it = heapq.merge(a,b) #iterator consisting of merged elements of a and b
it = (k for k,v in itertools.groupby(it))
L = list(it) #list made of it
print(L)

Вывод:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Ответ 5

Вы можете использовать bisect module для таких объединений, слияние второго списка python в первый.

Функции bisect* работают для массивов numpy, но функции insort* не работают. Достаточно легко использовать исходный код модуля для адаптации алгоритма, это довольно просто:

from numpy import array, copy, insert

def insort(a, x, lo=0, hi=None):
    if hi is None: hi = len(a)
    while lo < hi:
        mid = (lo+hi)//2
        if x < a[mid]: hi = mid
        else: lo = mid+1
    return lo, insert(a, lo, [x])

a = array([1,2,4,5,6,8,9])
b = array([3,4,7,10])

c = copy(a)
lo = 0
for i in b:
    lo, c = insort(c, i, lo)

Не то, чтобы пользовательский insort действительно добавлял что-нибудь здесь, по умолчанию bisect.bisect тоже отлично работает:

import bisect

c = copy(a)
lo = 0
for i in b:
    lo = bisect.bisect(c, i)
    c = insert(c, i, lo)

Использование этого адаптированного insort гораздо более эффективно, чем комбинация и сортировка. Поскольку b также сортируется, мы можем отслеживать точку вставки lo и искать следующую точку, начинающуюся там вместо того, чтобы рассматривать весь массив в каждом цикле.

Если вам не нужно сохранять a, просто действуйте непосредственно на этом массиве и сохраняйте копию.

Более эффективно: поскольку оба списка отсортированы, мы можем использовать heapq.merge:

from numpy import zeros
import heapq

c = zeros(len(a) + len(b), a.dtype)
for i, v in enumerate(heapq.merge(a, b)):
    c[i] = v

Ответ 6

Используйте bisect модуль для этого:

import bisect

a = array([1,2,4,5,6,8,9])
b = array([3,4,7,10])

for i in b:
    pos = bisect.bisect(a, i)
    insert(a,[pos],i) 

Я не могу проверить это прямо сейчас, но должен работать

Ответ 7

Похоже, никто не упомянул union1d (union1d). В настоящее время это ярлык для unique(concatenate((ar1, ar2))), но его короткое имя для запоминания, и он может быть оптимизирован разработчиками numpy с его библиотечной функции. Он очень похож на insort на принятый ответ seberg для больших массивов. Вот мой ориентир:

import numpy as np

def insort(a, b, kind='mergesort'):
    # took mergesort as it seemed a tiny bit faster for my sorted large array try.
    c = np.concatenate((a, b))  # we still need to do this unfortunatly.
    c.sort(kind=kind)
    flag = np.ones(len(c), dtype=bool)
    np.not_equal(c[1:], c[:-1], out=flag[1:])
    return c[flag]

size = int(1e7)
a = np.random.randint(np.iinfo(np.int).min, np.iinfo(np.int).max, size)
b = np.random.randint(np.iinfo(np.int).min, np.iinfo(np.int).max, size)

np.testing.assert_array_equal(insort(a, b), np.union1d(a, b))

import timeit
repetitions = 20
print("insort: %.5fs" % (timeit.timeit("insort(a, b)", "from __main__ import a, b, insort", number=repetitions)/repetitions,))
print("union1d: %.5fs" % (timeit.timeit("np.union1d(a, b)", "from __main__ import a, b; import numpy as np", number=repetitions)/repetitions,))

Выход на моей машине:

insort: 1.69962s
union1d: 1.66338s