1. Java / Говнокод #10361

    +74

    1. 01
    2. 02
    3. 03
    4. 04
    5. 05
    6. 06
    7. 07
    8. 08
    9. 09
    10. 10
    11. 11
    12. 12
    13. 13
    14. 14
    15. 15
    16. 16
    17. 17
    18. 18
    19. 19
    20. 20
    21. 21
    22. 22
    23. 23
    24. 24
    25. 25
    26. 26
    27. 27
    28. 28
    29. 29
    30. 30
    31. 31
    32. 32
    33. 33
    34. 34
    35. 35
    36. 36
    37. 37
    38. 38
    39. 39
    40. 40
    41. 41
    42. 42
    public class factorial {
        public static void main(String[] args) {
            boolean run = true;
            long count = 2142;
            long last_count=0;
            while (run) {
                if (ispand(count)) {
                    if (isprime(count)) {
                        System.out.println(count);
                        last_count=count;
                    }
    
                }
                if((count+"").length()>7){
                   System.out.println("Largest prime can be :"+last_count);
                   System.exit(1);
                }
                count++;
            }
        }
        public static boolean ispand(long num) {
            String text = num + "";
            for (int i = 1; i <= text.length(); i++) {
                if (!text.contains(i + "")) {
                    return false;
                }
            }
            return true;
        }
        public static boolean isprime(long num) {
            if (num == 1) {
                return false;
            } else {
                for (int i = 2; i <= Math.sqrt(num); i++) {
                    if (num % i == 0) {
                        return false;
                    }
                }
            }
            return true;
        }
    }

    http://projecteuler.net/problem=41
    http://projecteuler.net/thread=41&page=8


    Пациент каким-то образом растянул решение аж на две секунды.

    PS: одному Аллаху известно почему это "factorial".

    Запостил: TheHamstertamer, 24 Мая 2012

    Комментарии (95) RSS

    • Ох, черт, что я дальше-то не глянул, там есть еще более упоротый, вот где самый сок:

      public static void main(String[] args){
      		long start = System.currentTimeMillis();
      		int [] pandigitalMatrix = {7,6,5,4,3,2,1};
      		int [] rotationCounter = {0,0,0,0,0,0};
      		long biggestPrime = 0;
      		while (rotationCounter[0]<7 && biggestPrime==0) {
      			while (rotationCounter[1]<6 && biggestPrime==0) {
      				while (rotationCounter[2]<5 && biggestPrime==0) {
      					while (rotationCounter[3]<4 && biggestPrime==0) {
      						while (rotationCounter[4]<3 && biggestPrime==0) {
      							while (rotationCounter[5]<2 && biggestPrime==0) {
      								long candidate =generatePanDigitalNumber(pandigitalMatrix);
      								rotateMatrix(pandigitalMatrix, 2);
      								if (isPrime(candidate)) {
      									biggestPrime = candidate;
      								}
      								rotationCounter[5]++;
      							}																		
      							rotationCounter[5]=0;
      							rotateMatrix(pandigitalMatrix, 3);										
      							rotationCounter[4]++;
      						}																	
      						rotationCounter[4]=0;
      						rotateMatrix(pandigitalMatrix, 4);										
      						rotationCounter[3]++;
      					}								
      					rotationCounter[3]=0;
      					rotateMatrix(pandigitalMatrix, 5);										
      					rotationCounter[2]++;
      				}							
      				rotationCounter[2]=0;
      				rotateMatrix(pandigitalMatrix, 6);										
      				rotationCounter[1]++;
      			}						
      			rotationCounter[1]=0;
      			rotateMatrix(pandigitalMatrix, 7);										
      			rotationCounter[0]++;
      			System.out.println("Rotation : " + rotationCounter[0]);
      		}			
      		long end = System.currentTimeMillis();
      		System.out.println("Answer : " + biggestPrime);
      		System.out.println("exec time : " + (end-start) + "ms");
      }
      Ответить
    • Так, если не туплю, то начать перебор надо бы с семизначных чисел, и идти в сторону убывания? Тогда первое попавшееся pandigital prime и будет ответом. Причем интерес представляют только семизначные да четырехзначные числа (т.к. все остальные делятся на 3).
      Ответить
      • import Data.List
        
        primes = 2 : filter isPrime [3..]
        isPrime x = check primes where
            check (p:ps) | p*p > x        = True
                         | x `mod` p == 0 = False
                         | otherwise      = check ps
        
        makeNumber  = foldl (\acc digit -> acc*10+digit) 0
        
        pandigitals7 = reverse $ sort $ map makeNumber $ permutations [1..7]
        
        main = print $ head $ dropWhile (not . isPrime) pandigitals7


        Работает миллисекунды даже в ghci... Что тут делать 2 секунды - я не знаю :)
        Ответить
        • http://ideone.com/FyJIT

          >reverse $ sort $ map makeNumber $ permutations [1..7]
          Не хочу злорадствовать, но наверняка reverse $ sort тяжелые операции и от этого никакая ленивость и AsParallel не спасёт.
          В моём варианте достаточно, как Вы уже указали ранее, взять первое значение.
          Ответить
          • Ленивость не спасет, не спорю. Просто этот код набросан за несколько минут и, несмотря на это, он работает корректно и быстро. На 2ггц Атлоне в интерпретаторе (ghci) код работает всего 0.09с. Так зачем заниматься преждевременной оптимизацией?

            Ну а так, согласен, sort тут лишний, достаточно сделать правильный permutations (стандартный выдает их в каком-попало порядке).
            Ответить
            • Все так, только нормальный permutations будет размером с приведенное решение.
              Ответить
              • Permutations, сохраняющий порядок:
                ordPermutations [] = [[]]
                ordPermutations list = concat $ map (\x -> map (x:) $ ordPermutations (delete x list)) list
                Ответить
                • А вас не смущает, что он на порядок (причем не двоичный) дольше работает?
                  Ответить
                  • Это на 9.. на 10 еще сильнее разница, и GC ~ 50%
                    Ответить
                    • Тестировалось на -O2.

                      sum $ map makeNumber $ sort $ permutations [1..10]
                      22.91с, 4.6Гб

                      sum $ map makeNumber $ ordPermutations [1..10]
                      5.88c, 5.2Гб
                      Ответить
                      • Посмотрел на статистику. Походу вторая версия работает быстрее т.к. у версии с sort'ом огромный пик после построения всех перестановок (250+ мег).
                        Ответить
                        • >5.2Гб
                          Хуясе! Это используемая память?! Та нахуй он тогда нужен этот Хацкель.
                          У меня даже жаба n=11 на приведенном мною алгоритме:
                          2.5с, память - неощутимо (пару метров).

                          Притом что по объему кода примерный паритет теплая ламповая императивщина заруливает ваши функцианалы в глубокий минус.
                          Байтоитоебы снова побеждают.
                          Ответить
                          • Нет, не используемая. Это общий объем, прокачанный через GC. А реальный пик там в районе 250 метров для версии с сортом и меньше пары метров для ленивой версии.
                            Ответить
                            • >в районе 250 метров для версии с сортом
                              Предсказуемо.
                              > меньше пары метров для ленивой версии.
                              А, ясно. Меня несколько шокировали цифры.
                              Всё-равно n=10 у меня за 100мс. Агрумент о том что списки произвольной длины, а int всего 32 бита не катит, потому что уже на 20! железо просто помрет.
                              Ответить
                          • Ну зачем так сразу.. Есть такая хрень, как deforestation (GHC на правом fold может оптимизировать, выкидывая промежуточные списки),
                            есть еще stream fusion - соединяет list-produces и consumer в одну ф-ию, будет сравнимо с С.

                            Ну и всегда (когда списки только как промежуточные) можно в рекурсивном виде записать, только очень некрасиво получится.
                            Ответить
                            • Ну можно попытаться позадрачивать и С. Типы в short итд.
                              Но вот одна действительно хорошая оптимизация - нахождение наибольшего неиспользуемого символа (позиция первого нулевого бита), чтобы сократить кол-во итераций.
                              Тут можно либо битовую магию применить, либо юзать заранее просчитанные таблицы.
                              10 бит - 1024 состояния, не так уж много. В L1 запросто влезет. Тогда вплотную подходим к O(n!)
                              Ответить
                      • Причем тут sort? Одно дело - ф-ия для локального использования, другое - претендующая на общность.
                        Сравните, например, main = print $ length (permutations [1..11]) для двух вариантов.
                        Ответить
                        • Да, я погорячился, вы правы. Работает даже дольше чем на порядок, по сравнению со стандартным permutations.

                          Хотя, возможно, их нельзя сравнивать напрямую - т.к. они решают немного разные задачи - permutations генерит перестановки в произвольном порядке, а ordPermutations в строго заданном.

                          P.S. попробую придумать версию пооптимальней.
                          Ответить
                          • Угум, большинство современных алгоритмов поиска по деревьям не реализуемы в языках с немутабельными типами данных потому что любые такие алгоритмы должны кешировать данные непосредственно в узлах, потомков которых они "открыли". Когда это вомзожно сделать только заменой узла (это было бы еще хорошо, но, как правило, это замена, как минимум под-дерева), то какой-нибудь RBFS / SMA* просто не реализуем, или реализуем для деревьев размер которых не достаточно большой для практического использования.
                            Ответить
                          • > нельзя сравнивать напрямую - т.к. они решают немного разные задачи
                            Пожалуй, главное чтоб коэффициент не вы рос быстрее, чем n*log n (оверхед sort). Вроде не выходит.
                            Ответить
                    • Вот тебе иммутабельность под ребро, вот тебе куча дорогостоящих списков в бороду.
                      Сборщик мусора очень рад Хацкелу.
                      http://ideone.com/IByI4
                      Ответить
                      • Вы эстафетную палочку перехватили?) Здесь by design, проверял: length $ permutations [1..9]:
                        Проверятся как быстро создается список, length считает длину, посчитанное сразу в мусор. (оригинальный был 25% на [1..9], при больших - меньше)
                        Ответить
                  • sum $ map makeNumber $ reverse $ sort $ permutations [1..9]
                    201599999798400
                    (3.09 secs, 557943280 bytes)
                    sum $ map makeNumber $ ordPermutations [1..9]
                    201599999798400
                    (4.05 secs, 791391080 bytes)

                    Поясните, пожалуйста, почему он должен работать на порядок дольше?

                    P.S. В контексте задачи про pandigital primes он явно быстрее прошлой версии с reverse + sort + permutations т.к. ленив и не генерирует лишних перестановок, после того как будет найдено первое решение.
                    Ответить
                    • Протестил на ghc -O2. Вариант с reverse $ sort $ permutations - 1.7с (без реверса 1.6с), вариант с ordPermutations - 0.46с. Т.е. он даже на полном переборе быстрее оригинального варианта, не говоря уже о том, что он совместим с ленивостью.
                      Ответить
                    • > "правильный permutations"
                      Раз написали так, значит, нужно сравнить с permutations в целом, а не в контексте данной задачи.
                      Для _данной_ задачи ваш вариант подходит, но в ответе 765 впереди, из 7! остается 24 варианта -- можно писать абы как, лишь бы лениво.
                      Ответить
                  • Так это плата за сортировку. Permutation можно написать двумя способами: быстрый и минимально использующий память - на swapaх (сортировки нет), либо сортированным - оба приведенных тут рекурсивных алгоритма по сути одинаковые.
                    Ответить
                    • А какое соотношение сложностей? Если по-крайней мере длина списка - то я виноват.
                      Ответить
                      • Ну в swapах там идея как в коде Грея. То есть 1 итерация(обмен) генерирует новую перестановку. Эффективность 100%.
                        Это n! итераций.

                        В рекурсивном способе, чтобы не было дубликатов, нужно либо проверять использовалось/не использовалось как это сделал я, либо удалять из списка (порождением нового).
                        В любом случае думаю где-то в районе n^n проверок (итераций).
                        Ответить
                        • Значит соотношение ~ e^n, вполне возможно, что плата адекватная, субъективно показалось, что должно быть быстрее.
                          (Забыл, что еще оригинальный permutations может быть хуже n! - swap на списках затруднен).
                          Ответить
                          • У меня такое чувство, что существует менее затратный алгоритм, который каждый раз переводит перестановку (тот же список или массив) в следующее меньшее состояние.
                            А в треде - неоптимальное говно, ибо я вчера писал первое что пришло в голову.

                            >swap на списках затруднен
                            Не гораздо трудней, чем удалить из него один элемент :)
                            Ответить
                          • http://en.wikipedia.org/wiki/Permutation#Generation_in_lexicographic_ order
                            Как я и думал. Базовый Swap и иногда Reverse подсписка.
                            PS> Тестьте!
                            http://rosettacode.org/wiki/Sorting_algorithms/Permutation_sort#Haskell
                            Ответить
                            • > http://rosettacode.org/wiki/Sorting_algorithms/Permutation_sort#Haskell
                              Неупорядоченный :(

                              Попробую реализовать алгоритм из вики.
                              Ответить
                              • Как указал ниже гость сгенить, а потом сортировать может оказаться теоретически дешевле.
                                Ответить
                                • С учетом того, что алгоритм с сортировкой использует О(n!) памяти - вполне возможно.
                                  Ответить
                            • взял, что дейкстра предложил когда-то: http://www.cut-the-knot.org/do_you_know/AllPerm.shtml

                              сырой код, абы как:
                              next xs = let (rev_i, i, m, rest) = go_i [] 0 xs
                                            (rev_j, j, n      ) = go_j [] 0 i xs
                                        in take (m - n) rev_i ++ [i] ++ rev_j ++ [j] ++ rest
                              
                              go_i acc !n (x:xs@(y:ys)) | x <= y    = go_i (x:acc) (n + 1) xs
                                                        | otherwise = (x : acc, y, n, ys)
                              
                              go_j acc !n i (x:xs) | x <= i    = go_j (x:acc) (n + 1) i xs
                                                   | otherwise = (acc, x, n)
                              
                              main = print $ length (take 3628800 $ iterate next [5,4..1])


                              корректность проверял так:
                              take 120 (iterate next [5,4..1]) == map reverse (sort $ permutations [1,2,3,4,5])

                              на [1..10] быстрее, чем у [color=blue]bormand[/blue] main = print $ length (ordPermutations [1..10])

                              А вот какой вывод сделать не знаю: не уверен, что смогу оценить сложность изкоробочного permutations.
                              Ответить
                              • Выдернул jadом код из апплета по сцылке, особо не переписывал, и запускал все 3 на жабе: http://ideone.com/sLmS4
                                1. swap-версия с апплета (по сути, то что я предлагал тут: http://govnokod.ru/10361#comment139237)
                                Аналог вот этого http://en.wikipedia.org/wiki/Permutation#Generation_in_lexicographic_ order
                                2. оптимизированная вариация 3-го пункта (с массивом позиций младшего нулевого бита)
                                3. первоначальная версия с битами на быструю руку

                                n=12
                                1. 4313ms
                                2. 10500ms
                                3. 30391ms

                                n=11
                                1. 734ms
                                2. 875ms
                                3. 2359ms

                                n=10
                                1. 31ms
                                2. 78ms
                                3. 203ms

                                Разница между 1 и 2 ~ 2.5 раза. Но на N=11 - странный результат, запускал трижды - повторяется.

                                PS.Что интересно версия с Integer[] m_Val работает в два раза медленней, чем примитивные типы: new int[m_Max];
                                Вот вам и автобоксинг.
                                Ответить
                                • Блин. Вчера было при N=11
                                  1. 734ms
                                  А сегодня запустил:
                                  1. 360ms

                                  Теперь всё стало на места, смотрите, в обоих методах зависимость от n! линейная:
                                  10500ms/875ms=12 (ровно!)
                                  875ms/78ms=11.2
                                  4313ms/360ms=11.99
                                  360/31=11.6
                                  Ответить
                        • > n^n
                          Хм.. n! + цена сортировки n! (log n!) должны сверху ограничивать оценки сортированных перестановок.
                          O(n! n (log n)), n (log n) - верхняя оценка коэф. разницы.
                          Ответить
                          • > n^n
                            Да. Это я загнул.
                            Посчитал кол-во итераций с своем алгоритме ~ 2*(n-1)*n!
                            Всё потому что это не таки тупой брутфорс n^n и скип ненужных веток присутствует:
                            if ((mask & 1<<j)==0);
                            Ответить
                          • Но O(n*n!) полагаю не предел и алгоритм с вики будет O(log(n)*n!). Но на n порядка 10 оно малозаметно.
                            Ответить
                          • Сделал битовую таблицу для нахождения нужных битов - снизил число итераций в 3-5 раз. Код небрежный, но теперь вроде как O(log n*n!).

                            #include	<stdarg.h>
                            #include	<stdio.h>
                            	
                            int	n=10,cnt=0;
                            int	pos0[4100];	
                            int	pos=0;
                            
                            void perm(int base, int mask){
                               if (mask+1==1<<n){
                                   ++cnt;
                            	  //printf("%d\n",base);
                                    return;
                               }
                               int mask1=mask;int sh=0;
                               for (;mask1+1!=1<<n;){
                                    ++cnt;
                                   int j=pos0[mask1];
                            	   sh=1<<j;
                            	   mask1|=sh;	   
                                   perm(base*10+j+1, mask | sh);
                               }
                            }
                            int	main(void)
                            {
                            	//generation of bit array
                            	for	(int i=0;i<=(1<<n);++i){
                            		pos=0;
                            		for	(int j=i; j>0; j>>=1,++pos){
                            			if	((j & 1)==0 )
                            				break;
                            		}
                            		pos0[i]=pos;
                            		//printf("%d=%d\n",i,pos);
                            	}
                               perm(0,0);  
                                    printf("%d",cnt);
                            }

                            http://ideone.com/NJpzU
                            время: 0.09s
                            Ответить
                            • Кстати в gcc есть вот такая штука:

                              Built-in Function: int __builtin_clz (unsigned int x)
                              Returns the number of leading 0-bits in x, starting at the most significant bit position. If x is 0, the result is undefined.

                              На интеле она должна выполняться за 1 машинную команду. Жаль, что этой функции нет в стандарте.
                              Ответить
                              • Так вроде где-то обсуждали, что pure С хак кагбе быстрее, чем i386 bsr/bsf и не рекомендовано, или то речь шла о cntlz, не помню.
                                В любом случае как еще улучшать алгоритмически я не знаю.
                                И выходит рвет даже haskell unordered permutations.
                                Ответить
                                • Кстати если верить формуле n + n*(n-1) + n*(n-1)*(n-2) + ... + n! для числа узлов в префиксном дереве, в котором в каждом узле содержится 1 цифра и моему предположению, что это число равно n!*e-1, то сложность вашего алгоритма составляет как раз О(n!).
                                  Ответить
                                  • ~n! e, точного равенства же не будет: ряд слева конечен (и вообще там рациональное)
                                    Ответить
                                    • >~n! e, точного равенства же не будет: ряд слева конечен (и вообще там рациональное)
                                      Ну это в пределе при n->∞
                                      Кстати через разложение e в сумму обратных факториалов как раз и доказывают, что оно иррациональное.
                                      Вот, чтоб было понятней что я ниже написал - пикча e^x в ряд Тейлора.

                                      http://upload.wikimedia.org/wikipedia/ru/math/f/5/5/f551e394956f5458a059c2b041a4fbfb.png
                                      при x=1
                                      Ответить
                                  • >n + n*(n-1) + n*(n-1)*(n-2) + ... + n!
                                    Да, Вы абсолютно правы. Просто я тогда чето затупил, и подумал на ln(n), вместо e.
                                    Я думал и пришел к такой же формуле независимо.
                                    >равно n!*e-1
                                    Именно! Но это n!*e предел при n->∞.
                                    (n + n*(n-1) + n*(n-1)*(n-2) + ... + n! )=
                                    n*!((n + n*(n-1) + n*(n-1)*(n-2) + ... + n! )/n!)=
                                    n*!((1/(n-1)! + 1/(n-2)! + 1/2!+1/1!+1 )/n!)

                                    При n=0 и n->∞ Сумма(1/n!) -> e
                                    Q.E.D
                                    Ответить
                              • Кстати если портировать на жабу - то способ с массивом или хитрыми бит-операциями типа такого
                                c = a - ((a >> 1) & 0x55555555);
                                c = ((c >> 2) & 0x33333333) + (c & 0x33333333);
                                c = ((c >> 4) + c) & 0x0F0F0F0F;
                                c = ((c >> 8) + c) & 0x00FF00FF;
                                неизбежен.
                                Ответить
                            • Жалко, что экспериментально сложно проверить, факториал оставляет мало шансов.
                              Ответить
        • > dropWhile (not . isPrime)
          filter чем не устроил?
          Ответить
      • >интерес представляют только семизначные да четырехзначные числа
        >т.к. все остальные делятся на 3
        Вот это не понял.
        Ответить
        • Ну смотрите, нас интересуют числа, состоящие из цифр от 1 до n.
          Если мы берем все 10 цифр - то их сумма будет 45, она делится на 3 => по признаку делимости на 3 все эти числа делятся на 3. По тому же принципу отлетают все диапазоны кроме 1..7 и 1..4.
          Ответить
    • Интересно, а сколько минимально было бы узлов в префиксном дереве отображающем все пермутации чисел от 0 до 9? 9*9? 9!? 9^9?
      Ответить
      • Если не ошибаюсь: n + n*(n-1) + n*(n-1)*(n-2) + ... + n!

        Что при n=10 составляет 9864100
        Ответить
        • Для 10 можно построить 10 веток, в каждой по 9! элементов, получим 10!+10.
          Если считать вырожденный случай в виде всех значений деревом, то можно обойтись и n!
          Ответить
          • Хм, а почему вы рассматриваете в этих ветках лишь вершины, опустив промежуточные узлы?
            Ответить
            • >минимально
              Формально это тоже префиксное дерево, но с глубиной 2.
              Ответить
              • Да, согласен. Я думал, что мы ограничиваемся деревьями, у которых по одной цифре на узел.
                Ответить
        • Что-то мне как-то тяжело поверить что в нем будет х + n! узлов. Пермутаций столько нету, откуда столько узлов возьмется? Т.е. из n! пермутаций у (n - 1)! есть общий суффикс длиной n - 1, который можно объединить, это уже в нашем случае 9 + (n - 1)! (а не 9 * (n - 1)!).
          Ответить
          • > есть общий суффикс длиной n - 1
            Но Вы же сами пишете, что дерево - "префиксное".
            Ответить
            • Ну это безразлично с какой стороны.

              Зы. транслит.ру посчитал, что правильно писать "пощитал" :)
              Ответить
              • >Ну это безразлично с какой стороны.
                Тогда квадрат 9 на 9 :)
                Но это, по определению, не префиксное дерево.
                >пощитал
                Тоже так иногда пейшу.
                Ответить
                • Если смотреть с обоих сторон - то это не квадрат, это ориентированный граф, у которого в первом слое 10 вершин и в последнем 10 вершин, а в средних слоях вершин намного больше 10.
                  Ответить
                  • >а в средних слоях вершин намного больше 10.
                    10 ведь минимально достаточно.
                    > это ориентированный граф
                    Циклов нет, значит дерево. Но требование префиксности, как уже отмечал, не выполняется.
                    Ответить
                    • > 10 ведь минимально достаточно.
                      Если в средних слоях брать по 10 - то получим лишние пути, которые не являются перестановками.
                      > Циклов нет, значит дерево. Но требование префиксности, как уже отмечал, не выполняется.
                      Да, согласен, это не префиксное дерево.
                      Ответить
                      • По-идее, мне кажется, что можно получить C(10,5) * P(5,5) + P(10,5) но мне лень считать на сколько это отличается и в какую сторону от 10!
                        Ответить
                      • Т.е. другими словами: найти все комбинации по 5 чисел длиной, и из каждой такой комбинации наделать пермутаций - логично, что они нигде не пересекаются. Теперь, у нас остались пермутации (префиксы), которые мы будем строить по принципу: 9 узлов в начале со степенью ветвления 8, у второго уровня степень ветвления 7 и так до 5-го уровня - и вот тут надо как-то придумать, как всем узлам пятого уровня назначить их суффиксы - но, в принципе, уже все пермутации у нас есть, я вот только даже затрудняюсь посчитать степень ветвления на 5м уровне...
                        Ответить
                        • На пятом уровне будет 10!/5! = 30240 узлов.

                          Если сгруппировать эти узлы по множеству чисел, входящих в пути к ним, то получим C(10,5) = 252 группы по 5! = 120 узлов.

                          К каждой группе мы прицепляем оставшиеся 5! = 120 суффиксов.

                          В применении к генерации перестановок - получается, что нужно сгенерить C(10,5) подмножеств. А затем для каждого из них сгенерить по 5! перестановок элементов подмножества, и 5! перестановок оставшихся элементов и взять их декартово произведение.

                          Как-то так.
                          Ответить
          • В смысле, суффикс длинной в 1 элемент, а не n - 1.
            Ответить
          • Т.е. мне кажется, что должно быть возможным сделать дерево в два раза ниже, прощитав, например, все суффиксы из {1,2,3,4,5} и приделав их к веткам того, что получилось из {6,7,8,9} - не?
            Ответить
        • Кстати странно, но походу это e*n! - 1.
          Ответить
    • Если кому интересно - нашел вот такую либу: http://hackage.haskell.org/packages/archive/combinat/0.2.4/doc/html/Math-Combinat-Permutations.html
      В комментах к коду написано что это менее эффективная версия L-алгоритма из книги Кнута.

      n: 9 10 11
      fasc2B_algorithm_L: 0.124с 1.27с 14.757с
      permutations: 0.03с 0.20с 1.938с
      мой тупой однострочник: 0.29с 3.13с 35.639с

      К сожалению, результаты L-алгоритма отличаются от моего в константное число раз (2.4). Поэтому рискну предположить, что в языке с иммутабельными данными понизить сложность алгоритма уже не получится, только выиграть в константе.
      Ответить
      • Спасибо, по сабжу на hayoo видел, но устанавливать не стал. А на каких n (длина списка) коэф. провеpялся?
        Ответить
        • (В смысле константа 2.4)
          Ответить
        • Проверял на 9..11, константа получилась так - поделил в каждом из примеров мое время на время L-алгоритма. Если бы они отличались по сложности - был бы заметен рост отношения (как, например, он заметен если сравнивать эти алгоритмы со стандартным permutations), а так - оно во всех трех случаях 2.4.
          Ответить
    • Реализовал алгоритм на субсетах (http://govnokod.ru/10361#comment139283):
      import Data.List
      
      -- генерация упорядоченных подмножеств (не оптимальная, но т.к. их мало - пофиг)
      subsets :: Int -> [a] -> [([a], [a])]
      subsets n list = subs n (length list) [] list where
          subs n l r s@(x:xs)
              | n == 0     = [([], reverse r ++ s)]
              | n == l     = [(s, reverse r)]
              | otherwise  = (map (\(a, b) -> (x:a, b)) $ subs (n-1) (l-1) r xs) ++
                             (subs n (l-1) (x:r) xs)
      
      -- генерация перестановок
      perms :: [a] -> [[a]]
      perms [] = [[]]
      perms [x] = [[x]]
      perms list = p where
          l = length list
          l1 = l `div` 2
          l2 = l - l1
          s = subsets l1 list
          p = concat $ map (\(a, b) -> [ (x++y) | x <- perms a, y <- perms b ]) s

      Результаты теста на print $ length:
      n: 9 10 11 12
      permutations: 0.03 0.20 1.938 22.329
      новый генератор: 0.02 0.358 2.485 15.371

      К сожалению пример с вычислением длины для этого генератора совсем не показателен.
      Ответить
      • Вот более адекватный тест на sum $ map makeNumber $ perms [1..n]:

        n: 9 10 11
        permutations: 0.151 2.077 30.32
        perms: 0.21 2.8666 34.08
        Ответить
      • > вычислением длины для этого генератора совсем не показателен
        Определенно, ленивость надо обходить, хотел заметить, но вы сами написали.
        BTW, вы смотрели http://govnokod.ru/10361#comment139267 ?
        Потом форсировал через (sum . map sum) вроде быстрее оригинального (по идее еще неплохо было бы вычитать из всех время для такого генератора: replicate (product [1..length xs]) xs)
        Ответить
        • На списке из 10 чисел с форсированием через (sum.map sum):

          Для n=9
          Ваш вариант - 0.09с (1.2% GC)
          perms - 0.15с (49% GC)
          replicate - 0.01с (3.3% GC)

          Для n=10
          Ваш вариант - 0.94с (1.1% GC)
          perms - 1.41с (45% GC)
          replicate - 0.06с (1.7% GC)

          Для n=11
          Ваш вариант - 11.43с (1.0% GC)
          perms - 11.23с (15% GC)
          replicate - 1.28с (1.1% GC)

          Странно, но при росте n perms начинает догонять ваш вариант. Жаль, что для 12 ждать уже слишком долго.
          Ответить
          • Хм.. вроде на 11 он был чуть быстрее permutations, а вы писали что там 30 : 34
            Ответить
            • Там форсировалось через sum . makeNumber а здесь форсируется через sum . map sum.
              Ответить
            • С этой ленивость без поллитры не разберешься: ведь replicate наверяняка создает список линков на тот же объект. Именно аккуратно оверхед перестановок вычисли еще та задача.

              Все-таки ваш первый вариант по соотношению кол-во кода / производительность неплох, и его легко можно улучшить раз в два: http://haskell.1045720.n5.nabble.com/String-permutation-td3190999.html (последний пост)
              Ответить
              • > С этой ленивость без поллитры не разберешься
                Ага, согласен, производительность на хаскеле хрен измеришь. То GC вмешается, то поленится раскрыть что-то, то фаза луны не та...

                >Все-таки ваш первый вариант по соотношению кол-во кода / производительность неплох, и его легко можно улучшить раз в два
                Ага, их вариант selections интересный и краткий.
                Ответить
      • Расчехлил criterion: http://pastebin.com/tHLTH7cq , если интересно:
        "генератор"-"то, что форсирует"-"длина списка", для ordPermutations опустил n=9
        ваш perms обозвал subs, dumb - то что предлагал выше вычитать.
        Можно делить sum на n! / n! n / n! (log n0) / n! n (log n) и смотреть графики: что больше на прямую похоже. Но как то уже лень), да и выборка маловата.
        Ответить
        • Для половины результатов разброс может быть большой: http://pastehtml.com/view/bzclm7flc.html (js может много съесть, на len надо было забить) - тут те же данные (на n! не нормированы, но между собой уже можно сравнивать)
          Ответить
          • Странно, получается что subs самый медленный из них?
            Ответить
            • Не.. там у ord n = 9 не учтен: лень ждать было.
              Ответить
      • Круто. Это похоже на quickSort.
        Я об таком думал, но только как бы еще закешировать маленькие перестановки.
        Ответить
      • >новый генератор: 0.02 0.358 2.485 15.371
        Это что же получается. Новый сортированный генератор работает быстрее родного в Хацкеле, да еще и несортированного.

        Проверьте, пожалуйста, на своей машине мой первый вариант на С:
        >http://www.govnokod.ru/10361#comment139219
        У меня сопоставимые цифры - 2-3 секунды при n=11.
        Ответить
        • Мой генератор бажный, попробуйте на [1,2,3,4]. Выдает не в лексикографическом порядке.

          $ g++ -O2 1.cpp
          $ time ./a.out
          39916800
          real 0m2.933s
          user 0m2.932s
          sys 0m0.000s
          Ответить

    Добавить комментарий