Случайны выбор дневника Раскрыть/свернуть полный список возможностей


Найдено 262 сообщений
Cообщения с меткой

сортировка - Самое интересное в блогах

Следующие 30  »
rss_rss_hh_new

Таблицы сортировок в СУБД Cach'e

Пятница, 25 Марта 2016 г. 18:57 (ссылка)

Зато какая сортировка!

(А. С. Пушкин)




Если бы это была запись для твиттера, то она была бы следующей: «Программисты на Cach'e ObjectScript! Используйте Cyrillic4 вместо Cyrillic3!». Но тут Хабр, поэтому придётся развернуть мысль – добро пожаловать под кат.



Всё в Cach'e хранится в глобалах. Данные, метаданные, классы, программы. Узлы глобала сортируются по значениям индексов (subscript) и сохраняются на диске не в том порядке, в котором их вставили, а в отсортированном — для быстрого поиска:



USER>set ^a(10)=""

USER>set ^a("фф")=""

USER>set ^a("бб")=""

USER>set ^a(2)=""

USER>zwrite ^a
^a(2)=""
^a(10)=""
^a("бб")=""
^a("фф")=""


При сортировке Cach'e различает числа и строки — 2 рассматривается как число и сортируется перед 10. Команда zwrite и функции $Order и $Query выводят индексы глобала в том порядке, в котором они хранятся на диске: сначала пустая строка, затем отрицательные числа, ноль, положительные числа, затем строки в порядке, определяемом таблицей сортировки (collation).



В простейшем случае таблица сортировки сопоставляет каждому символу некоторый порядковый номер. Далее таблицу сортировки я буду называть сортировкой.



Стандартная сортировка в Cach'e так и называется — Cach'e standard. Она сопоставляет каждому символу его код в Unicode.



Сортировка, с которой создаются локальные массивы в текущем процессе, определяется локалью (Портал Управления > Администрирование Системы > Конфигурация > Настройка поддержки национальных языков). Для rusw — русской локали юникодных инсталляций Cach'e — таблица сортировки по умолчанию — Cyrillic3. Другие возможные сортировки в rusw — Cach'e standard, Cyrillic1, Cyrillic3, Cyrillic4, Ukrainian1.



Метод ##class(%Collate).SetLocalName() меняет сортировку для локальных массивов текущего процесса:



USER>write ##class(%Collate).GetLocalName()
Cyrillic3
USER>write ##class(%Collate).SetLocalName("Cache standard")
1
USER>write ##class(%Collate).GetLocalName()
Cache standard
USER>write ##class(%Collate).SetLocalName("Cyrillic3")
1
USER>write ##class(%Collate).GetLocalName()
Cyrillic3


У каждой сортировки есть парная, в которой числа сортируются как строки. Имя такой парной сортировки получается добавлением " string" к имени оригинальной сортировки:



USER>write ##class(%Collate).SetLocalName("Cache standard string")
1
USER>kill test

USER>set test(10) = "", test(2) = "", test("фф") = "", test("бб") = ""

USER>zwrite test
test(10)=""
test(2)=""
test("бб")=""
test("фф")=""

USER>write ##class(%Collate).SetLocalName("Cache standard")
1
USER>kill test

USER>set test(10) = "", test(2) = "", test("фф") = "", test("бб") = ""

USER>zwrite test
test(2)=""
test(10)=""
test("бб")=""
test("фф")=""


Cach'e standard и Cyrillic3



В Cach'e standard символы сортируются в порядке их кодов Unicode:



 write ##class(%Library.Collate).SetLocalName("Cache standard"),!
write ##class(%Library.Collate).GetLocalName(),!
set letters = "абвгдеёжзийклмнопрстуфхцчщщьыъэюя"
set letters = letters _ $zconvert(letters,"U")
kill test

//заполняем test данными
for i=1:1:$Length(letters) {
set test($Extract(letters,i)) = ""
}

//выводим индексы массива test в отсортированном порядке
set l = "", cnt = 0
for {
set l = $Order(test(l))
quit:l=""
write l, " ", $Ascii(l),","
set cnt = cnt + 1
write:cnt#8=0 !
}


USER>do ^testcol
1
Cache standard
Ё 1025,А 1040,Б 1041,В 1042,Г 1043,Д 1044,Е 1045,Ж 1046,
З 1047,И 1048,Й 1049,К 1050,Л 1051,М 1052,Н 1053,О 1054,
П 1055,Р 1056,С 1057,Т 1058,У 1059,Ф 1060,Х 1061,Ц 1062,
Ч 1063,Щ 1065,Ъ 1066,Ы 1067,Ь 1068,Э 1069,Ю 1070,Я 1071,
а 1072,б 1073,в 1074,г 1075,д 1076,е 1077,ж 1078,з 1079,
и 1080,й 1081,к 1082,л 1083,м 1084,н 1085,о 1086,п 1087,
р 1088,с 1089,т 1090,у 1091,ф 1092,х 1093,ц 1094,ч 1095,
щ 1097,ъ 1098,ы 1099,ь 1100,э 1101,ю 1102,я 1103,ё 1105,


Все буквы на своём месте, кроме букв «ё» и «Ё». Их коды в Unicode выбиваются из общего порядка. Поэтому для русской локали понадобилась своя таблица сортировки — Cyrillic3, в которой буквы идут в том же порядке, как и в русском алфавите:




USER>do ^testcol
1
Cyrillic3
А 1040,Б 1041,В 1042,Г 1043,Д 1044,Е 1045,Ё 1025,Ж 1046,
З 1047,И 1048,Й 1049,К 1050,Л 1051,М 1052,Н 1053,О 1054,
П 1055,Р 1056,С 1057,Т 1058,У 1059,Ф 1060,Х 1061,Ц 1062,
Ч 1063,Щ 1065,Ъ 1066,Ы 1067,Ь 1068,Э 1069,Ю 1070,Я 1071,
а 1072,б 1073,в 1074,г 1075,д 1076,е 1077,ё 1105,ж 1078,
з 1079,и 1080,й 1081,к 1082,л 1083,м 1084,н 1085,о 1086,
п 1087,р 1088,с 1089,т 1090,у 1091,ф 1092,х 1093,ц 1094,
ч 1095,щ 1097,ъ 1098,ы 1099,ь 1100,э 1101,ю 1102,я 1103,


В Cach'e ObjectScript есть специальный бинарный оператор ]] — «сортируется после». Он возвращает 1, если в индексе массива левый аргумент будет размещен после правого аргумента, иначе 0:



USER>write ##class(%Library.Collate).SetLocalName("Cache standard"),!
1
USER>write "А" ]] "Ё"
1
USER>write ##class(%Library.Collate).SetLocalName("Cyrillic3"),!
1
USER>write "А" ]] "Ё"
0


Глобалы и таблицы сортировок



Разные глобалы в одной и той же базе данных могут иметь разную сортировку. У каждой базы данных есть настройка — сортировка для новых глобалов. Сразу после инсталляции у всех баз кроме USER сортировка по умолчанию для новых глобалов — Cach'e standard. Для USER — в зависимости от локали инсталляции. Для rusw — Cyrillic3.



Создание глобала с сортировкой отличной от сортировки по умолчанию для данной базы:



USER>kill ^a
USER>write ##class(%GlobalEdit).Create(,"a",##class(%Collate).DisplayToLogical("Cache standard"))


В списке глобалов в Портале управления (Обозреватель Системы > Глобалы) для каждого глобала показывается его сортировка (четвёртый столбец).



Нельзя поменять сортировку существующего глобала. Нужно создать глобал с новой сортировкой и скопировать данные из старого глобала командой merge. Массовую конвертацию глобалов из одной сортировки в другую можно сделать методом ##class(SYS.Database).Copy()



Cyrillic4, Cyrillic3 и умляуты



В процессе эксплуатации Cyrillic3 выяснилось, что преобразование текстового индекса во внутренний формат происходит дольше, чем в сортировке Cach'e standard, поэтому вставка и навигация по глобалу (или локальному массиву) с сортировкой Cyrillic3 выполняется медленней. Была создана новая сортировка Cyrillic4, доступная с версии 2014.1. Порядок букв кириллицы в ней такой же, как и в Cyrillic3, но Cyrillic4 заметно быстрее.



for collation="Cyrillic3","Cyrillic4","Cache standard","Cyrillic1" {
write ##class(%Library.Collate).SetLocalName(collation),!
write ##class(%Library.Collate).GetLocalName(),!
do test(100000)
}
quit
test(C)
set letters = "абвгдеёжзийклмнопрстуфхцчщщьыъэюя"
set letters = letters _ $zconvert(letters,"U")

kill test
write "test insert: "
// заполняем test данными
set z1=$zh
for c=1:1:C {
for i=1:1:$Length(letters) {
set test($Extract(letters,i)_"плюс длинное русское слово" _ $Extract(letters,i)) = ""
}
}
write $zh-z1,!

//перебираем индексы массива test
write "test $Order: "
set z1=$zh
for c=1:1:C {
set l = ""
for {
set l = $Order(test(l))
quit:l=""
}
}
write $zh-z1,!


USER>do ^testcol
1
Cache standard
test insert: 1.520673
test $Order: 2.062228
1
Cyrillic3
test insert: 3.541697
test $Order: 5.938042
1
Cyrillic4
test insert: 1.925205
test $Order: 2.834399


Cyrillic4 пока не является сортировкой по умолчанию в локали rusw, но, создав свою локаль на базе rusw, можно указать Cyrillic4 сортировкой по умолчанию для локальных массивов. Или указать Cyrillic4 сортировкой по умолчанию для новых глобалов в настройках базы данных.



Cyrillic3 медленнее Cach'e standard и Cyrillic4, потому что в её основе лежит алгоритм более общий, чем сортировка двух строк в зависимости от кодов соответствующих символов этих строк.



В немецком языке при сортировке буква ss должна восприниматься как ss. В Cach'e так и работает:



USER>write ##class(%Collate).GetLocalName()
German3
USER>set test("Strasser")=1
USER>set test("Strasser")=1
USER>set test("Straster")=1
USER>zwrite test
test("Strasser")=1
test("Strasser")=1
test("Straster")=1


Обратите внимание на порядок строк. А именно, что первые четыре буквы первой строки — «Stras», затем «Strass», затем опять «Stras». Такого порядка нельзя достичь, если каждой букве сопоставлять некоторый код.



Нам с вами повезло с русским языком — в нём нет умляутов или букв, которые могут сортироваться одинаково, как, например, v и w в финском языке. В русском языке достаточно дать каждой букве номер, и сравнивать строки по номерам букв на соответствующих позициях. За счёт этого и получилось выиграть в скорости в Cyrillic4.



Таблицы сортировок и SQL



Не путайте таблицу сортировки в глобалах и сортировку (тоже collation) для столбца в SQL. Вторая сортировка — преобразование, применяемое к значению, перед тем как положить его в индексный глобал или сравнить с другим значением. В Cach'e SQL сортировка по умолчанию для строк — SQLUPPER. Это преобразование переводит все буквы в верхний регистр, удаляет пробельные символы в конце и добавляет один пробел в начало строки. Три другие SQL сортировки (EXACT, SQLSTRING, TRUNCATE) описаны в документации.


При некотором умении не сложно создать путаницу, когда разные глобалы в базе данных имеют разную сортировку, а локальные массивы — третью. SQL для внутренних нужд использует базу CACHETEMP, сортировка глобалов которой тоже может отличаться от сортировки по умолчанию текущей локали.



Основное правило одно — чтобы ORDER BY в запросах SQL возвращал строки в ожидаемом порядке, сортировка глобалов, в которых хранятся данные и индексы таблиц, участвующих в запросе должна быть такой же как сортировка по умолчанию для базы CACHETEMP и сортировка локальных массивов. Подробнее — смотрите абзац в документации «SQL and NLS Collations».



Создадим тестовый класс:



Class Habr.test Extends %Persistent
{

Property Name As %String;

Property Surname As %String;

Index SurInd On Surname;

ClassMethod populate()
{
do ..%KillExtent()

set t = ..%New()
set t.Name = "Павел", t.Surname = "Ёлкин"
write t.%Save()

set t = ..%New()
set t.Name = "Пётр", t.Surname = "Иванов"
write t.%Save()

set t = ..%New()
set t.Name = "Прохор", t.Surname = "Александров"
write t.%Save()
}

}


Внесём данные (можете потом поменять имена на строки из немецкого примера):



USER>do ##class(Habr.test).populate()


Выполним запрос:





Результат неожиданный. Главный вопрос — почему не алфавитный порядок имён (Павел, Пётр, Прохор)? Смотрим план запроса:





Ключевые слова в этом плане — «populates temp-file». Для выполнения запроса оптимизатор SQL решил использовать временную структуру — temp-file — глобал (в некоторых случаях локальный массив), видный только текущему процессу (process-private global). В индексы этого глобала помещаются значения и потом выводятся в отсортированном порядке. Временные глобалы хранятся в базе CACHETEMP, сортировка дла новых глобалов в которой — Cach'e standard. Но почему «ё» в начале, а не в конце? В индексы временного глобала кладётся значение поля name приведённое к верхнему регистру (SQLUPPERпреобразование по умолчанию для строк), соответственно буква Ё будет идти в самом начале.



Отменив автоматические преобразование (функция %Exact), получим всё ещё неправильный, но хотя бы ожидаемый порядок — «ё» сортируется после всех букв





Не будем пока исправлять таблицу сортировки в CACHETEMP — проверим запросы с surname. Ведь на этот столбец есть индекс в глобале ^Habr.TestI. Сортировка этого глобала — Cyrillic3, значит и порядок строк должен быть алфавитный:



Опять не то. Смотрим план:





Чтобы вывести фамилии в исходном виде (до преобразования SQLUPPER, которое по умолчанию применяется к элементам индекса SurInd) данных только индекса мало и нужно обращаться к таблице, поэтому оптимизатор SQL решил брать данные сразу из таблицы и сортировать их во временной переменной — так же как и в случае с name.



Если указать в запросе, что верхний регистр нас устраивает, то порядок будет верный — ведь данные будут браться напрямую из индексного глобала ^Habr.testI:





План запроса ожидаемый:





Теперь давайте сделаем, что давно нужно было сделать — изменим сортировку по умолчанию для новых глобалов в базе CACHETEMP на Cyrillic3 (или на Cyrillic4).



Запросы, использовавшие временные структуры, теперь выводят строки в правильном порядке:









Выводы




  • Если вас не волнует, в каком порядке отображаются данные с буквой Ё, — используйте таблицу сортировок Cach'e standard.

  • Если вы используете Cyrillic3, протестируйте своё приложение с таблицей сортировки Cyrillic4. Приложение может стать быстрее.

  • Проверьте, что в базе данных CACHETEMP, рабочей базе данных и в настройках локали стоит одна и та же таблица сортировки.





Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/280081/

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
Совет1

Как узнать почтовый индекс

Пятница, 02 Января 2016 г. 01:03 (ссылка)

Что такое почтовый индекс ? Это набор символов, который при отправке письма добавляется к почтовому адресу. Нужен он для того, чтобы сортировка почтовых отправлений происходила быстрее и проще. В России почтовый индекс состоит из шести цифр. Первые три означают код города, последние - номер почтового отделения. В крупных городах (таких, как Москва) может быть несколько кодов городов. ..Далее

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

[Из песочницы] Сортировка масива без использования условных операторов

Вторник, 03 Ноября 2015 г. 13:49 (ссылка)

Сразу предупреждаю, что данная статья будет альтернативной версией вот этой. Да, я уж прям чую как все сразу ринуться меня критиковать, но, ребята, алгоритм представлений в решение не оптимален. В комментариях предлагали еще более простые алгоритмы, на питоне. На питоне достаточно написать:



array.sort()


И все. Кому нужен вообще какой-то алгоритм. В задание было установлено ограничение от 0 до 100, мы же не трусы, сделаем в общем виде, для любых значений и с повторениями чисел. Не много подумав, пришел вот к такому решению:



Код
public class main {

public static int max(int a, int b) {
int i;
for (i = 0; i < a - Math.abs(b);) {
return a;
}
return Math.abs(b);
}
public static void main(String[] args) {
int[] arrayForSort;
int[] sortArray;
int NUM_ELEMENT = 20, maxNum = -1000, i, j;
arrayForSort = new int[NUM_ELEMENT];
for (i = 0; i < NUM_ELEMENT; i++) {
arrayForSort[i] = (int) (Math.random() * 101) - 50;
maxNum = max(maxNum, arrayForSort[i]);
System.out.print(arrayForSort[i] + " ");
}
System.out.println();
sortArray = new int[maxNum * 2 + 1];
for (i = 0; i < NUM_ELEMENT; i++) {
sortArray[arrayForSort[i] + maxNum]++;
}
for (i = 0; i <= maxNum * 2; i++) {
for (j = 0; j < sortArray[i]; j++) {
System.out.print(i - maxNum + " ");
}
}
}
}




А теперь, разберем что я тут написал.



В общем-то, проблема состоит в нахождение минимума и максимума, так как сравнение мы не можем использовать, то:



public static int max(int a, int b) {
int i;
for (i = 0; i < a - Math.abs(b);) {
return a;
}
return Math.abs(b);
}


Если B по модулю будет больше чем А, то цикл попросту не выполниться и пройдет на возврат. Это довольно простое решение. Без «великих» математических формул. Просто и понятно. И еще: почему я беру по модулю? Это вызвано методом сортировки, который я использую.



В оригинальной статье используется сортировка «с флажком» (если я правильно понял). Выполнение такой сортировки при самом плохом случае О(N^2) (или близко к этому), что не есть хорошо.



Этот метод (не помню его название, искать не царское это дело лень), решит за О(N) даже при том что числа будут повторяться.



for (i = 0; i < NUM_ELEMENT; i++) {
sortArray[arrayForSort[i] + maxNum]++;
}


Суть сортировки заключаеться в том что при заполнение массива он сам сортирует себя. Значение мы представляем в виде индекса и увеличиваем количество элементов в этом индексе.



Пример
Мы два раза встретили число 44, значит в отсортированном массиве по индексу 44 будет содержаться 2. Это просто!



Как оказалось (то ли я криворукий), массивы создаються от 0 до N и как я не старался сделать от -N до N, без успешно. По этому делаем смещение, по этому ищем максимум с модулем. Потом просто отражаем относительно 0 и получаем диапазон индексов в который точно влезут все элементы, кроме граничного, так что +1.



Пояснение
Мы получим минимум -48, а максимум 38. Так мы берем что минимум -48 а макс 48, для упрощение алгоритма. И смещаем так что-бы минимум был на 0 -48+48



sortArray = new int[maxNum * 2 + 1];
. . .
sortArray[arrayForSort[i] + maxNum]++;
. . .
System.out.print(i - maxNum + " ");


На этом у меня все. Поставленную задачу выполнил при этом оптимизировав процесс и представив свое виденье решения данной задачи.



Пример вывода
35 -29 26 17 -44 -10 31 -22 24 2 -28 17 2 -36 -30 35 39 -35 41 50

-44 -36 -35 -30 -29 -28 -22 -10 2 2 17 17 24 26 31 35 35 39 41 50



Original source: habrahabr.ru (comments, light).

http://habrahabr.ru/post/270071/

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

[Из песочницы] Внешняя сортировка с O(1) дополнительной памяти

Пятница, 09 Октября 2015 г. 13:25 (ссылка)

Прочитав эту статью, я вспомнил, как писал внешнюю сортировку, которая использовала O(1) внешней памяти. Функция получала бинарый файл и максимальный размер памяти, которую она могла выделить под массив:



void ext_sort(const std::string filename, const size_t memory)


Я использовал алгоритм из Effective Performance of External Sorting with No Additional Disk Space:




  1. Разделим файл на блоки, которые помещаются в доступную память. Обозначим эти блоки Block_1, Block_2, …, Block_(S-1), Block_S. Установим P = 1.

  2. Читаем Block_P в память.

  3. Отсортируем данные в памяти и запишем назад в Block_P. Установим P = P + 1, и если P <= S, то читаем Block_P в память и повторяем этот шаг. Другими словами, отсортируем каждый блок файла.

  4. Разделим каждый блок на меньшие блоки B_1 и B_2. Каждый из таких блоков занимает половину доступной памяти.

  5. Читаем блок B_1 блока Block_1 в первую половину доступной памяти. Установим Q = 2.

  6. Читаем блок B_1 блока Block_Q во вторую половину доступной памяти.

  7. Объеденим массивы в памяти с помощью in-place слияния, запишем вторую половину памяти в блок B_1 блока Block_Q и установим Q = Q + 1, если Q <= S, читаем блок B_1 блока Block_Q во вторую половину доступной памяти и повторяем этот шаг.

  8. Записываем первую половину доступной памяти в блок B_1 блока Block_1. Так как мы всегда оставляли в памяти меньшую половину элементов и провели слияние со всеми блоками, то в этой части памяти хранятся M минимальных элементы всего файла.

  9. Читаем блок B_2 блока Block_S во вторую половину доступной памяти. Установим Q = S -1.

  10. Читаем блок B_2 блока Block_Q в первую половину доступной памяти.

  11. Объеденим массивы в памяти с помощью in-place слияния, запишем первую половину доступной памяти в блок B_2 блока Block_Q и установим Q = Q -1. Если Q >= 1 читаем блок B_2 блока Block_Q в первую половину доступной памяти и повторяем этот шаг.

  12. Записываем вторую половину доступной памяти в блок B_2 блока Block_S. Аналогично шагу 8, тут хранятся максимальные элементы всего файла.

  13. Начиная от блока B_2 блока Block_1 и до блока B_1 блока Block_S, определим новые блоки в файле и снова пронумеруем их Block_1 to Block_S. Разделим каждый блок на блоки B_1 и B_2. Установим P = 1.

  14. Читаем B_1 и B_2 блока Block_P в память. Объеденим массивы в памяти. запишем отсортированный массив назад в Block_P и установим P = P +1. Если P <= S, повторяем этот шаг.

  15. Если S > 1, возвращаемся к шагу 5. Каждый раз мы выделяем M минимальных и максимальных элементов, записываем их в начало и конец файла соответственно, а потом делаем то же самое с оставшимися элементами, пока не дойдем до середины файла.



Преимущество такого алгоритма, кроме отсутствия буфера на диске, это то, что с диска мы читаем данные относительно большими порциями, что ускоряет алгоритм.



Реализуем алгоритм на C++.



Для начала определим количество блоков и размер блока в байтах и в элементах и выделим память:



	const size_t type_size = sizeof(T);
const uint64_t filesize = file_size(filename);
std::fstream data(filename, std::ios::in | std::ios::out | std::ios::binary);
const uint64_t chunk_number = filesize / memory;
const size_t chunk_size = memory / type_size - (memory / type_size) % 2, chunk_byte_size =
chunk_size * type_size, half_chunk_byte_size = chunk_byte_size / 2, half_chunk_size = chunk_size / 2;

std::vector *chunk = new std::vector(chunk_size);


Теперь пункты 2-3 — сортируем каждый блок:



	for (uint64_t i = 0; i < chunk_number; ++i) {
data.seekg(chunk_byte_size * i);
data.read((char *) chunk->data(), chunk_byte_size);
flat_quick_sort(chunk->begin(), chunk->end());
data.seekp(chunk_byte_size * i);
data.write((char *) chunk->data(), chunk_byte_size);
}


Саму сортировку мы напишем чуть позднее.



Приступим к слияниям. Нижняя половина:



	int64_t s = chunk_number, start = 0;
while (s > 0) {
data.seekp(half_chunk_byte_size * start);
data.read((char *) chunk->data(), half_chunk_byte_size);
for (int64_t q = 1; q < s; ++q) {
data.seekg(half_chunk_byte_size * start + chunk_byte_size * q);
data.read((char *) (chunk->data() + half_chunk_size), half_chunk_byte_size);
in_place_merge(chunk->begin(), chunk->begin() + half_chunk_size - 1, chunk->begin() + chunk_size);
data.seekp(half_chunk_byte_size * start + chunk_byte_size * q);
data.write((char *) (chunk->data() + half_chunk_size), half_chunk_byte_size);
}
data.seekp(half_chunk_byte_size * start);
data.write((char *) chunk->data(), half_chunk_byte_size);


И аналогично верхняя:



		data.seekp(half_chunk_byte_size * start + chunk_byte_size * s - half_chunk_byte_size);
data.read((char *) (chunk->data() + half_chunk_size), half_chunk_byte_size);
for (int64_t q = s - 2; q >= 0; --q) {
data.seekg(half_chunk_byte_size * (start + 1) + chunk_byte_size * q);
data.read((char *) chunk->data(), half_chunk_byte_size);
in_place_merge(chunk->begin(), chunk->begin() + half_chunk_size - 1, chunk->begin() + chunk_size);
data.seekp(half_chunk_byte_size * (start + 1) + chunk_byte_size * q);
data.write((char *) chunk->data(), half_chunk_byte_size);
}
data.seekg(half_chunk_byte_size * start + chunk_byte_size * s - half_chunk_byte_size);
data.write((char *) (chunk->data() + half_chunk_size), half_chunk_byte_size);


Перераспределяем блоки, завершаем цикл и не забываем освободить память:



		--s;
++start;
for (int64_t p = 0; p < s; ++p) {
data.seekp(half_chunk_byte_size * start + chunk_byte_size * p);
data.read((char *) chunk->data(), chunk_byte_size);
in_place_merge(chunk->begin(), chunk->begin() + half_chunk_size - 1, chunk->begin() + chunk_size);
data.seekg(half_chunk_byte_size * start + chunk_byte_size * p);
data.write((char *) chunk->data(), chunk_byte_size);
}
}

delete chunk;
}


Функция полностью
template
void ext_sort(const std::string filename, const size_t memory) {

const size_t type_size = sizeof(T);
const uint64_t filesize = file_size(filename);
std::fstream data(filename, std::ios::in | std::ios::out | std::ios::binary);
const uint64_t chunk_number = filesize / memory;
const size_t chunk_size = memory / type_size - (memory / type_size) % 2, chunk_byte_size =
chunk_size * type_size, half_chunk_byte_size = chunk_byte_size / 2, half_chunk_size = chunk_size / 2;

std::vector *chunk = new std::vector(chunk_size);
for (uint64_t i = 0; i < chunk_number; ++i) {
data.seekg(chunk_byte_size * i);
data.read((char *) chunk->data(), chunk_byte_size);
flat_quick_sort(chunk->begin(), chunk->end());
data.seekp(chunk_byte_size * i);
data.write((char *) chunk->data(), chunk_byte_size);
}

int64_t s = chunk_number, start = 0;
while (s > 0) {
data.seekp(half_chunk_byte_size * start);
data.read((char *) chunk->data(), half_chunk_byte_size);
for (int64_t q = 1; q < s; ++q) {
data.seekg(half_chunk_byte_size * start + chunk_byte_size * q);
data.read((char *) (chunk->data() + half_chunk_size), half_chunk_byte_size);
in_place_merge(chunk->begin(), chunk->begin() + half_chunk_size - 1, chunk->begin() + chunk_size);
data.seekp(half_chunk_byte_size * start + chunk_byte_size * q);
data.write((char *) (chunk->data() + half_chunk_size), half_chunk_byte_size);
}
data.seekp(half_chunk_byte_size * start);
data.write((char *) chunk->data(), half_chunk_byte_size);

data.seekp(half_chunk_byte_size * start + chunk_byte_size * s - half_chunk_byte_size);
data.read((char *) (chunk->data() + half_chunk_size), half_chunk_byte_size);
for (int64_t q = s - 2; q >= 0; --q) {
data.seekg(half_chunk_byte_size * (start + 1) + chunk_byte_size * q);
data.read((char *) chunk->data(), half_chunk_byte_size);
in_place_merge(chunk->begin(), chunk->begin() + half_chunk_size - 1, chunk->begin() + chunk_size);
data.seekp(half_chunk_byte_size * (start + 1) + chunk_byte_size * q);
data.write((char *) chunk->data(), half_chunk_byte_size);
}
data.seekg(half_chunk_byte_size * start + chunk_byte_size * s - half_chunk_byte_size);
data.write((char *) (chunk->data() + half_chunk_size), half_chunk_byte_size);
--s;
++start;
for (int64_t p = 0; p < s; ++p) {
data.seekp(half_chunk_byte_size * start + chunk_byte_size * p);
data.read((char *) chunk->data(), chunk_byte_size);
in_place_merge(chunk->begin(), chunk->begin() + half_chunk_size - 1, chunk->begin() + chunk_size);
data.seekg(half_chunk_byte_size * start + chunk_byte_size * p);
data.write((char *) chunk->data(), chunk_byte_size);
}
}

delete chunk;
}




Осталось реализовать функции flat_quick_sort и in_place_merge. Идею (и большую часть реализации) flat quick sort я взял в статье хабраюзера ripatti. Я только заменил median of medians (посчитал это оверкиллом в среднем случае) на median-of-nine и добавил сортировку вставками для маленьких частей массива.



flat_quicksort.h
#ifndef EXTERNAL_SORT_FLAT_QUICKSORT_H
#define EXTERNAL_SORT_FLAT_QUICKSORT_H

template
void insertion_sort(ForwIt be, ForwIt en) {
for (ForwIt ii = be + 1; ii != en; ii++) {
ForwIt jj = ii - 1;
auto val = *ii;
while (jj >= be and *jj > val) {
*(jj + 1) = *jj;
--jj;
}
*(jj + 1) = val;
}
}

template
ForwIt median_of_3(ForwIt it1, ForwIt it2, ForwIt it3) {
return (*it1 > *it2) ?
(*it3 > *it2) ? (*it1 > *it3) ? it3 : it1 : it2 :
(*it3 > *it1) ? (*it2 > *it3) ? it3 : it2 : it1;
}

template
ForwIt choose_pivot(ForwIt be, ForwIt en) {
ptrdiff_t s = (en - be) / 8;
ForwIt mid = be + (en - be) / 2;
ForwIt best1 = median_of_3(be, be + s, be + 2 * s), best2 = median_of_3(mid - s, mid, mid + s), best3 = median_of_3(
en - 2 * s, en - s, en);
return median_of_3(best1, best2, best3);
}

// search for the end of the current block
template
ForwIt block_range(ForwIt be, ForwIt en) {
ForwIt it = be;
while (it != en) {
if (*be < *it)
return it;
++it;
}
return it;
}

// warning: after the partition outer pivot may point to random element
template
std::pair partition_range(ForwIt be, ForwIt en, ForwIt pivot) {
std::pair re;
ForwIt j = be;
for (ForwIt i = be; i != en; ++i)
if (*i < *pivot) {
if (pivot == i) pivot = j; else if (pivot == j) pivot = i;
std::swap(*j, *i);
++j;
}
re.first = j;
for (ForwIt i = j; i != en; ++i)
if (*pivot == *i) {
if (pivot == i) pivot = j; else if (pivot == j) pivot = i;
std::swap(*j, *i);
++j;
}
re.second = j;
return re;
}
// makes largest element the first
template
void blockify(ForwIt be, ForwIt en) {
for (ForwIt it = be; it != en; ++it)
if (*be < *it)
std::swap(*be, *it);
}

template
void flat_quick_sort(ForwIt be, ForwIt en) {
ForwIt tmp = en; // the current end of block
while (be != en) {
if (std::is_sorted(be, tmp)) {
be = tmp;
tmp = block_range(be, en);
continue;
}
if (tmp - be < 32)
insertion_sort(be, tmp);
else {
ForwIt pivot = choose_pivot(be, tmp);
std::pair range = partition_range(be, tmp, pivot);
blockify(range.second, tmp);
tmp = range.first;
}
}
}

#endif //EXTERNAL_SORT_FLAT_QUICKSORT_H




Со слиянием было сложнее. Сначала я использовал заглушку, использующую O(n) памяти:



template
void merge(std::vector &chunk, size_t s, size_t q, size_t r) {
std::vector *chunk2 = new std::vector(q - s + 1);
auto it2 = chunk2->begin(), it1 = chunk.begin() + q + 1, it = chunk.begin() + s;
std::copy(it, it1, it2);
while (it2 != chunk2->end() && it1 != chunk.begin() + r + 1) {
if (*it1 > *it2) {
*it = *it2;
++it2;
} else {
*it = *it1;
++it1;
}
++it;
}
if (it1 == chunk.begin() + r + 1)
std::copy(it2, chunk2->end(), it);
else
std::copy(it1, chunk.begin() + r + 1, it);
delete chunk2;
}


Когда я захотел заменить заглушку in-place версией, оказалось, что быстрые алгоритмы in-place слияния в большинстве своем достаточно запутанные (посмотрите, например, On optimal and efficient in place merging). Мне надо было что-то попроще, и я выбрал алгоритм из статьи A simple algorithm for in-place merging:



in-place merge
template
void merge_B_and_Y(Iter z, Iter y, Iter yn) {
for (; z < y && y < yn; ++z) {
Iter j = std::min_element(z, y);
if (*j <= *y)
std::swap(*z, *j);
else {
std::swap(*z, *y);
++y;
}
}
if (z < y)
flat_quick_sort(z, yn);
}

template
Iter find_next_X_block(Iter x0, Iter z, Iter y, size_t k, size_t f, Iter b1,
Iter b2, auto max) {
auto min1 = max, min2 = max;
Iter m = x0 + (ptrdiff_t) floor((z - x0 - f) / k) * k + f, x = x0;
if (m <= z)
m += k;

for (auto i = m; i + k <= y; i += k) {
if (i != b1 && i != b2) {
Iter j = (i < b1 && b1 < i + k) ? m - 1 : i + k - 1;
if (*i <= min1 && *j <= min2) {
x = i;
min1 = *i;
min2 = *j;
}
}
}
return x;
}

template
void in_place_merge(Iter x0, Iter y0, Iter yn, int64_t k, bool rec) {
if (k == -1)
k = (int64_t) sqrt(yn - x0);
size_t f = (y0 - x0) % k;
Iter x = (f == 0) ? y0 - 2 * k : y0 - k - f;
auto t = *x, max = *std::max_element(x0, yn);
*x = *x0;
Iter z = x0, y = y0, b1 = x + 1, b2 = y0 - k;
int i = 0;
while (y - z > 2 * k) {
++i;
if (*x <= *y || y >= yn) {
*z = *x;
*x = *b1;
++x;
if ((x - x0) % k == f) if (z < x - k)
b2 = x - k;
x = find_next_X_block(x0, z, y, k, f, b1, b2, max);
} else {
*z = *y;
*y = *b1;
++y;
if ((y - y0) % k == 0)
b2 = y - k;
}
++z;
*b1 = *z;
if (z == x)
x = b1;
if (z == b2)
b2 = yn + 1;
++b1;
if ((b1 - x0) % k == f)
b1 = (b2 == yn + 1) ? b1 - k : b2;
}
*z = t;
if (rec)
merge_B_and_Y(z, y, yn);
else {
flat_quick_sort(z, y);
in_place_merge(z,y,yn,(int64_t)sqrt(k),true);
}
}




Но на моем компьютере замена merge на in-place merge замедляла алгоритм почти на порядок. Возможно я ошибся в реализации или просто выбрал медленный алгоритм в погоне за простотой. Времени разбираться, как всегда, не было, к тому же gprof почему-то падал. И тут меня осенило. Если мы выделям M байт динамической памяти, то не важно, как мы её используем, мы все равно получаем O(1). Тогда просто выделим 2/3 под данные, а треть — под буфер слияния. Замедление будет гораздо меньше. И правда:






































Алгоритм Время (75MB int64 в 7,5MB памяти) Скорость (75MB int64 в 7,5MB памяти) Время (7,5MB int64 в 75KB памяти) Скорость (7,5MB int64 в 75KB памяти) Время (750MB int64 в 75MB памяти) Скорость (750MB int64 в 75MB памяти)
In-place merge 6.04329 s 1 241 045 B/s 24.2993 s 3 086 508 B/s - -
Merge 0.932663 s 8 041 489 B/s 2.73895 s 27 382 756 B/s 47.7946 s 15 691 689 B/s
Алгоритм SLY_G 1.79629 s 4175272 B/s 3.84775 s 19 491 910 B/s 39.77 s 18 858 436 B/s
К сожалению, на больших объемах алгоритм замедляется, что ожидаемо, ведь мы не используем никакого буфера вообще. Тем не менее, скорость алгоритма достаточно адекватная, и, я уверен, может быть улучшена.



Все исходники лежат здесь.

Original source: habrahabr.ru (comments, light).

http://habrahabr.ru/post/268535/

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
lenov_ru

AIMP » Русский Google Play - игры Android без вирусов и регистрации

Воскресенье, 26 Апреля 2015 г. 07:58 (ссылка)
mod-hak.ru/apps/multimedia/104-aimp.html


AIMP - Мощный музыкальный плеер от отечественных разработчиков, который пришел к нам с персонального компьютера. Данный плеер воспроизводит все известные музыкальные форматы, которые существуют на

Комментарии (0)КомментироватьВ цитатник или сообщество

Следующие 30  »

<сортировка - Самое интересное в блогах

Страницы: [1] 2 3 ..
.. 10

LiveInternet.Ru Ссылки: на главную|почта|знакомства|одноклассники|фото|открытки|тесты|чат
О проекте: помощь|контакты|разместить рекламу|версия для pda