Войти в систему

Home
    - Создать дневник
    - Написать в дневник
       - Подробный режим

LJ.Rossia.org
    - Новости сайта
    - Общие настройки
    - Sitemap
    - Оплата
    - ljr-fif

Редактировать...
    - Настройки
    - Список друзей
    - Дневник
    - Картинки
    - Пароль
    - Вид дневника

Сообщества

Настроить S2

Помощь
    - Забыли пароль?
    - FAQ
    - Тех. поддержка



Пишет superhuman ([info]superhuman)
@ 2014-04-12 14:48:00


Previous Entry  Add to memories!  Tell a Friend!  Next Entry
Взял числа от 1 до 9 (без перестановок), четыре арифметические операции (+, -, *, /) и построил декартов квадрат: все возможные размещения операций на все возможные расстановки скобок. И этот сучок не влазит в 32 гига рамы! Не пойму, мощность квадрата = C_8 * 4^8 = 93'716'480 ~ 100 млн. Это что, одна формула занимает больше 3*10^10 / 10^8 = 300 байт?

Формула - во вложенном списке, т.е. 9 + 8 = 17 элементов. Ну, добавим терминаторы +8 = 25. Это что, по 12 байт на элемент? Что за оверхед. Ну, возможно racket лочит память в стиле коллекций дотнета, c выравниванием до 2^n. Вполне возможно, если хранить списки в массивах.

Ладно, хуй с ним, снял лимит по памяти. Тот елозил свопом всю ночь, но так и не доелозил. А для 8 элементов за 2 минуты считается, и занимает 5-6 Гб. Это C_7 * 4^7 = ~7 млн формул. Ну, понятно, что по мегабайту такие маленькие списки не могут занимать, просто сборки мусора не было. Но всё равно, слишком много, мне кажется.

Теперь два-три варианта вижу, или переложить алгоритм на ... Немерле? Хаскелл бы хорошо, но думаю, там по памяти не лучше, чем Схема будет. Или проиндексировать квадрат и оставлять только индексы. Размещения индексируются естественным образом по два байта (4^8 = 2^16). Для скобок (двоичных деревьев с 9 листьями) естественной нумерации не знаю, но можно индексировать через рекурсию, которой они строятся (она ведь детерминированная). Или ещё проще, вдоль одного измерения квадрата - индексировать, вдоль второго - нет. Наверное, так и сделаю.

А у бразильца-то было 6 операций в конце, а не 4. Так что мощность будет C_8 * 6^8 = ~2.4 млрд, однако. И хотя, если делать то же, что и бразилец (поиск разложения числа, анализ лакун), то прямое произведение хранить не нужно, и достаточен лишь перебор, но я хочу построить 3-мерный график на этом квадрате и, вообще, посмотреть на все возможные способы разложения, пересчитать варианты и т. д. Здесь, однако, надо начинать с упрощённых случаев.


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


[info]evilbug
2014-04-12 17:49 (ссылка)
А вы там не строки храните? Или ещё какие жирные переменные?

(Ответить) (Ветвь дискуссии)


[info]phantom
2014-04-12 17:51 (ссылка)
Стандартные схемовские списки, типа '(+ (* 1 2) (- 3 4))

(Ответить) (Уровень выше)


[info]666
2014-04-12 18:54 (ссылка)
а как ты скобки перебираешь?
например если 1+2*3 vs 1+(2*3)

(Ответить) (Ветвь дискуссии)


[info]phantom
2014-04-12 19:00 (ссылка)
Для "1+2*3" - есть два варианта, (1+2)*3 и 1+(2*3). В программе гораздо естественнее и проще построить полное двоичное дерево, чем вводить приоритеты и/или левоассоцитивную конвенцию. Понятно, что для ассоциативной операции типа "+", результат редукции обоих формул совпадёт, ну и ладно.

Подробнее здесь написано:
http://lj.rossia.org/users/superhuman/205813.html
http://en.wikipedia.org/wiki/Operator_associativity

(Ответить) (Уровень выше) (Ветвь дискуссии)


[info]666
2014-04-12 19:16 (ссылка)
то есть для (1+2)+3 и 1+(2+3) ты делаешь 2 разных итоговых вычисления результата?

(Ответить) (Уровень выше) (Ветвь дискуссии)


[info]phantom
2014-04-12 19:24 (ссылка)
Ага. Если подумать, это не намного замедляет перебор. В конечном трёхмерном графике будут константные линии, например, для 1+2+3+4+5+6+7+8+9 и 1*2*3*4*5*6*7*8*9 (дают одно и то же, как ни расставляй скобки).

(Ответить) (Уровень выше) (Ветвь дискуссии)


[info]666
2014-04-12 19:32 (ссылка)
но если ты не различаешь их,
то не сможешь сказать,
сколько разных вариантов записи данного числа X
(т.к. формулы разные, но не различимы)

а разве не в этом смысл?

(Ответить) (Уровень выше) (Ветвь дискуссии)


[info]phantom
2014-04-12 19:42 (ссылка)
В графике я не буду их различать. При подсчёте вариантов буду и 1) различать, и 2) не различать. Интересно сравнить эти два числа. Для варианта 2 у меня есть уже функции, переводящие полную форму записи в форму с убранными (ненужными скобками) для одной операции.

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

Потом можно в свой ЯП встроить такую функцию. :) Но в лисп-интерпретаторах, к примеру, такая функция не нужна, потому что префиксная нотация.

(Ответить) (Уровень выше) (Ветвь дискуссии)


[info]666
2014-04-12 19:51 (ссылка)
то есть смысл задачи - эффективный перебор всех )(синтаксически правильных, минимальной длины) возможных вариантов записи формулы?

(Ответить) (Уровень выше) (Ветвь дискуссии)


[info]phantom
2014-04-12 19:57 (ссылка)
Нет, я решал задачу перебора всех формул с заданными бинарными операторами.

Синтаксическая запись пока меня вообще не касается. Длина у формул всегда одна и та же - 9, число операций всегда 8, в скобках всегда по два операнда.

(Ответить) (Уровень выше)


[info]phantom
2014-04-12 19:59 (ссылка)
Вот, для ясности, что даёт перебор для n = 3, формулы в префиксной форме:

((+ 1 (+ 2 3))
 (* 1 (+ 2 3))
 (- 1 (+ 2 3))
 (/ 1 (+ 2 3))
 (+ 1 (* 2 3))
 (* 1 (* 2 3))
 (- 1 (* 2 3))
 (/ 1 (* 2 3))
 (+ 1 (- 2 3))
 (* 1 (- 2 3))
 (- 1 (- 2 3))
 (/ 1 (- 2 3))
 (+ 1 (/ 2 3))
 (* 1 (/ 2 3))
 (- 1 (/ 2 3))
 (/ 1 (/ 2 3))
 (+ (+ 1 2) 3)
 (* (+ 1 2) 3)
 (- (+ 1 2) 3)
 (/ (+ 1 2) 3)
 (+ (* 1 2) 3)
 (* (* 1 2) 3)
 (- (* 1 2) 3)
 (/ (* 1 2) 3)
 (+ (- 1 2) 3)
 (* (- 1 2) 3)
 (- (- 1 2) 3)
 (/ (- 1 2) 3)
 (+ (/ 1 2) 3)
 (* (/ 1 2) 3)
 (- (/ 1 2) 3)
 (/ (/ 1 2) 3))

(Ответить) (Уровень выше) (Ветвь дискуссии)


[info]666
2014-04-12 20:29 (ссылка)
просто тут сразу видно 2 перебора, как бы наложенные друг на друга, и эти подзадачи (с одной стороны) должны решаться по-разному:

1. перебор оператора (как если везде заменить +-*/ на X, а потом уже, выполнив п.2, перебирать X, подставляя разные символы)

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

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

(Ответить) (Уровень выше) (Ветвь дискуссии)


[info]phantom
2014-04-12 20:48 (ссылка)
Ну, я решаю задачу 1, и не гонюсь за оптимальностью. Я, вообще, любитель брутфорса, на первом этапе, как минимум.

После того, как задача 1 решена, можно уж сократить скобки, и проранжировать по количеству скобок (бразилец, к слову, выбирал наименьшее количество скобок).

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

(Ответить) (Уровень выше)


[info]666
2014-04-12 20:38 (ссылка)
> Длина у формул всегда одна и та же - 9, число операций всегда 8, в скобках всегда по два операнда

при таком упрощении, вообще не понятно, что и перебирать - надо тогда выкинуть скобки нахуй, и каждому из 8ми операторов, кроме его значения 1..4, приписать ещё и уникальный в рамках формулы order 1..8, когда он должен выполниться

(Ответить) (Уровень выше) (Ветвь дискуссии)


[info]666
2014-04-12 20:52 (ссылка)
т.е. получается что каждый оператор - это 32 возможных варианта, а раз операторов всего 8, то число формул = 8**32
или нет?

(Ответить) (Уровень выше) (Ветвь дискуссии)


[info]phantom
2014-04-12 21:15 (ссылка)
Чё-то я не пойму. Ты что именно считаешь? Формул ~ 100 млн., как я писал выше.

(Ответить) (Уровень выше) (Ветвь дискуссии)


[info]666
2014-04-12 21:51 (ссылка)
ага вот теперь и я до того дошел
8! * 4**8 = 40320 * 65536 = 2,642,411,520

(Ответить) (Уровень выше) (Ветвь дискуссии)


[info]phantom
2014-04-12 22:03 (ссылка)
8! это число способов расставить скобки? Это не правильно. Должно быть число Каталана вместо.

(Ответить) (Уровень выше) (Ветвь дискуссии)


[info]666
2014-04-12 22:17 (ссылка)
не понимаю почему

8 операторов, выполняются последовательно,
каждый окружён 2мя аргументами, и c ними заключён в скобки

(Ответить) (Уровень выше) (Ветвь дискуссии)


[info]phantom
2014-04-12 22:30 (ссылка)
Вот, смотри, для 4 операндов:



Это 5 способов расставить скобки. То, есть:

((ab)c)d (a(bc))d (ab)(cd) a((bc)d) a(b(cd))

(Ответить) (Уровень выше) (Ветвь дискуссии)


[info]666
2014-04-12 23:34 (ссылка)
ну вобщем понятно
суть скобок такова, что можно вычислить сначала левую часть, а потом правую, а можно и наборот! т.е. в скобках больше свободы, им как бы поебать на порядок операторов
и поэтому числа каталана растут медленнее чем факториал
т.е. ошибкой было воспринимать скобки как часть системы операторов/аргументов, а надо видеть их как дерево, как граф исполнения программы какой-то, они первичны

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

(Ответить) (Уровень выше) (Ветвь дискуссии)


[info]phantom
2014-04-12 23:45 (ссылка)
Ну да, скобки и придуманы, чтобы не ебать мозг с приоритетом операций и лево-право-ассоциативностью. Обычные ЯП навязывают скобки только для применения функции, отсюда непонятки, например, с семантикой "++*a++" в си-подобных, и там внутри компиляторов ебобля с приоритетами и ассоциативностью. А вот лиспы навязывают скобки даже для операторов. Но нагромождения скобок воспринимаются негативно.

(Ответить) (Уровень выше) (Ветвь дискуссии)


[info]666
2014-04-13 00:04 (ссылка)
но вот знаешь хоть функциональная тематика и порадовала
но остаётся какойто неприятный осадок
как будто я теперь напишу (a) * (b)
и какаято падла сначала вычислит (b)
так нельзя

(Ответить) (Уровень выше) (Ветвь дискуссии)


[info]phantom
2014-04-13 00:17 (ссылка)
А правоассоциативная форма записи, говорят, как раз, более естественна:

http://dkeenan.com/Lambda/

It is traditional in the textual form that operator and operand are simply placed side by side with the operator on the left and the operand on the right with parentheses to remove any ambiguity. This is opposite to the order for direct application in the graphical form. It is also traditional in the textual lambda calculus that we avoid having to write so many brackets by using a left-associative convention. That is, BCT is the same as (BC)T. This proves to be rather unfortunate when compared with the graphical form. The graphical expression which looks most like "BCT" (apart from the left-right reversal) corresponds in fact to B(CT). If we were to make a right-associative convention for the textual form, the correspondence with the graphical form would be more obvious. György Révész (1988) also remarks on how unfortunate is the traditional choice and, in fact, chooses to go against tradition in his book (p16-17). We will stay with tradition in the remainder of this paper.

(Ответить) (Уровень выше)


[info]phantom
2014-04-12 22:59 (ссылка)
Надо рассматривать формулы как деревья. Это помогает понять суть многих задач, и в том числе, как компиляторы организованы. Ну, то есть, почему они оперируют AST, а не текстом.

(Ответить) (Уровень выше)


[info]666
2014-04-12 22:18 (ссылка)
програмка:

foreach $p (perm_without_rep_recursive(8, 8))
{
@a = ('x', (map { ($$p[$_], 'x') } (0..7)));
foreach $n (0..7) {
for($i = 0; $i <= $#a; $i++) {
if ($a[$i] eq $n) {
$a[$i] = '('.$a[$i-1].$a[$i].$a[$i+1].')';
$a[$i-1] = '';
$a[$i+1] = '';
}
}
@a = grep { $_ ne '' } @a;
}
@b = split(//, $a[0]);
$d = 0;
for($j = 0; $j <= $#b; $j++) {
if ($b[$j] eq 'x') { $b[$j] = ++$d; }
elsif ($b[$j] ge '0' && $b[$j] le '9') { $b[$j] = 'o'; }
}
print join('',@b),"\n";
}

sub perm_without_rep_recursive {
my ($n, $m) = @_;
return (map {[$_]} 0..$n-1) if $m==1;
my @b;
for(my $d=0; $d<$n; $d++) {
foreach (perm_without_rep_recursive($n-1,$m-1)) {
push @b, [$d, map { ($_>=$d?$_+1:$_) } @$_];
}
}
@b;
}

(Ответить) (Уровень выше) (Ветвь дискуссии)


[info]phantom
2014-04-12 22:31 (ссылка)
Что она выводит для n=3?

(Ответить) (Уровень выше) (Ветвь дискуссии)


[info]666
2014-04-12 22:35 (ссылка)
(((1o2)o3)o4)
((1o2)o(3o4))
((1o(2o3))o4)
((1o2)o(3o4))
(1o((2o3)o4))
(1o(2o(3o4)))

(Ответить) (Уровень выше) (Ветвь дискуссии)


[info]phantom
2014-04-12 22:55 (ссылка)
№2 = №4.

(Ответить) (Уровень выше) (Ветвь дискуссии)


[info]666
2014-04-12 23:05 (ссылка)
да, вижу

вот более подробный вывод:

012 --> (((x0x)1x)2x) --> (((1o2)o3)o4)
021 --> ((x0x)2(x1x)) --> ((1o2)o(3o4))
102 --> ((x1(x0x))2x) --> ((1o(2o3))o4)
120 --> ((x1x)2(x0x)) --> ((1o2)o(3o4))
201 --> (x2((x0x)1x)) --> (1o((2o3)o4))
210 --> (x2(x1(x0x))) --> (1o(2o(3o4)))

в записи посредине, x - это число, а цифра - номер оператора

допустим, посредине (оператор '2') - деление

симметрии нет

т.е. ((1o2)o(3o4)) имеет 2 формы, если среднее o это деление

(Ответить) (Уровень выше) (Ветвь дискуссии)


[info]666
2014-04-12 23:08 (ссылка)
нет, я хуйню какую-то написал
похоже чего-то не понимаю (как так?)

(Ответить) (Уровень выше) (Ветвь дискуссии)


[info]phantom
2014-04-12 23:54 (ссылка)
Операции надо не переставлять (012, 021, ...), а размещать (000, 001, 002, 010, ...).

(Ответить) (Уровень выше) (Ветвь дискуссии)


[info]666
2014-04-13 00:15 (ссылка)
нет там был смысл что операторы имеют уникальные номера 0,1,2,..., и выполняются в порядке этих номеров (последовательно заключаются в скобки вместе с окружающими аргументами)
т.е. 001 не может быть, потому что 0 только один

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

(Ответить) (Уровень выше) (Ветвь дискуссии)


[info]phantom
2014-04-13 00:24 (ссылка)
Вот не понимаю, зачем операторам уникальные номера.

(Ответить) (Уровень выше) (Ветвь дискуссии)


[info]666
2014-04-13 00:37 (ссылка)
потому что я проявил не скобко-, а операторо-центристский подход, который оказался неверный

(Ответить) (Уровень выше) (Ветвь дискуссии)


[info]phantom
2014-04-13 01:11 (ссылка)
А, ну, ясно.

(Ответить) (Уровень выше) (Ветвь дискуссии)


[info]lankar
2014-04-16 08:38 (ссылка)
Прочитал и охуел :)

(Ответить) (Уровень выше) (Ветвь дискуссии)


[info]phantom
2014-04-16 11:17 (ссылка)
:)

(Ответить) (Уровень выше)


[info]phantom
2014-04-12 19:12 (ссылка)
Собственно, вот подпрограмма:

(define (formulae variables)
  (define (split-and-recombine position)
    (define-values (left right) (split-at variables position))
    (flatten-unary-subtrees
     (cartesian-product (formulae left) (formulae right))))
  (if ((length variables) . < . 3)
      (list variables)
      (append*
       (map split-and-recombine
            (numbers 1 ((length variables) . - . 1))))))


Это ещё до расстановки операций. Здесь, по сути, строятся все возможные полные двоичные деревья с заданными листьями (variables).

(Ответить) (Уровень выше)