Преобразование Шварца - Schwartzian transform

В компьютерное программирование, то Преобразование Шварца это метод, используемый для повышения эффективности сортировка список предметов. Этот идиома[1] подходит для сортировка на основе сравнения когда заказ фактически основан на заказе определенного свойства ( ключ) элементов, где вычисление этого свойства является интенсивной операцией, которую следует выполнять минимальное количество раз. Преобразование Шварца примечательно тем, что в нем не используются именованные временные массивы.

Преобразование Шварца - это вариант Лисп идиома известная как украсить-сортировать-убрать, что позволяет избежать повторного вычисления ключей сортировки путем временного связывания их с элементами ввода. Этот подход похож на мемоизация, что позволяет избежать повторения вычисления ключа, соответствующего определенному входному значению. Для сравнения, эта идиома гарантирует, что ключ каждого входного элемента вычисляется ровно один раз, что может привести к повторению некоторых вычислений, если входные данные содержат повторяющиеся элементы.

Идиома названа в честь Рэндал Л. Шварц, который впервые продемонстрировал это в Perl вскоре после выпуска Perl 5 в 1994 году. Термин «преобразование Шварца» применялся исключительно к Perl программирование в течение нескольких лет, но позже он был принят некоторыми пользователями других языки, Такие как Python, чтобы ссылаться на похожие идиомы в этих языках. Однако алгоритм уже использовался на других языках (без определенного названия) до того, как Шварц популяризировал его в сообществе Perl в форме этой конкретной идиомы. Термин «преобразование Шварца» указывает на конкретную идиому, и нет алгоритм в целом.

Например, чтобы отсортировать список слов («аааа», «а», «аа») по длине слова: сначала создайте список ([«аааа», 4], [«а», 1], [«аа ", 2]), затем отсортируйте его согласно полученным числовым значениям ([" a ", 1], [" aa ", 2], [" aaaa ", 4]), затем удалите числа, и вы получите ( «а», «аа», «аааа»). Это был алгоритм в целом, поэтому он не считается преобразованием. Чтобы сделать его истинным преобразованием Шварца, это нужно сделать на Perl следующим образом:

@sorted = карта  { $_->[0] }          Сортировать { $ а->[1] <=> $ млрд->[1] или же $ а->[0] cmp $ млрд->[0] } # Использовать числовое сравнение, вернуться к сортировке строк в оригинале          карта  { [$_, длина($_)] }    # Вычислить длину строки               @ unsorted;

Идиома Perl

Общая форма преобразования Шварца:

@sorted = карта  { $_->[0] }          Сортировать { $ а->[1] cmp $ млрд->[1] или же $ а->[0] cmp $ млрд->[0] }          карта  { [$_, фу($_)] }               @ unsorted;

Здесь foo ($ _) представляет собой выражение, которое принимает $_ (каждый элемент списка по очереди) и выдает соответствующее значение, которое будет сравниваться вместо него.

Чтение справа налево (или снизу вверх):

  • исходный список @ unsorted подается в карта операция, которая превращает каждый элемент в массив (ссылка на анонимный 2-элементный), состоящий из самого себя и вычисленного значения, которое будет определять его порядок сортировки (список элементов становится списком [элемент, значение]);
  • затем список списков, созданных карта подается в Сортировать, который сортирует его согласно ранее вычисленным значениям (список [элемент, значение] ⇒ отсортированный список [элемент, значение]);
  • наконец, еще один карта операция разворачивает значения (из анонимного массива), используемые для сортировки, создавая элементы исходного списка в отсортированном порядке (отсортированный список из [элемент, значение] ⇒ отсортированный список элементов).

Использование анонимных массивов гарантирует, что память будет освобождена сборщиком мусора Perl сразу после завершения сортировки.

Анализ эффективности

Без преобразования Шварца сортировка в приведенном выше примере была бы написана на Perl следующим образом:

@sorted = Сортировать { фу($ а) cmp фу($ млрд) } @ unsorted;

Хотя код короче, наивный подход здесь может быть гораздо менее эффективным, если ключевая функция (называемая фу в приведенном выше примере) требует больших затрат на вычисление. Это связано с тем, что код в скобках оценивается каждый раз, когда необходимо сравнить два элемента. Оптимальный сортировка сравнения выполняет О (п войти п) сравнения (где п - длина списка), с 2 вызовами фу каждое сравнение, в результате О(п войти п) звонки фу. Для сравнения, используя преобразование Шварца, мы делаем только 1 вызов фу за элемент, в начале карта этап, в общей сложности п звонки в фу.

Однако если функция фу относительно просто, то дополнительные накладные расходы на преобразование Шварца могут быть неоправданными.

Пример

Например, чтобы отсортировать список файлов по их время модификации, наивный подход может быть таким:

 функция naiveCompare (файл a, файл b) { возвращаться Время модификации (а) <Время модификации (б)} // Предположим, что sort (list, comparePredicate) сортирует данный список, используя // Предикат сравнения для сравнения двух элементов. sortedArray: = sort (filesArray, naiveCompare)

Если время модификации не запомнено для каждого файла, этот метод требует повторного вычисления их каждый раз, когда файл сравнивается при сортировке. Используя преобразование Шварца, время модификации рассчитывается только один раз для каждого файла.

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

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

 для каждого файл в filesArray вставить массив (файл, модификацияTime (файл)) в конец transformedArray функция simpleCompare (массив a, массив b) { возвращаться a [2] для каждого файл в transformedArray вставить файл [1] в конец sortedArray

История

Первое известное появление в сети преобразования Шварца - 16 декабря 1994 г. публикация Рэндала Шварца в поток в comp.unix.shell Группа новостей Usenet, перекрестно с comp.lang.perl. (Текущая версия Временная шкала Perl неверно и относится к более поздней дате в 1995 году.) Цикл начался с вопроса о том, как отсортировать список строк по их "последнему" слову:

адл: Джошуа Нгадктк: КаЛап Тимоти Квонгадмг: Махалингам Гобиерамананадмлн: Марта Л. Нангалама

Шварц ответил:

#! / usr / bin / perlтребовать 5; # Новые функции, новые ошибки!Распечатать    карта { $_->[0] }    Сортировать { $ а->[1] cmp $ млрд->[1] }    карта { [$_, / ( S +) $ /] }    <>;

Этот код дает результат:

admg: Махалингам Gobieramananadktk: KaLap Тимоти Kwongadmln: Martha L. Nangalamaadjn: Джошуа Нг

Шварц отметил в сообщении, что он «шепелявил в Perl», отсылка к идиоме Лисп происхождение.

Сам термин «преобразование Шварца» был придуман Том Кристиансен в последующем ответе. Более поздние сообщения Кристиансена ясно дали понять, что он не собирался имя конструкции, а просто для того, чтобы сослаться на нее из исходного сообщения: его попытка окончательно назвать ее «Черным преобразованием» не увенчалась успехом («Черный» здесь - каламбур на «schwar [t] z», что означает черный Немецкий).

Сравнение с другими языками

Некоторые другие языки предоставляют удобный интерфейс для той же оптимизации, что и преобразование Шварца:

  • В Python 2.4 и выше, как отсортировано () функция и на месте list.sort () метод взять ключ = параметр, который позволяет пользователю указать «ключевую функцию» (например, фу в примерах выше). В Python 3 и выше использование ключевой функции - единственный способ указать собственный порядок сортировки (ранее поддерживаемый аргумент компаратора был удален). До Python 2.4 разработчики использовали бы идиому decorate-sort-undecorate (DSU), возникшую из lisp,[2] обычно путем упаковки объектов в кортеж (ключ сортировки, объект).
  • В Рубин 1.8.6 и выше, Перечислимый абстрактный класс (который включает Множествоs) содержит Сортировать по[3] метод, позволяющий указать «ключевую функцию» (например, фу в приведенных выше примерах) как блок кода.
  • В D 2 и выше, schwartzSort функция доступна. Это может потребовать меньше временных данных и быть быстрее, чем идиома Perl или идиома «украсить-сортировать-не украшать», присутствующая в Python и Lisp. Это связано с тем, что сортировка выполняется на месте, и создается только минимальный дополнительный объем данных (один массив преобразованных элементов).
  • Ракетки основной Сортировать функция принимает #:ключ аргумент ключевого слова с функцией, которая извлекает ключ, и дополнительным #: ключи кеша? запрашивает кэширование полученных значений во время сортировки. Например, удобный способ перемешать список - (Сортировать л < #:ключ (λ (_) (случайный)) #: ключи кеша? #t).
  • В PHP 5.3 и выше преобразование может быть реализовано с помощью array_walk, например обойти ограничения нестабильных алгоритмов сортировки в PHP.
    функция spaceballs_sort(множество& $ а): пустота{    array_walk($ а, функция(&$ v, $ k) { $ v = множество($ v, $ k); });    asort($ а);    array_walk($ а, функция(&$ v, $_) { $ v = $ v[0]; });}
  • В Эликсир, то Enum.sort_by / 2 и Enum.sort_by / 3 методы позволяют пользователям выполнять преобразование Шварца для любого модуля, реализующего Перечислимый протокол.
  • В Раку, необходимо предоставить лямбда-выражение компаратора, которое принимает только 1 аргумент для выполнения скрытого преобразования Шварца:
    @a.Сортировать( { $ ^ а.Ул. } ) # или короче: @ a.sort (*. Str)
    будет сортировать строковое представление с помощью преобразования Шварца,
    @a.Сортировать( { $ ^ а.Ул. cmp $ ^ b.Ул. } )
    будет делать то же самое, преобразовывая элементы для сравнения непосредственно перед каждым сравнением.

Рекомендации

  1. ^ Мартелли, Алекс; Ашер, Дэвид, ред. (2002). «2.3 Сортировка при обеспечении стабильности сортировки». Поваренная книга Python. O'Reilly & Associates. п.43. ISBN  0-596-00167-3. Эта идиома также известна как «преобразование Шварца» по аналогии с родственной идиомой Perl.
  2. ^ «Как / Сортировка / Украсить Сортировка Без Украсить».
  3. ^ "Классы Core-API Ruby-doc". Получено 14 сентября 2011.

внешняя ссылка