#!/usr/bin/perl
# Generate a meander sequence
# 2017-08-31, Georg Fischer
# Program in the public domain
# c.f. <http://www.teherba.org/index.php/OEIS/A220952>
use strict;
use integer; # avoid division problems with reals
my $debug = 0;
my $ansi = 0; # whether to use ANSI colors on console output
my $bfile = 0; # whether to print b-file
my $graph = 0; # whether to plot y,x behind the b-file entries
my $fail = 0;
my $sep = "/";
my $base = 5;
my $fbase = 10;
my $tbase = $base;
my $ident = "xx";
my $limit = 125;
my @path = (0,1,2,3,4,9,14,19,18,17,16,11,12,13,8,7,6,5,10,15,20,21,22,23,24);
# $bpath = "00/01/02/03/04/14/24/34/33/32/31/21/22/23/13/12/11/10/20/30/40/41/42/43/44/"; # defines the meander sequence
# print " 0 3 6 9 12 15 18 21 24 27 30 33 36 39 42 45 48 51 54 57 60 63 66 69 72\n" if $debug >= 1;
while (scalar(@ARGV) > 0 and ($ARGV[0] =~ m{\A\-})) { # start with hyphen
my $opt = shift(@ARGV);
if (0) {
} elsif ($opt eq "\-a") {
$ansi = 1;
} elsif ($opt eq "\-b") {
$bfile = 1;
} elsif ($opt eq "\-d") {
$debug = shift(@ARGV);
} elsif ($opt eq "\-g") {
$graph = 1;
} elsif ($opt eq "\-i") {
$ident = shift(@ARGV);
} elsif ($opt eq "\-l") {
$limit = shift(@ARGV);
} elsif ($opt eq "\-p") {
@path = split(/\,/, shift(@ARGV));
}
} # while opt
print "<!-================================-_>\n";
my $bpath = join("", map { my $bnum = &to_base($_); (length($bnum) < 2 ? "0$bnum" : $bnum) . $sep} @path);
print "<meander id=\"$ident\" path=\"" . join(",", @path) . "\"\n"
. " bpath=\"$bpath\"\n"
. " >\n";
my $ind = 1;
if (1) { # generate b-file
print "<b$base-file>\n";
$ind = 1;
my $ind_1 = $ind - 1;
my $bprev = "9";
my $bcurr = "0";
print "0 0\n";
while ($fail == 0 and $ind <= $limit) {
my $bnext = &get_successor($bprev, $bcurr);
if (length($bnext) > 8) {
$fail = 1;
} else { # not yet_failed
if ($bfile > 0) {
print "$ind $bnext\n";
} else {
if (length($bcurr) != length($bnext)) {
$ind_1 = $ind - 1;
print "$ind_1 $bcurr\n$ind $bnext\n";
}
}
} # not yet failed
$bprev = $bcurr;
$bcurr = $bnext;
$ind ++;
} # while $ind
print "</b$base-file>\n";
} # b-file
if ($fail == 0) { # success
&draw_path(@path);
if ($graph > 0) {
&draw_graph();
}
} # success
print "</meander>\n";
#--------
sub get_successor {
# get the successor node $bnext of a pair ($bprev, $bcurr)
our @cands = (); # candidates
#----
sub add_candidate {
# add a candidate
#----
sub adjust {
# make sure that both arguments start with "0" and have the same length
my ($bnum1, $bnum2) = @_;
while (length($bnum1) < length($bnum2)) {
$bnum1 = "0$bnum1";
}
while (length($bnum2) < length($bnum1)) {
$bnum2 = "0$bnum2";
}
if (substr($bnum1, 0, 1) != "0" or
substr($bnum2, 0, 1) != "0") {
$bnum1 = "0$bnum1";
$bnum2 = "0$bnum2";
}
return ($bnum1, $bnum2);
} # adjust
#----
sub is_adjacent {
# check whether 2 nodes are adjacent
my ($bprev, $bcurr) = @_;
($bprev, $bcurr) = &adjust($bprev, $bcurr);
# print "# is_adjacent(bprev=$bprev, bcurr=$bcurr) \n" if ($debug >= 2);
my $adjacent = 1;
my $width = length($bcurr);
my $j = $width - 1;
while ($adjacent == 1 and $j > 0) {
my $i = $j - 1;
while ($adjacent == 1 and $i >= 0) {
my $ppair = substr($bprev, $i, 1) . substr($bprev, $j, 1);
my $cpair = substr($bcurr, $i, 1) . substr($bcurr, $j, 1);
my $ppos = index($bpath, $ppair);
my $cpos = index($bpath, $cpair);
if ($ppair != $cpair and abs($cpos - $ppos) != 3) {
print "\n# is_adjacent($bprev,$bcurr): ppair=$ppair, cpair=$cpair, ppos=$ppos, cpos=$cpos\n" if $debug >= 2;
$adjacent = 0;
}
$i --;
} # while $i
$j --;
} # while $j
return $adjacent;
} # is_adjacent
#----
my ($npair, $bprev, $bcurr, $i, $j) = @_;
my $width = length($bcurr);
print "# add_candidate(npair=$npair, i=$i, j=$j)" if ($debug >= 1);
my $bcand = substr($bcurr, 0, $i) # before i
. substr($npair, 0, 1) # 1st digit -> [$i]
. substr($bcurr, $i + 1, $j - $i - 1) # between i and j
. substr($npair, 1, 1) # 2nd digit -> [$j]
. substr($bcurr, $j + 1, $width - 1 - $j) # behind j
;
if (0) {
} elsif (&is_adjacent($bcurr, $bcand) == 0) {
print "# $bcurr not adjacent to $bcand" if $debug >= 1;
} elsif ($bcand eq $bprev) {
print " $bcand=bprev" if $debug >= 1;
} else {
my $cpos = substr($bcurr, $i, 1) ne substr($bcand, $i, 1) ? $i : $j; # else change must be at j
my $icand = 0;
my $busy = 1;
while ($busy == 1 and $icand < scalar(@cands)) {
$busy = $cands[$icand] ne $bcand ? 1 : 0;
$icand ++;
} # while icand
if ($busy == 1) { # not yet stored
# $bcand =~ s{\A0}{};
push(@cands, $bcand);
} # not yet stored
print " -> bcand=$bcand" if $debug >= 1;
}
print "\n" if ($debug >= 1);
} # add_candidate
#----
my ($bprev, $bcurr) = @_;
($bprev, $bcurr) = &adjust($bprev, $bcurr);
print "# get_successor(bcurr=$bcurr)\n" if ($debug >= 1);
@cands = ();
my $width = length($bcurr);
my $j = $width - 1;
while ($j > 0) {
my $i = $j - 1;
while ($i >= 0) {
my $cpair = substr($bcurr, $i, 1) . substr($bcurr, $j, 1);
my $cpos = index($bpath, $cpair);
print "# cpair=$cpair -> cpos=$cpos\n" if ($debug >= 1);
if ($cpos >= 3) { # before
&add_candidate(substr($bpath, $cpos - 3, 2), $bprev, $bcurr, $i, $j);
} # before
if ($cpos < length($bpath) - 3) { # behind
&add_candidate(substr($bpath, $cpos + 3, 2), $bprev, $bcurr, $i, $j);
} # behind
$i --;
} # while $i
$j --;
} # while $j
my $cand = "";
print "# get_successor: " if ($debug >= 1);
my $lcand = scalar(@cands);
if (0) {
} elsif ($lcand > 1) {
print "# more than 1 candidate for $bcurr @ $ind" . ", cands=" . join(",", @cands) . "\n";
$fail = 1;
} elsif ($lcand < 1) {
print "# no candidate for $bcurr at $ind\n";
$fail = 1;
} else { # $lcand == 1
$cand = $cands[0];
$cand =~ s{\A0+}{};
if (length($cand) > 16) {
print "# $cand exploding @ $ind\n";
$fail = 1;
}
}
print "\n" if $debug >= 1;
return $cand;
} # get_successor
#--------
sub to_base {
# return a normal integer as number in base $tbase
my ($num) = @_;
my $result = "";
while ($num > 0) {
my $digit = $num % $tbase;
$result = $digit . $result;
$num /= $tbase;
} # while > 0
return $result eq "" ? "0" : $result;
} # to_base
#--------
sub from_base {
# return a number in base $fbase (string, maybe with letters) as normal integer
my ($num) = @_;
my $bpow = 1;
my $result = 0;
my $pos = length($num) - 1;
while ($pos >= 0) { # from backwards
my $digit = substr($num, $pos, 1);
if ($digit < 0) {
print STDERR "invalid digit in number $num\n";
}
$result += $digit * $bpow;
$bpow *= $fbase;
$pos --;
} # positive
return $result;
} # from_base
#--------
sub draw_graph {
print "<draw-graph>\n";
$ind = 1;
my $ind_1 = $ind - 1;
my $bprev = "9";
my $bcurr = "0";
print "0 0\n";
while ($fail == 0 and $ind <= $limit) {
my $bnext = &get_successor($bprev, $bcurr);
print sprintf("%-10s", "$ind $bnext")
. ($graph > 0 ? "|" . " " x (&from_base($bnext) + 1) . "@" : "")
. "\n";
$bprev = $bcurr;
$bcurr = $bnext;
$ind ++;
} # while $ind
print "</draw-graph>\n";
} # draw_graph
#--------
sub draw_path {
our $vert = "||"; if ($ansi == 1) { $vert = "\x1b[103m$vert\x1b[0m"; }
our $hori = "=="; if ($ansi == 1) { $hori = "\x1b[103m$hori\x1b[0m"; }
our @matrix = ();
our $blan = " ";
#----
sub get_matrix_pos {
my ($x, $y) = @_;
my $base2_1 = $base * 2 - 1; # 9 for base=5
return $x * 2 + ($base2_1 - 1) * $base2_1 - $y * 2 *$base2_1;
} # get_matrix_pos
#----
sub get_digit {
# return the value of a digit from a string in $base representation
# $base <= 10 for the moment, but hex is prepared
my ($num, $pos) = @_; # pos is 0 for last character
my $bum = &based0($num);
return substr($bum, length($bum) - 1 - $pos, 1);
} # get_digit
#----
sub based0 {
# return a number in base $base,
# filled to $maxexp - 1 with leading zeroes
my $maxexp = 2; # for drawing the start path only!
my ($num) = @_;
my $result = "";
my $ind = 0;
while ($ind < $maxexp) {
$result = ($num % $base) . $result;
$num /= $base;
$ind ++;
} # while $idig
return $result;
} # based0
#----
sub connect {
my ($pa0, $pa1) = @_;
if ($pa0 > $pa1) { # exchange, make p1 smaller
my $temp = $pa0;
$pa0 = $pa1;
$pa1 = $temp;
} # pa0 <= pa1
my $ba0 = &based0($pa0);
my $ba1 = &based0($pa1);
print "ba0=$ba0, ba1=$ba1" if $debug >= 2;
my $x0 = &get_digit($pa0, 1);
my $y0 = &get_digit($pa0, 0);
my $x1 = &get_digit($pa1, 1);
my $y1 = &get_digit($pa1, 0);
print ", x0=$x0, y0=$y0, x1=$x1, y1=$y1" if $debug >= 2;
my $mp0 = &get_matrix_pos($x0, $y0);
if ($x0 eq $x1) { # up
$matrix[$mp0 - ($base * 2 - 1)] = $vert; # up
print " $vert\n" if $debug >= 2;
} else {
$matrix[$mp0 + 1] = $hori; # right
print " $hori\n" if $debug >= 2;
}
} # connect
#----
# initialize the matrix
my $x = 0;
my $y = 0;
while ($x < $base) {
$y = 0;
while ($y < $base) {
my $mp = &get_matrix_pos($x, $y);
$matrix[$mp] = $ansi == 1 ? "\x1b[102m$x$y\x1b[0m" : "$x$y";
if ($x < $base - 1) {
$matrix[$mp + 1] = $blan; # " "; # right
}
if ($y > 0) {
$matrix[$mp + $base * 2 - 1] = $blan; # " "; # down
if ($x < $base - 1) {
$matrix[$mp + $base * 2 - 1 + 1] = $blan; # " "; # down
}
}
$y ++;
} # while y
$x ++;
} # while $x
my $ipa = 1;
while ($ipa < scalar(@path)) {
&connect($path[$ipa - 1], $path[$ipa]);
$ipa ++;
} # while $ipa
print "<draw-path>\n\n";
my $imp = 0;
while ($imp < scalar(@matrix)) { # print
print "$matrix[$imp]";
$imp ++;
if ($imp % ($base * 2 - 1) == 0) {
print "\n";
}
} # printing
print "\n</draw-path>\n";
} # draw_path
#--------
__DATA__