Я пытался найти быстрый способ сортировки строк в Python, и языковой стандарт не вызывает беспокойства, т.е. я просто хочу отсортировать массив лексически в соответствии с базовыми байтами. Это идеально подходит для чего-то вроде поразрядной сортировки. Вот мой MWE

import numpy as np
import timeit

# randChar is workaround for MemoryError in mtrand.RandomState.choice
# http://stackoverflow.com/questions/25627161/how-to-solve-memory-error-in-mtrand-randomstate-choice
def randChar(f, numGrp, N) :
   things = [f%x for x in range(numGrp)]
   return [things[x] for x in np.random.choice(numGrp, N)]

N=int(1e7)
K=100
id3 = randChar("id%010d", N//K, N)   # small groups (char)
timeit.Timer("id3.sort()" ,"from __main__ import id3").timeit(1) # 6.8 seconds

Как вы можете видеть, это заняло 6,8 секунды, что почти в 10 раз медленнее, чем сортировка R.

N = 1e7
K = 100
id3 = sample(sprintf("id%010d",1:(N/K)), N, TRUE)
system.time(sort(id3,method="radix"))

Я понимаю, что Python .sort() не использует сортировку по основанию, есть ли где-нибудь реализация, которая позволяет мне сортировать строки так же эффективно, как R?

AFAIK как R, так и Python "внутренние" строки, поэтому любые оптимизации в R также могут быть выполнены в Python.

Лучшим результатом Google для "строк сортировки по основанию" python "является this gist, который вызвал ошибку при сортировке на моем тестовом массиве.

5
xiaodai 31 Дек 2017 в 04:57

2 ответа

Лучший ответ

Джереми Метс написал в комментариях к это сообщение в блоге о том, что Numpy может правильно отсортировать строку, преобразовав массив в np.araray. Это действительно улучшает производительность, но все же медленнее, чем реализация Джулии.

import numpy as np
import timeit

# randChar is workaround for MemoryError in mtrand.RandomState.choice
# http://stackoverflow.com/questions/25627161/how-to-solve-memory-error-in-mtrand-randomstate-choice
def randChar(f, numGrp, N) :
   things = [f%x for x in range(numGrp)]
   return [things[x] for x in np.random.choice(numGrp, N)]

N=int(1e7)
K=100
id3 = np.array(randChar("id%010d", N//K, N))   # small groups (char)
timeit.Timer("id3.sort()" ,"from __main__ import id3").timeit(1) # 6.8 seconds
0
xiaodai 10 Май 2018 в 12:34

Это правда, что R обрабатывает все строки, то есть имеет «глобальный кеш символов», который служит центральным словарем всех строк, когда-либо использовавшихся вашей программой. Это имеет свои преимущества: данные занимают меньше памяти, и некоторые алгоритмы (например, поразрядная сортировка) могут использовать эту структуру для достижения более высокой скорости. Это особенно верно для сценариев, таких как ваш пример, где количество уникальных строк мало по сравнению с размером вектора. С другой стороны, у него тоже есть свои недостатки: глобальный символьный кеш предотвращает многопоточный доступ на запись к символьным данным.

В Python, afaik, интернируются только строковые литералы. Например:

 >>> 'abc' is 'abc'
 True
 >>> x = 'ab'
 >>> (x + 'c') is 'abc'
 False

На практике это означает, что, если вы не встроили данные непосредственно в текст программы, ничего интернироваться не будет.


Теперь по вашему исходному вопросу: «каков самый быстрый способ сортировки строк в Python»? Вы можете достичь очень хорошей скорости, сравнимой с R, с пакетом python datatable. Вот тест, который сортирует N = 10⁸ строк, случайно выбранных из набора из 1024:

import datatable as dt
import pandas as pd
import random
from time import time
n = 10**8
src = ["%x" % random.getrandbits(10) for _ in range(n)]
f0 = dt.Frame(src)
p0 = pd.DataFrame(src)
f0.to_csv("test1e8.csv")

t0 = time(); f1 = f0.sort(0); print("datatable: %.3fs" % (time()-t0))
t0 = time(); src.sort(); print("list.sort: %.3fs" % (time()-t0))
t0 = time(); p1 = p0.sort_values(0); print("pandas:    %.3fs" % (time()-t0))

Что производит:

datatable: 1.465s / 1.462s / 1.460s (multiple runs)
list.sort: 44.352s
pandas:    395.083s

Тот же набор данных в R (v3.4.2):

> require(data.table)
> DT = fread("test1e8.csv")
> system.time(sort(DT$C1, method="radix"))
   user  system elapsed 
  6.238   0.585   6.832 
> system.time(DT[order(C1)])
   user  system elapsed 
  4.275   0.457   4.738 
> system.time(setkey(DT, C1))  # sort in-place
   user  system elapsed 
  3.020   0.577   3.600 
3
Pasha 21 Мар 2018 в 18:44