matrix_path_4-ways_best_3.pl 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. #!/usr/bin/perl
  2. # Author: Daniel "Trizen" Șuteu
  3. # License: GPLv3
  4. # Date: 14 August 2016
  5. # Website: https://github.com/trizen
  6. # Problem from: https://projecteuler.net/problem=83
  7. # (this algorithm is scalable up to matrices of size 80x80)
  8. use 5.010;
  9. use strict;
  10. use warnings;
  11. no warnings 'recursion';
  12. use List::Util qw(min max);
  13. use Term::ANSIColor qw(colored);
  14. my @matrix = map {
  15. [map { int rand 10_000 } 1 .. 15]
  16. } 1 .. 15;
  17. sub draw {
  18. my ($path) = @_;
  19. print "\e[H\e[J\e[H";
  20. my @screen = map {
  21. [map { sprintf "%4s", $_ } @{$_}]
  22. } @matrix;
  23. foreach my $p (@$path) {
  24. my ($i, $j) = @$p;
  25. $screen[$i][$j] = colored($screen[$i][$j], 'red');
  26. }
  27. foreach my $row (@screen) {
  28. say join(' ', @{$row});
  29. }
  30. }
  31. my %seen;
  32. sub valid {
  33. not exists $seen{"@_"};
  34. }
  35. my %two_way_cache;
  36. my $end = $#matrix;
  37. sub two_way_path {
  38. my ($i, $j, $k, $l) = @_;
  39. my $key = "$i $j $k $l";
  40. if (exists $two_way_cache{$key}) {
  41. return $two_way_cache{$key};
  42. }
  43. my @paths;
  44. if ($i < $k) {
  45. push @paths, two_way_path($i + 1, $j, $k, $l);
  46. }
  47. if ($j < $l) {
  48. push @paths, two_way_path($i, $j + 1, $k, $l);
  49. }
  50. $two_way_cache{$key} = $matrix[$i][$j] + (min(@paths) || 0);
  51. }
  52. my @stack;
  53. my $sum = 0;
  54. my ($i, $j) = (0, 0);
  55. my $limit = two_way_path(0, 0, $end, $end);
  56. my $max = max(map { @$_ } @matrix);
  57. my %min = (sum => 'inf');
  58. while (1) {
  59. undef $seen{"$i $j"};
  60. $sum += $matrix[$i][$j];
  61. my @points;
  62. if ($i >= $end and $j >= $end) {
  63. if ($sum < $min{sum}) {
  64. $min{sum} = $sum;
  65. $min{path} = [keys %seen];
  66. }
  67. @stack ? goto STACK: last;
  68. }
  69. # Skip invalid starting paths
  70. if (not($sum <= $limit) or not($sum <= two_way_path(0, 0, $i, $j))) {
  71. goto STACK if @stack;
  72. }
  73. # Skip invalid ending paths (this is a HUGE optimization)
  74. if (not($sum - $matrix[$i][$j] + two_way_path($i, $j, $end, $end) <= $limit + $max)) {
  75. goto STACK if @stack;
  76. }
  77. if ($i > 0 and valid($i - 1, $j)) {
  78. push @points, [$i - 1, $j];
  79. }
  80. if ($j > 0 and valid($i, $j - 1)) {
  81. push @points, [$i, $j - 1];
  82. }
  83. if ($i < $end and valid($i + 1, $j)) {
  84. push @points, [$i + 1, $j];
  85. }
  86. if ($j < $end and valid($i, $j + 1)) {
  87. push @points, [$i, $j + 1];
  88. }
  89. STACK: if (!@points) {
  90. if (@stack) {
  91. my ($s_sum, $s_seen, $s_pos, $s_points) = @{pop @stack};
  92. $sum = $s_sum;
  93. undef %seen;
  94. @seen{@$s_seen} = ();
  95. @points = @$s_points;
  96. ($i, $j) = @$s_pos;
  97. }
  98. else {
  99. last;
  100. }
  101. }
  102. my $min = splice(@points, int(rand(@points)), 1);
  103. if (@points) {
  104. my @ok = (
  105. grep {
  106. my $s = ($sum + $matrix[$_->[0]][$_->[1]]);
  107. $s <= $limit
  108. and ($s <= two_way_path(0, 0, $_->[0], $_->[1]))
  109. and ($sum + two_way_path($_->[0], $_->[1], $end, $end) <= $limit + $max)
  110. } @points
  111. );
  112. if (@ok) {
  113. push @stack, [$sum, [keys %seen], [$i, $j], \@ok];
  114. }
  115. }
  116. ($i, $j) = @$min;
  117. }
  118. my @path = map { [split ' '] } @{$min{path}};
  119. draw(\@path);
  120. say "\nMinimum path-sum is: $min{sum}\n";