OEIS/gen meander.pl

From tehowiki
Jump to navigation Jump to search
#!/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__