exercise: AllYourBase version: 2 plan: 23 imports: '&convert-base' tests: | sub test ($case, $expected) { is-deeply &::('convert-base')(|$case), $expected, $case } for @($c-data) { when . ~~ Array { test $_, . } when . ~~ /base|digit/ { throws-like {&::('convert-base')(|.)}, Exception, . } when . eq 'leading zeros' { test $_, [4,2] } when . eq 'empty list' { test $_, [] } when . ~~ /zero/ { test $_, [0] } default { flunk . } # To ensure that no canonical-data cases are missed. } unit: module example: | class X::AllYourBase::InvalidBase is Exception { has $.payload; method message {"$!payload is not a valid base."} } class X::AllYourBase::InvalidDigit is Exception { has %.payload; method message {"%!payload is not a valid digit for base %!payload."} } sub convert-base (Int:D $input-base, @input-digits, Int:D $output-base --> Array:D) is export { for $input-base, $output-base { X::AllYourBase::InvalidBase.new(payload => $_).throw if $_ < 2; } from-decimal($output-base, (to-decimal $input-base, @input-digits)); } sub to-decimal ($input-base, @input-digits) { return [].Slip if !@input-digits; my $elems = @input-digits.elems; for @input-digits { if $_ == 0 { $elems-- } else { last } } my $dec = 0; loop (my $i = 0; $i < $elems; $i++) { if @input-digits.reverse[$i] < 0 || @input-digits.reverse[$i] >= $input-base { X::AllYourBase::InvalidDigit.new( :payload(base => $input-base, digit => @input-digits.reverse[$i]) ).throw; } $dec += @input-digits.reverse[$i] * $input-base ** $i; } return $dec; } sub from-decimal ($output-base, $dec) { my @output-digits; my $num = $dec; while $num >= $output-base { unshift @output-digits, $num % $output-base; $num div= $output-base; } unshift @output-digits, $num; return @output-digits; }