sequence_closed_form.pl 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  1. #!/usr/bin/perl
  2. # Daniel "Trizen" Șuteu
  3. # Date: 15 April 2016
  4. # Edit: 15 May 2021
  5. # https://github.com/trizen
  6. # Analyze a sequence of numbers and find a closed-form expression.
  7. # Unfinished work...
  8. # Use the script "sequence_analyzer.pl" instead.
  9. use 5.010;
  10. use strict;
  11. use warnings;
  12. package Sequence::ClosedForm {
  13. use Math::AnyNum qw(Inf);
  14. sub new {
  15. my ($class, %opt) = @_;
  16. bless \%opt, $class;
  17. }
  18. sub sub_n {
  19. my $n = 0;
  20. sub {
  21. $_[0] - ++$n;
  22. };
  23. }
  24. sub add_n {
  25. my $n = 0;
  26. sub {
  27. $_[0] + ++$n;
  28. };
  29. }
  30. sub mul_n {
  31. my $n = 1;
  32. sub {
  33. $_[0] * ++$n;
  34. };
  35. }
  36. sub div_n {
  37. my $n = 1;
  38. sub {
  39. $_[0] / ++$n;
  40. };
  41. }
  42. sub sub_constant {
  43. my (undef, $c) = @_;
  44. sub {
  45. $_[0] - $c;
  46. };
  47. }
  48. sub div_constant {
  49. my (undef, $c) = @_;
  50. sub {
  51. $_[0] / $c;
  52. };
  53. }
  54. sub add_constant {
  55. my (undef, $c) = @_;
  56. sub {
  57. $_[0] + $c;
  58. };
  59. }
  60. sub add_all {
  61. my $sum = 0;
  62. sub {
  63. $sum += $_[0];
  64. $sum;
  65. };
  66. }
  67. sub mul_all {
  68. my $prod = 1;
  69. sub {
  70. $prod *= $_[0];
  71. $prod;
  72. };
  73. }
  74. sub sub_consecutive {
  75. my $prev;
  76. sub {
  77. my ($term) = @_;
  78. if (defined($prev)) {
  79. $term = $term - $prev;
  80. }
  81. $prev = $_[0];
  82. $term;
  83. };
  84. }
  85. sub add_consecutive {
  86. my $prev;
  87. sub {
  88. my ($term) = @_;
  89. if (defined($prev)) {
  90. $term = $term + $prev;
  91. }
  92. $prev = $_[0];
  93. $term;
  94. };
  95. }
  96. sub div_consecutive {
  97. my $prev;
  98. sub {
  99. my ($term) = @_;
  100. if (defined($prev)) {
  101. $term = $term / $prev;
  102. }
  103. $prev = $_[0];
  104. $term;
  105. };
  106. }
  107. sub find_closed_form {
  108. my ($self, $seq) = @_;
  109. my %data = (
  110. diff_min => Inf,
  111. diff_max => -Inf,
  112. diff_avg => 0,
  113. ratio_min => Inf,
  114. ratio_max => -Inf,
  115. ratio_avg => 0,
  116. min => Inf,
  117. max => -Inf,
  118. );
  119. my $count = @$seq - 1;
  120. return if $count <= 0;
  121. my $prev;
  122. foreach my $term (@{$seq}) {
  123. if ($term < $data{min}) {
  124. $data{min} = $term;
  125. }
  126. if ($term > $data{max}) {
  127. $data{max} = $term;
  128. }
  129. if (defined $prev) {
  130. my $diff = $term - $prev;
  131. if ($diff < $data{diff_min}) {
  132. $data{diff_min} = $diff;
  133. }
  134. if ($diff > $data{diff_max}) {
  135. $data{diff_max} = $diff;
  136. }
  137. $data{diff_avg} += $diff / $count;
  138. my $ratio = $term / $prev;
  139. if ($ratio < $data{ratio_min}) {
  140. $data{ratio_min} = $ratio;
  141. }
  142. if ($ratio > $data{ratio_max}) {
  143. $data{ratio_max} = $ratio;
  144. }
  145. $data{ratio_avg} += $ratio;
  146. }
  147. $prev = $term;
  148. }
  149. $data{ratio_avg} /= $count;
  150. my @closed_forms;
  151. if ($data{diff_avg} == $data{diff_max} and $data{diff_max} == $data{diff_min}) {
  152. my $min = ($data{min} - $data{diff_min})->round(-20);
  153. push @closed_forms,
  154. scalar {
  155. factor => $data{diff_min},
  156. offset => $min,
  157. type => 'arithmetic',
  158. };
  159. }
  160. if ($data{ratio_avg} == $data{ratio_max} and $data{ratio_max} == $data{ratio_min}) {
  161. my $factor = $data{min} / $data{ratio_min};
  162. push @closed_forms,
  163. scalar {
  164. factor => $factor,
  165. base => $data{ratio_min},
  166. type => 'geometric',
  167. };
  168. }
  169. #foreach my $key (sort keys %data) {
  170. # printf("%9s => %s\n", $key, $data{$key});
  171. #}
  172. #print "\n";
  173. return @closed_forms;
  174. }
  175. }
  176. use Math::AnyNum;
  177. use List::Util qw(first);
  178. my $seq = Sequence::ClosedForm->new();
  179. my @constants = (1 .. 5); #, #exp(1), atan2(0, -'inf'));
  180. my @rules = (
  181. #['sub_consecutive', 'add_n'], # 'add_n'],
  182. #['add_constant', 'sub_consecutive'],
  183. ['sub_constant', 'sub_consecutive'],
  184. ['sub_constant', 'div_constant'],
  185. ['sub_constant'],
  186. #['add_constant', 'div_consecutive'],
  187. ['sub_constant', 'add_n',],
  188. ['sub_constant', 'div_consecutive', 'sub_constant'],
  189. #['sub_constant'],
  190. #['sub_constant', 'div_consecutive',],
  191. ['sub_constant', 'div_consecutive'],
  192. #['div_consecutive', 'sub_constant'],
  193. # ['sub_constant', 'sub_consecutive'],
  194. #['sub_constant'],
  195. #['add_n', 'div_consecutive',],
  196. #['div_consecutive',],
  197. );
  198. sub make_constant_obj {
  199. my ($method) = @_;
  200. my %cache;
  201. my %state = (
  202. i => 0,
  203. done => 0,
  204. code => sub {
  205. my ($self, $n) = @_;
  206. my $i = $self->{i} - 1;
  207. my $sub = ($cache{$i} //= $seq->$method($constants[$i]));
  208. $sub->($n);
  209. }
  210. );
  211. bless \%state, 'Sequence::Constant';
  212. }
  213. sub generate_actions {
  214. map { /_constant\z/ ? [$_, make_constant_obj($_)] : [$_, $seq->$_] } @_;
  215. }
  216. my @numbers = (map { Math::AnyNum->new($_) } 1 .. 9);
  217. #my @seq = map { 3**$_ + 2} @numbers;
  218. #my @seq = map { 3 * $_ } @numbers;
  219. #my @seq = map { $_ * ($_ + 1) / 2 + 1 } @numbers;
  220. my @seq = map { $_->factorial + 2 } @numbers;
  221. say "\nseq: @seq\n";
  222. my %closed_forms = (
  223. sub_consecutive => sub {
  224. my ($n, $data) = @_;
  225. #"($data->{factor}*$n + $data->{offset})*($data->{factor}*$n + $data->{offset} + 1)/2";
  226. #"($n * ($n+1) / 2)";
  227. $data->{type} eq 'arithmetic'
  228. ? "($n * ($n+1) / 2)"
  229. : "($data->{base}**$n)";
  230. },
  231. add_n => sub {
  232. my ($n, $data) = @_;
  233. #"(2 * ($n) / $data->{factor})";
  234. #"($n / (2 * $data->{factor}))";
  235. #"($n - 1)";
  236. "($n * " . ($data->{factor} - 1) . " / $data->{factor})";
  237. },
  238. div_consecutive => sub {
  239. my ($n) = @_;
  240. "($n!)";
  241. },
  242. add_constant => sub {
  243. my ($n, $data, $const) = @_;
  244. $data->{type} eq 'arithmetic'
  245. ? "($data->{factor}*($n-$constants[$const->{i}-1+$data->{offset}]))"
  246. : die "geometric sequences are not supported, yet!"; # TODO: implement it
  247. },
  248. sub_constant => sub {
  249. my ($n, $data, $const) = @_;
  250. $data->{type} eq 'arithmetic'
  251. ? "($data->{factor}*($n+$constants[$const->{i}-1]+$data->{offset}))"
  252. : "($constants[$const->{i}-1] + $n)"; # wrong
  253. },
  254. div_constant => sub {
  255. my ($n, $data, $const) = @_;
  256. $data->{type} eq 'geometric'
  257. ? "($constants[$const->{i}-1] * $data->{factor} * $data->{base}**$n)"
  258. : "($data->{factor} * $n)"; # wrong
  259. },
  260. );
  261. sub fill_closed_form {
  262. my ($cf, $actions) = @_;
  263. my $result = 'n';
  264. foreach my $action (reverse @$actions) {
  265. my ($name, $obj) = @$action;
  266. #$report .= "name: $name" . (ref($obj) eq 'Sequence::Constant' ? (' (' . $constants[$obj->{i}-1] . ')') : '') . "\n";
  267. if (not exists($closed_forms{$name})) {
  268. warn "No closed-form for rule: $name\n";
  269. next;
  270. }
  271. $result = $closed_forms{$name}($result, $cf, $obj);
  272. }
  273. $result;
  274. #"$result / $cf->{factor} + $cf->{offset}";
  275. }
  276. say '-' x 80;
  277. my %seen;
  278. RULE: foreach my $rule (@rules) {
  279. my @actions = generate_actions(@$rule);
  280. my @const_pos = grep { $rule->[$_] =~ /_constant\z/ } 0 .. $#{$rule};
  281. my $has_const = !!@const_pos;
  282. WHILE: while (1) {
  283. foreach my $group (grep { $_->[0] !~ /_constant\z/ } @actions) {
  284. my $method = $group->[0];
  285. $group->[1] = $seq->$method;
  286. }
  287. my @sequence;
  288. my $stop = $has_const;
  289. foreach my $pos (@const_pos) {
  290. my $constant = $actions[$pos][1];
  291. if ($constant->{done}) {
  292. if ($constant->{i} >= $#constants) {
  293. $constant->{i} = 0;
  294. }
  295. else {
  296. $constant->{i}++;
  297. }
  298. }
  299. else {
  300. if ($constant->{i} >= $#constants) {
  301. $constant->{i} = 0;
  302. $constant->{done} = 1;
  303. }
  304. else {
  305. $constant->{i}++;
  306. }
  307. $stop = 0;
  308. last;
  309. }
  310. }
  311. last if $stop;
  312. foreach my $term (@seq) {
  313. my $result = $term;
  314. foreach my $group (@actions) {
  315. my $action = $group->[1];
  316. if (ref($action) eq 'Sequence::Constant') {
  317. $result = $action->{code}($action, $result);
  318. }
  319. else {
  320. $result = $action->($result);
  321. }
  322. }
  323. next WHILE if ($result <= 0 or not $result->is_real);
  324. push @sequence, $result;
  325. }
  326. if ($sequence[0] >= $sequence[1]) {
  327. $has_const || last;
  328. next;
  329. }
  330. next if $seen{join(';', map { $_->as_rat } @sequence)}++;
  331. say "try: @sequence";
  332. my @closed_forms = $seq->find_closed_form(\@sequence);
  333. if (@closed_forms) {
  334. say "new: @sequence\n";
  335. foreach my $cf (@closed_forms) {
  336. if ($cf->{type} eq 'geometric') {
  337. say "type: $cf->{type}";
  338. say "base: $cf->{base}";
  339. say "fact: $cf->{factor}";
  340. }
  341. elsif ($cf->{type} eq 'arithmetic') {
  342. say "type: $cf->{type}";
  343. say "fact: $cf->{factor}";
  344. say "offs: $cf->{offset}";
  345. }
  346. foreach my $action (@actions) {
  347. my ($name, $obj) = @$action;
  348. say "name: $name" . (ref($obj) eq 'Sequence::Constant' ? " (constant: $constants[$obj->{i}-1])" : '');
  349. }
  350. my $filled = fill_closed_form($cf, \@actions);
  351. say "\n=> Possible closed-form: $filled";
  352. }
  353. say '-' x 80;
  354. }
  355. $has_const || last;
  356. }
  357. }