Argsorting значения в списке списков - PullRequest
0 голосов
/ 27 мая 2018

У меня есть список списков A длины m.Каждый список A содержит положительные числа от {1, 2, ..., n}.Ниже приведен пример, где m = 3 и n = 4.

A = [[1, 1, 3], [1, 2], [1, 1, 2, 4]]

Я представляю каждое число x в A в виде пары (i, j), где A[i][j] = x.Я хотел бы отсортировать числа в A в неубывающем порядке;разрыв связи по наименьшему первому индексу.То есть, если A[i1][j1] == A[i2][j2], то (i1, j1) предшествует (i2, j2) тогда и только тогда i1 <= i2.

В этом примере я хотел бы вернуть пары:

(0, 0), (0, 1), (1, 0), (2, 0), (2, 1), (1, 1), (2, 2), (0, 2), (2, 3)

, которыепредставляет отсортированные числа

1, 1, 1, 1, 1, 2, 2, 3, 4

То, что я сделал, это наивный подход, который работает следующим образом:

  • Сначала я сортирую каждый список в A.
  • Затем я перебираю числа в {1, 2, ..., n} и списке A и добавляю пары.

Код:

for i in range(m): 
    A[i].sort()
S = []
for x in range(1, n+1):
    for i in range(m):
        for j in range(len(A[i])):
            if A[i][j] == x:
                S.append((i, j))

Я думаю, что этот подход не очень хорош.Можем ли мы сделать лучше?

Ответы [ 4 ]

0 голосов
/ 28 мая 2018

Только потому, что @jpp развлекается:

from itertools import chain
import numpy as np
def agn(A):
    idx = np.argsort(np.fromiter(chain(*A), dtype=np.int))
    return np.array(tuple((i, j) for i, r in enumerate(A) for j, _ in enumerate(r)))[idx]

Сроки Тесты:

Тест 1:

Сравнение с самым быстрым методом из @coldspeed:

In [1]: import numpy as np

In [2]: print(np.__version__)
1.13.3

In [3]: from itertools import chain

In [4]: import sys

In [5]: print(sys.version)
3.5.5 |Anaconda, Inc.| (default, Mar 12 2018, 16:25:05) 
[GCC 4.2.1 Compatible Clang 4.0.1 (tags/RELEASE_401/final)]

In [6]: A = [[1],[0, 0, 0, 1, 1, 3], [1, 2], [1, 1, 2, 4]] * 10000

In [7]: %timeit np.array(tuple((i, j) for i, r in enumerate(A) for j, _ in enumerate(r)))[np.argsort(np.fromit
   ...: er(chain(*A), dtype=np.int))]
89.4 ms ± 718 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [8]: %timeit B = [(i, j) for i, x in enumerate(A) for j, _ in enumerate(x)]; B.sort(key=lambda ix: A[ix[0]]
   ...: [ix[1]])
93.5 ms ± 1.65 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

Тест 2a:

В этом тесте используется один случайно сгенерированный большой массив A (каждый подсписок отсортирован, потому что так выглядит OP-список):

In [20]: A = [sorted([random.randint(1, 100) for _ in range(random.randint(1,1000))]) for _ in range(10000)]

In [21]: def agn(A):
    ...:     idx = np.argsort(np.fromiter(chain(*A), dtype=np.int))
    ...:     return np.array(tuple((i, j) for i, r in enumerate(A) for j, _ in enumerate(r)))[idx]
    ...:     

In [22]: %timeit agn(A)
3.1 s ± 62.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [23]: %timeit cs1(A)
3.2 s ± 89.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

Тест 2.b

Аналогично тесту 2.b, но с несортированным массивом A:

In [25]: A = [[random.randint(1, 100) for _ in range(random.randint(1,1000))] for _ in range(10000)]

In [26]: %timeit cs1(A)
4.24 s ± 215 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [27]: %timeit agn(A)
3.44 s ± 49.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
0 голосов
/ 27 мая 2018

Вы можете сделать триплетов из (x, i, j), отсортировать эти триплеты, а затем извлечь индексы (i, j).Это работает, потому что триплеты содержат всю информацию, необходимую для сортировки и включения в окончательный список, в порядке, необходимом для сортировки.(Это называется идиома «Украсить-Сортировать-Украсить», относящаяся к преобразованию Шварца - Hat-tip to @Morgen для названия и обобщения, а также для меня мотивации объяснить общность этого метода.) Это можно объединитьв одно утверждение, но я разделил его здесь для ясности.

A = [[1, 1, 3], [1, 2], [1, 1, 2, 4]]

triplets = [(x, i, j) for i, row in enumerate(A) for j, x in enumerate(row)]
pairs = [(i, j) for x, i, j in sorted(triplets)]
print(pairs)

Вот результат печати:

[(0, 0), (0, 1), (1, 0), (2, 0), (2, 1), (1, 1), (2, 2), (0, 2), (2, 3)]
0 голосов
/ 27 мая 2018

Для забавы, вот метод через стороннюю библиотеку numpy.Производительность примерно на 10% ниже, чем у решения @ coldspeed из-за дорогого шага заполнения.

Кредиты : для этого решения я адаптировал @ Divakar's array-from-jagged-list рецепт и дословно скопированное @ AshwiniChaudhary's многомерное решение argsort .

import numpy as np

A = [[1, 1, 3], [1, 2], [1, 1, 2, 4]]

def create_array(data):

    """Convert jagged list to numpy array; pad with max_value + 1"""

    lens = np.array([len(i) for i in data])
    mask = np.arange(lens.max()) < lens[:,None]
    out = np.full(mask.shape, max(map(max, data))+1, dtype=int)  # Pad with max_value + 1
    out[mask] = np.concatenate(data)
    return out

def apply_argsort(arr):

    """Flatten, argsort, extract indices, then stack into a single array"""

    return np.dstack(np.unravel_index(np.argsort(arr.ravel()), arr.shape))[0]

# limit only to number of elements in A
res = apply_argsort(create_array(A))[:sum(map(len, A))]

print(res)

[[0 0]
 [0 1]
 [1 0]
 [2 0]
 [2 1]
 [1 1]
 [2 2]
 [0 2]
 [2 3]]
0 голосов
/ 27 мая 2018

list.sort

Вы можете создать список индексов и затем вызвать list.sort с key:

B = [(i, j) for i, x in enumerate(A) for j, _ in enumerate(x)]
B.sort(key=lambda ix: A[ix[0]][ix[1]])

print(B)
[(0, 0), (0, 1), (1, 0), (2, 0), (2, 1), (1, 1), (2, 2), (0, 2), (2, 3)]

Примечаниечто в python-2.x, где поддерживается повторяемая распаковка в функциях, вы можете упростить вызов sort немного:

B.sort(key=lambda (i, j): A[i][j])

sorted

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

B = sorted([
       (i, j) for i, x in enumerate(A) for j, _ in enumerate(x)
    ], 
    key=lambda ix: A[ix[0]][ix[1]]
)

print(B)
[(0, 0), (0, 1), (1, 0), (2, 0), (2, 1), (1, 1), (2, 2), (0, 2), (2, 3)]

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

По многочисленным просьбам, добавив немного времени и сюжет.

enter image description here

Из графика видно, что вызов list.sort более эффективен, чемsorted.Это связано с тем, что list.sort выполняет сортировку на месте, поэтому при создании копии данных, которая есть у sorted, нет затрат времени / пространства.

Функции

def cs1(A):
    B = [(i, j) for i, x in enumerate(A) for j, _ in enumerate(x)]
    B.sort(key=lambda ix: A[ix[0]][ix[1]]) 

    return B

def cs2(A):
    return sorted([
           (i, j) for i, x in enumerate(A) for j, _ in enumerate(x)
        ], 
        key=lambda ix: A[ix[0]][ix[1]]
    )

def rorydaulton(A):

    triplets = [(x, i, j) for i, row in enumerate(A) for j, x in enumerate(row)]
    pairs = [(i, j) for x, i, j in sorted(triplets)]

    return pairs

def jpp(A):
    def _create_array(data):
        lens = np.array([len(i) for i in data])
        mask = np.arange(lens.max()) < lens[:,None]
        out = np.full(mask.shape, max(map(max, data))+1, dtype=int)  # Pad with max_value + 1
        out[mask] = np.concatenate(data)
        return out

    def _apply_argsort(arr):
        return np.dstack(np.unravel_index(np.argsort(arr.ravel()), arr.shape))[0]

    return _apply_argsort(_create_array(A))[:sum(map(len, A))]

def agngazer(A):
    idx = np.argsort(np.fromiter(chain(*A), dtype=np.int))
    return np.array(
       tuple((i, j) for i, r in enumerate(A) for j, _ in enumerate(r))
    )[idx]

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

from timeit import timeit
from itertools import chain

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

res = pd.DataFrame(
       index=['cs1', 'cs2', 'rorydaulton', 'jpp', 'agngazer'],
       columns=[10, 50, 100, 500, 1000, 5000, 10000, 50000],
       dtype=float
)

for f in res.index: 
    for c in res.columns:
        l = [[1, 1, 3], [1, 2], [1, 1, 2, 4]] * c
        stmt = '{}(l)'.format(f)
        setp = 'from __main__ import l, {}'.format(f)
        res.at[f, c] = timeit(stmt, setp, number=30)

ax = res.div(res.min()).T.plot(loglog=True) 
ax.set_xlabel("N"); 
ax.set_ylabel("time (relative)");

plt.show();
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...