Pull to refresh

Comments 45

Браво. Читал с удовольствием.

но меня задолбало это это набирать

Ну есть же замена в редакторе, это быстро.

И вот тут кажется мы можем нащупать какую-то пользу из нашего эксперимента. Мы получили действую модель оптимизации алгоритма под исходные данные. Если мы что-то знаем про частотность перестановок в исходных данных, мы можем подогнать под них алгоритм сортировки и получить гарантированно самую быструю сортировку. Быстрее теоретических чисел сортировки.

Ну вот, а когда я пишу что сортировка зависит от контекста данных (да и не только сортировка, но и кодирование, сжатие и т.д.), то меня закидывают ссаными тряпками, мол не трогай святое пользуйся универсальными методами и вообще, если метод не универсальный и не стабильный то он фуфуфу.

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

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

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

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

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

Нет же. Они гонят все в gzip или те что более продвинутые в zstd. И не парятся.

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

кстати PNG по сути как раз сжимает данные в зависимости от контекста, а вот насколько хорошо, то уже зависит от реализации, поэтому я обычно за paint.net и photoshop еще файлы дожимаю с помощью Pingo. У меня на сетевом диске есть библиотека PNG файлов для рисования всякого, размер около 4 Гб, я прогнал как-то ее через Pingo и уменьшил размер до 3.2Гб.

Подобрать более подходящий алгоритм под паттерн данных на стадии проектирования можно. И так делают иногда. Хотя как правило все равно просто берут gzip или zstd.

Это все равно безмерно далеко от анализа текущего потока и подбора чего-то под него. Один раз спроектировали, написали и оно крутится годами.

PS: Правильная настройка словаря для zstd дает больше чем практически все остальное (работающее со сравнимой скоростью) на почти любом паттерне данных.

настройка словаря - это и есть контекстная настройка, каким методом вы решили задачу не так важно. я вам писал ответ на ваши "берут gzip и не парятся", а вы тут же пишете что "настраивают словарь", то есть заморочились таки.

В подавляющем большинстве случаев берут гзип и не парятся. Оно того не стоит. В остальных случаях стоит подумать о чем-то вроде настройки словаря zstd.

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

То что кто-то не слишком умный это понятно, но мы же не такие, а значит выбор стратегии имеет место быть. Ну и разговор идёт об обычных архиваторах, а не о бизнес-моделях, а там zip/7z/rar где Deflate совсем не единственный метод, скорее уж вы попадёте на LZMA.

Лично я для себя пользуюсь zpaq (m5) и 7zip(bzip2 или ppmd) и за 50 лет ни разу не пользовался ни gzip ни zstd.

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

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

zpaq
zpaq

Пример тупой сортировки (не стоит её повторять):

#include

#define N 10

int main()
{
int vec[N] = {1, 7, 3, -7, -1, 7, 9, -2, 5, 8};
for (auto i = 0; i < N - 1; ++i)
if ((vec[i] > vec[i + 1])) {
std::swap(vec[i], vec[i + 1]); i = -1;
}
for (auto i = 0; i < N; ++i) std::cout << vec[i] << "\t";
return EXIT_SUCCESS;
}

Никак не пойму, зачем писать auto i =0? int и на один символ короче, и нагляднее...

Согласен, это победитель)

По сложности он идентичен самому первую варианту в статье. Так что они тут делят первое место по тупости :)

По идее у Bogosort тупость всё же чуть выше (если не смотреть на память). Дело в сохранении информации — ваш алгоритм гарантированно проверяет каждый вариант максимум один раз, и с каждым шагом он всё ближе к получению "правильного" массива; в то же время Bogosort не ограничен от "топтания на месте" ничем. Так что O(n*n!) у вас соответствует максимальному времени сортировки, а у Bogosort — среднему… впрочем, его преимущество в том, что памяти почти не нужно.

Ох, насколько же приятно выглядит мобильная википедия на десктопе!

Ой. Простите. Копипастил ссылку с телефона, забыл .m. убрать.

Редактировать коммент уже не могу. Надеюсь это не проблема? Вот ссылка на не мобильную версию вики.

https://ru.wikipedia.org/wiki/Bogosort

Блин, как начал читать статью, тут же подумал об этом алгоритме, но оказалось, что его уже придумали (

Я больше SlowSort предпочитаю


никакого рандома
экспоненциальная сложность

Моя номинация на самую тупую сортировку:

Java
import java.util.Arrays;

public class Test {

  public static void main(String[] args) {

    final int[] arr = {1, 7, 3, -7, -1, 7, 9, -2, 5, 8};
    final int[] sorted = naiveSort(arr);

    System.out.println(Arrays.toString(arr));
    System.out.println(testSorted(arr));
    System.out.println(Arrays.toString(sorted));
    System.out.println(testSorted(sorted));

  }

  private static int[] naiveSort(int[] arr) {

    final int[] sorted = new int[arr.length];
    final boolean[] checked = new boolean[arr.length];
    Arrays.fill(checked, false);

    for (int i = 0; i < arr.length; i++) {
      sorted[i] = findMin(arr, checked);
    }

    return sorted;
  }

  private static int findMin(int[] arr, boolean[] checked) {
    int min = Integer.MAX_VALUE;
    int minInd = 0;

    for (int i = 0; i < arr.length; i++) {
      if (!checked[i] && arr[i] <= min) {
        min = arr[i];
        minInd = i;
      }
    }

    checked[minInd] = true;
    return min;
  }

  private static String testSorted(int[] arr) {

    for (int i = 0; i < arr.length - 1; i++) {
      if (arr[i] > arr[i + 1]) {
        return  "Array is not sorted";
      }
    }

    return "Array is sorted";
  }

}

Хотя это скорее не тупая, а "наивная". Т.е. если бы я ничего не знал о программировании (ну, кроме синтаксиса), я бы написал именно так. Даже то что можно просто поменять местами два элемента - это какая-то иезуитская хитрость :) Я бы наверно сам не допёр, по крайней мере, не сразу.

это ж по сути сортировка выбором, только складывают не в отдельный массив, а в начало/конец сортируемого, сдвигая неотсортированное.

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

это ж по сути сортировка выбором

:)

У меня самый простой вариант:

    public void sort(List<Integer> list) {
        while (!isSorted(list)) {
            Collections.shuffle(list);
        }
    }

    private boolean isSorted(List<Integer> list) {
        for (int i = 0; i < list.size() - 1; i++) {
            if (list.get(i) > list.get(i + 1))
                return false;
        }

        return true;
    }

У вас он еще и некорректный, потому что если числа повторяются, список никогда не отсортируется :)

Конечно, это юмористическая статья, но подобная сортировка оптимизированная под размер массива (4-9 элементов) существует в прод-коде. Если не изменяет память, разработчики гугла применили такое в одной из библиотек и увеличили скорость сортировки внутри sql-запросов до 30 %.

Реализация libcxx обрабатывала несколько конкретных случаев особым образом. Коллекции длиной 5 или меньше сортировались при помощи специальных самописных сортировок на основе сравнений.
https://habr.com/ru/articles/662181/

Вероятно вы правы. Не удивительно. Идея же лежит на поверхности.

Адаптивное сжатие, зависящее от контекста - оно же в полный рост используется во всём, что использует вейвлеты. Там для каждого типа "сигнала" подбирается соответствующий вейвлет и количество коэффициентов, обращающихся в "0" или близких к "0" (которые можно отбросить) от этого увеличивается.

который я где-то пролюбил

fixed. Вот среднее число сравнений если каждый раз выбирать индексы жадно (максимизируя min(len(lss), len(gtr)). Было бы хорошо если бы обновили график.

ссылка на код

[1.0, 2.6666666666666665, 4.666666666666667, 6.933333333333334, 9.577777777777778, 12.39047619047619, 15.406795634920634]

более тупое

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

какое наименьше (в среднем) число вопросов (сравнений двух элементов) нужно задать что бы отгадать загаданную перестановку

Гениально! В то время как лучшие умы человечества пытаются превзойти n log n, этот код при сортировке отрицательных чисел должен выдать ответ ещё до вызова!

Также известно как delay sort

О, когда весной изучал работу нового поколения псевдоразумных нейросетей архитектуры трансформера (GPT3 / GPT 4 и их многочисленные аналоги), в частности остановился на том, как они сортируют списки или занимаются простым счетом если задать им такую задачу в чистом виде или как часть более общей.
В частности что они без проблем справляются с короткими списками, но начинают ошибаться и сильно тупить (даже если указывать им на ошибки и пытаться помочь/подсказать) если список становится длиннее (в зависимости от конкретной реализации и "размера" сети = количества ее "параметров"/весов, это происходит где-то в диапазоне от 7-10 элементов и до нескольких десятков)
И пришел к выводу, что нейросети в процессе "тренировки" самостоятельно сформировали внутри себя очень специфические алгоритмы. Которые скорее всего представляют что-то очень похожее на описанное вами! Конкретно для случая сортировки.


Вот тут про это немного писал в комментариях:
https://habr.com/ru/companies/ods/articles/716918/comments/#comment_25509482
https://habr.com/articles/729966/#comment_25509056 (тут 1й пример, где прошу нейросеть на базе GPT-4 отсортировать буквы по алфавиту)


Что-то похожее(хотя и немного другое) происходит и с математическими операциями, когда подобная нейросеть пытается выполнять математические операции — там размерность чисел не ограничена, но результат получается "примерный".


Из-за того, что такие нейросети класса "большая текстовая модель", в частности Трансформер имеют очень жесткие исходные(архитектурные) ограничения: они не рекуррентные (т.е. прямые циклы или даже их иналоги в их внутренних алгоритмах просто невозможны, но зато возможны множественные и очень сложные ветвления и проверки условий!), у них нет "рабочей памяти" для хранения переменных(память и слежение за контекстом разговора чат-ботов на основе таких сетей реализуется внешними алгоритмами, подающими часть истории разговора на вход нейросети заново при каждом следующем сообщении/запросе).


То при достаточном уровне тренировки и размере сети (количестве параметров представляющих собой аналог объема кода) в ней рождаются вот такие вот "тупые" алгоритмы. В частности для сортировки и подсчета элементов. Не по принципу "тупости", а "жить захочешь, еще не так раскорячишься" (с).


Вот представьте, что вы программируете, но у вас в распоряжении есть только один очень дурацкий язык программирования, в котором ВООБЩЕ отсутствуют циклы и нет доступа к оперативной памяти для хранения переменных(зато констант прямо в код нафигачить можно почти неограниченно) и даже встроенных базовых математический операций(сложение, умножение и т.д.) в языке нет. Но зато if… then… else и их аналоги (Case, Select и т.д.) можно фигачить в код тоже почти в неограниченных количествах.
И вам вот с таким "инструментарием" нужно отсортировать массив, или сосчитать количество элементов в некотором множестве или выполнять математические операции и т.д.


И вот тогда, подобные "тупые" алгоритмы, окажутся далеко неплохим вариантом, а вероятно одним из лучшим (из возможных в конкртеных ограниченных условиях). А вот это:


А факториал растет очень быстро, значит наши функции будут становиться все длиннее, отнимая не только вычисления, но и память — ее нам тоже потребуется O(n!). Не для хранения данных (мы же их даже никуда не сохраняем), а для самого алгоритма.

Становится не багой (простыни говнокода из бесконечной лапши IF-ов, вместо считанных байт для хранения нескольких переменных и обработки в цикле и вызова функций рекурсией), а киллер-фичей в условиях когда этих самых байт ОЗУ тупо нет в принципе (точнее не можем в него ничего писать во время исполнения "кода"), зато мегабайты кода — вообще не проблема.

Самая крутая сортировка — квантовая. Имея квантовый источник случайного шума, можно за O(n) получить случайную перестановку массива. Потом, опять же за O(n), можно, как верно заметил автор, проверить, а не отсортирован ли массив. Если нет, то надо, всего-лишь уничтожить вселенную. В результате (если принять многомировую интерпретацию квантовой физики) останется хотя бы одна вселенная, в которой массив отсортирован за O(n). Вот, мы и изобрели линейный алгоритм сортировки!

Обычно люди удивляются и не верят, когда им говоришь, что для целых чисел (int32 например) вычислительная сложность сортировки - это O(1). Но так и есть. По памяти она, правда, хромает, но зато честный научный O(1).

Всмысле? Да будь там числа хоть 0,1 — все равно надо все числа обойти. O(n) получается.

Не получается. O(1) означает, что существует такое константное k, что алгоритм заканчивается за k шагов для всех размеров входа. Целых чисел фиксированное количество (2^32 штук например), а значит, такое k существует.

Если кажется ну совсем сюром, тогда вот другой (на этот раз вполне себе практический, даже по памяти приемлемый): алгоритм сортировки «за O(n)», если диапазон чисел не очень велик и все числа в массиве уникальны (на самом-то деле он тоже O(1), ну да назовем это «похожим на O(n)»). Например, если у нас есть не более 1000 уникальных элементов в диапазоне 0..999, можно их отсортировать за ~2000 шагов: берем зануленную битовую карту на 1000 битов (это всего-то 125 байтов), дальше идем по входному массиву и взводим в 1 бит, соответствующий очередному числу. В конце проходим по битовой карте и выплевываем в качестве результата позиции тех битов, где 1 (тут огромный простор для оптимизаций: можно проскакивать те 8-байтовые группы, где все нули например). Похожий алгоритм широко используется в СУБД.

Нет же. 32 битных чисел — конечное число. Массивов из них — бесконечное. Не существует такого k. Допустим оно есть, а я вам дам массив из k+1 числа. И даже не любой массив, а очень ограниченный — все числа там нули и только одно число больше нуля. За k шагов вы даже это ненулевое число не всегда найдете, а найти его надо, ведь оно может быть любым. А оно может быть на любой из k+1 позиции.


Да, если у вас чисел строго ограниченное количество, то можно, конечно, и сову O(1) натянуть на глобус ассимптотики. Но это солипсизм какой-то получается. Такто, у вас всегда ограниченное количество данных, ибо компьютеры конечны. Поэтому счтают скорость роста от размера входных данных, хоть бы этот размер и ограничен, что не строго соответствует математическому определению.

Sign up to leave a comment.

Articles