В данном фрагменте Python кода функция сортирует список чисел но работает медленно и иногда возвращает неверный результат: def weird_sort(a): for i in range(len(a)): for j in range(i+1,len(a)): if a[i] > a[j]: a[i], a[j] = a[j], a[i] return a; найдите логическую ошибку, оцените сложность алгоритма, предложите исправление и модификации для работы с большими объёмами данных
Коротко — логическая ошибка в отступах: функция возвращает список внутри внешнего цикла, поэтому сортировка прерывается после первой итерации и список может остаться частично отсортированным.
Разбор и исправление
Исходный код спроблемой—returnвнутрициклас проблемой — return внутри цикласпроблемой—returnвнутрицикла: def weird_sortaaa: for i in rangelen(a)len(a)len(a): for j in rangei+1,len(a)i+1,len(a)i+1,len(a): if aiii > ajjj: aiii, ajjj = ajjj, aiii
return a # <- возвращаем слишком рано!
Правильный вариант — return должен быть после завершения обоих циклов: def weird_sortaaa: for i in rangelen(a)len(a)len(a): for j in rangei+1,len(a)i+1, len(a)i+1,len(a): if aiii > ajjj: aiii, ajjj = ajjj, aiii
return a
Альтернативы и улучшения
1) Использовать встроенную сортировку Python рекомендуетсядляобщегослучая—быстрая,надежнаярекомендуется для общего случая — быстрая, надежнаярекомендуетсядляобщегослучая—быстрая,надежная: def weird_sortaaa: a.sort # in-place, Onlognn log nnlogn
return a
или возвращающая новый список: return sortedaaa
2) Немного оптимизировать ваш On2n^2n2-алгоритм уменьшитьчислообменовуменьшить число обменовуменьшитьчислообменов — классический selection sort: def selection_sortaaa: n = lenaaa
for i in rangennn: min_idx = i for j in rangei+1,ni+1, ni+1,n: if ajjj < aminidxmin_idxminidx: min_idx = j if min_idx != i: aiii, aminidxmin_idxminidx = aminidxmin_idxminidx, aiii
return a
Сложность
Исходный двоичный цикл: время On2n^2n2точно n(n−1)/2сравненийточно ~ n(n-1)/2 сравненийточноn(n−1)/2сравнений. Количество обменов в худшем случае тоже On2n^2n2висходнойверсиивыменяетеприкаждомобнаружениименьшегоэлементав исходной версии вы меняете при каждом обнаружении меньшего элементависходнойверсиивыменяетеприкаждомобнаружениименьшегоэлемента.Selection sort: On2n^2n2 сравнений, но только Onnn обменов.Встроенный sorted/list.sort: Onlognn log nnlogn в среднем и в худшем TimsortTimsortTimsort, память Onnn в худшем для некоторых сценариев, обычно работает очень быстро на реальных данных.
Работа с большими объёмами данных
Если данные в памяти помещаются: используйте list.sort или sorted. Для чисел можно рассмотреть numpy.sort эффективнодлябольшихмассивовчиселэффективно для больших массивов чиселэффективнодлябольшихмассивовчисел.
Если данные не помещаются в ОЗУ внешняясортировкавнешняя сортировкавнешняясортировка:
Разбиваете файл/поток на чанки, сортируете каждый чанк в памяти и сохраняете отсортированные чанки на диск.Затем выполняете k-way merge например,heapq.mergeвPythonнапример, heapq.merge в Pythonнапример,heapq.mergeвPython по отсортированным файлам, записывая результат в выходной файл. Пример концепта упрощённоупрощённоупрощённо: import heapq, tempfile def external_sortinfile,outfile,chunksize=1000000infile, outfile, chunk_size=10_000_00infile,outfile,chunksize=1000000: chunks =
with openinfileinfileinfile as f: while True: chunk = listislice(f,chunksize)islice(f, chunk_size)islice(f,chunksize)
if not chunk: break chunk = int(x)forxinchunkint(x) for x in chunkint(x)forxinchunk
chunk.sort
tmp = tempfile.TemporaryFilemode=′w+t′mode='w+t'mode=′w+t′
tmp.writelines(f"{x}\n" for x in chunk) tmp.seek000
chunks.appendtmptmptmp
with openoutfile,′w′outfile, 'w'outfile,′w′ as out: for val in heapq.merge∗[map(int,c)forcinchunks]*[map(int, c) for c in chunks]∗[map(int,c)forcinchunks]: out.write(f"{val}\n")
Для параллельной обработки: разбить данные на части, отсортировать параллельно multiprocessing/MapReducemultiprocessing/MapReducemultiprocessing/MapReduce, затем выполнить k-way merge.
Если данные лежат в БД — сортировать средствами СУБД индексы,ORDERBYсexternalsortиндексы, ORDER BY с external sortиндексы,ORDERBYсexternalsort.
Для очень больших потоков и простых числовых ключей можно использовать radix sort / counting sort линейноевремялинейное времялинейноевремя при ограниченном диапазоне ключей.
Коротко по практическим рекомендациям
Для большинства задач — заменить ваш код на a.sort или sortedaaa.Если нужен in-place и вы не хотите побочных эффектов — a.sort; return a.Если память ограничена — использовать внешнюю сортировку chunk+mergechunk+mergechunk+merge или системные инструменты sortвUnixsort в UnixsortвUnix.Для почти отсортированных данных insertion sort илиоставитьTimsortили оставить TimsortилиоставитьTimsort будет быстрым.
Если нужно, могу:
Подправить ваш конкретный фрагмент кода в проекте.Показать рабочую реализацию внешней сортировки с учётом чтения/записи файлов.Рассчитать примерно время и место при конкретных объёмах данных.
Коротко — логическая ошибка в отступах: функция возвращает список внутри внешнего цикла, поэтому сортировка прерывается после первой итерации и список может остаться частично отсортированным.
Разбор и исправление
Исходный код спроблемой—returnвнутрициклас проблемой — return внутри цикласпроблемой—returnвнутрицикла:
def weird_sortaaa:
for i in rangelen(a)len(a)len(a):
for j in rangei+1,len(a)i+1,len(a)i+1,len(a):
if aiii > ajjj:
aiii, ajjj = ajjj, aiii return a # <- возвращаем слишком рано!
Правильный вариант — return должен быть после завершения обоих циклов:
def weird_sortaaa:
for i in rangelen(a)len(a)len(a):
for j in rangei+1,len(a)i+1, len(a)i+1,len(a):
if aiii > ajjj:
aiii, ajjj = ajjj, aiii return a
Альтернативы и улучшения
1) Использовать встроенную сортировку Python рекомендуетсядляобщегослучая—быстрая,надежнаярекомендуется для общего случая — быстрая, надежнаярекомендуетсядляобщегослучая—быстрая,надежная:
или возвращающая новый список: return sortedaaadef weird_sortaaa:
a.sort # in-place, Onlognn log nnlogn return a
2) Немного оптимизировать ваш On2n^2n2-алгоритм уменьшитьчислообменовуменьшить число обменовуменьшитьчислообменов — классический selection sort:
def selection_sortaaa:
n = lenaaa for i in rangennn:
min_idx = i
for j in rangei+1,ni+1, ni+1,n:
if ajjj < aminidxmin_idxmini dx:
min_idx = j
if min_idx != i:
aiii, aminidxmin_idxmini dx = aminidxmin_idxmini dx, aiii return a
Сложность
Исходный двоичный цикл: время On2n^2n2 точно n(n−1)/2сравненийточно ~ n(n-1)/2 сравненийточно n(n−1)/2сравнений. Количество обменов в худшем случае тоже On2n^2n2 висходнойверсиивыменяетеприкаждомобнаружениименьшегоэлементав исходной версии вы меняете при каждом обнаружении меньшего элементависходнойверсиивыменяетеприкаждомобнаружениименьшегоэлемента.Selection sort: On2n^2n2 сравнений, но только Onnn обменов.Встроенный sorted/list.sort: Onlognn log nnlogn в среднем и в худшем TimsortTimsortTimsort, память Onnn в худшем для некоторых сценариев, обычно работает очень быстро на реальных данных.Работа с большими объёмами данных
Если данные в памяти помещаются: используйте list.sort или sorted. Для чисел можно рассмотреть numpy.sort эффективнодлябольшихмассивовчиселэффективно для больших массивов чиселэффективнодлябольшихмассивовчисел.
Если данные не помещаются в ОЗУ внешняясортировкавнешняя сортировкавнешняясортировка:
Разбиваете файл/поток на чанки, сортируете каждый чанк в памяти и сохраняете отсортированные чанки на диск.Затем выполняете k-way merge например,heapq.mergeвPythonнапример, heapq.merge в Pythonнапример,heapq.mergeвPython по отсортированным файлам, записывая результат в выходной файл.Пример концепта упрощённоупрощённоупрощённо:
import heapq, tempfile
def external_sortinfile,outfile,chunksize=1000000infile, outfile, chunk_size=10_000_00infile,outfile,chunks ize=100 000 0:
chunks = with openinfileinfileinfile as f:
while True:
chunk = listislice(f,chunksize)islice(f, chunk_size)islice(f,chunks ize) if not chunk:
break
chunk = int(x)forxinchunkint(x) for x in chunkint(x)forxinchunk chunk.sort tmp = tempfile.TemporaryFilemode=′w+t′mode='w+t'mode=′w+t′ tmp.writelines(f"{x}\n" for x in chunk)
tmp.seek000 chunks.appendtmptmptmp with openoutfile,′w′outfile, 'w'outfile,′w′ as out:
for val in heapq.merge∗[map(int,c)forcinchunks]*[map(int, c) for c in chunks]∗[map(int,c)forcinchunks]:
out.write(f"{val}\n")
Для параллельной обработки: разбить данные на части, отсортировать параллельно multiprocessing/MapReducemultiprocessing/MapReducemultiprocessing/MapReduce, затем выполнить k-way merge.
Если данные лежат в БД — сортировать средствами СУБД индексы,ORDERBYсexternalsortиндексы, ORDER BY с external sortиндексы,ORDERBYсexternalsort.
Для очень больших потоков и простых числовых ключей можно использовать radix sort / counting sort линейноевремялинейное времялинейноевремя при ограниченном диапазоне ключей.
Коротко по практическим рекомендациям
Для большинства задач — заменить ваш код на a.sort или sortedaaa.Если нужен in-place и вы не хотите побочных эффектов — a.sort; return a.Если память ограничена — использовать внешнюю сортировку chunk+mergechunk+mergechunk+merge или системные инструменты sortвUnixsort в UnixsortвUnix.Для почти отсортированных данных insertion sort илиоставитьTimsortили оставить TimsortилиоставитьTimsort будет быстрым.Если нужно, могу:
Подправить ваш конкретный фрагмент кода в проекте.Показать рабочую реализацию внешней сортировки с учётом чтения/записи файлов.Рассчитать примерно время и место при конкретных объёмах данных.