This is my response to The Weekly Challenge #357.
Write a function that takes a 4-digit integer and returns how many iterations
are required to reach Kaprekar’s constant (6174). For more information about
Kaprekar's Constant please follow the
wikipedia page.
Input: $int = 3524
Output: 3
Iteration 1: 5432 - 2345 = 3087
Iteration 2: 8730 - 0378 = 8352
Iteration 3: 8532 - 2358 = 6174
Example 2:
Input: $int = 6174
Output: 0
Example 3:
Input: $int = 9998
Output: 5
Iteration 1: 9998 - 8999 = 0999
Iteration 2: 9990 - 0999 = 8991
Iteration 3: 9981 - 1899 = 8082
Iteration 4: 8820 - 0288 = 8532
Iteration 5: 8532 - 2358 = 6174
Example 4:
Input: $int = 1001
Output: 4
Iteration 1: 1100 - 0011 = 1089
Iteration 2: 9810 - 0189 = 9621
Iteration 3: 9621 - 1269 = 8352
Iteration 4: 8532 - 2358 = 6174
Example 5:
Input: $int = 1111
Output: -1
The sequence does not converge on 6174, so return -1.
File: kaprekar-constant
#! /usr/bin/env raku
unit sub MAIN (Int $int is copy where 1000 <= $int <= 9999, # [1]
:v(:$verbose));
my $iterations = 0; # [2]
constant $target = 6174; # [3]
while $int != $target # [4]
{
$iterations++; # [5]
my $small = $int.comb.sort.join; # [6]
my $large = $small.flip; # [7]
$large *= 10 while $large < 1000; # [8]
$int = $large - $small; # [9]
say ": $iterations: $large - $small = $int" if $verbose;
{ say "-1"; exit; } unless $int; # [10]
}
say $iterations; # [11]
[1] Ensure a four digit integer. The is copy adverb is
there to make it possible to change the value later (see [7]).
See docs.raku.org/type/Parameter#method_copy for more information about is copy.
[2] The result; the number of iterations. None so far.
[3] The target. Declared as a constant, as it is
constant.
See docs.raku.org/syntax/constant for more information about constant.
[4] As long as we have not reached the target.
[5] One more iteration to count.
[6] Split the integer into digits (with comb), sort them
(smallest first) and glue them together again (with join).
[7] Reverse the string with flip.
See docs.raku.org/routine/flip for more information about flip.
[8] Any leading zeroes of $int are missing. See the verbose output
from the third example for why this may occur. We fix this by adding them
as trailing zeroes on the large number.
[9] Get the new integer.
[10] Prevent an eternal loop, which we would get if we reached zero, as described in the wikipedia article.
[11] Print the number of iterations.
Running it:
$ ./kaprekar-constant 3524
3
$ ./kaprekar-constant 6174
0
$ ./kaprekar-constant 9998
5
$ ./kaprekar-constant 1001
4
$ ./kaprekar-constant 1111
-1
Looking good.
With verbose mode:
$ ./kaprekar-constant -v 3524
: 1: 5432 - 2345 = 3087
: 2: 8730 - 0378 = 8352
: 3: 8532 - 2358 = 6174
3
$ ./kaprekar-constant -v 6174
0
$ ./kaprekar-constant -v 9998
: 1: 9998 - 8999 = 999
: 2: 9990 - 999 = 8991
: 3: 9981 - 1899 = 8082
: 4: 8820 - 0288 = 8532
: 5: 8532 - 2358 = 6174
5
$ ./kaprekar-constant -v 1001
: 1: 1100 - 0011 = 1089
: 2: 9810 - 0189 = 9621
: 3: 9621 - 1269 = 8352
: 4: 8532 - 2358 = 6174
4
$ ./kaprekar-constant -v 1111
: 1: 1111 - 1111 = 0
-1
Input: $int = 3
Output: 1/3, 1/2, 2/3, 1/1, 3/2, 2/1, 3/1
Example 2:
Input: $int = 4
Output: 1/4, 1/3, 1/2, 2/3, 3/4, 1/1, 4/3, 3/2, 2/1, 3/1, 4/1
Example 3:
Input: $int = 1
Output: 1/1
Example 4:
Input: $int = 6
Output: 1/6, 1/5, 1/4, 1/3, 2/5, 1/2, 3/5, 2/3, 3/4,
4/5, 5/6, 1/1, 6/5, 5/4, 4/3, 3/2, 5/3, 2/1,
5/2, 3/1, 4/1, 5/1, 6/1
Example 5:
Input: $int = 5
Output: 1/5, 1/4, 1/3, 2/5, 1/2, 3/5, 2/3, 3/4, 4/5, 1/1,
5/4, 4/3, 3/2, 5/3, 2/1, 5/2, 3/1, 4/1, 5/1
Raku's built in support for rational numbers makes this easy:
> 1/3 + 1/3 + 1/3; # -> 1
> (1/2).WHAT; # -> (Rat)
See docs.raku.org/type/Rat for more information about the Rat type.
#! /usr/bin/env raku
unit sub MAIN (Int $int where $int > 0, :v(:$verbose)); # [1]
my @fractions; # [2]
my @verbose;
for 1 .. $int -> $numerator # [3]
{
for 1 .. $int -> $denominator # [4]
{
@fractions.push: $numerator / $denominator; # [5]
@verbose.push: "$numerator/$denominator" if $verbose;
}
}
say ": Added: { @verbose.join(", ") }" if $verbose;
say @fractions.sort.squish.map({ .numerator ~ "/"
~ .denominator }).join(", "); # [6]
[1] Ensure a positive integer.
[2] The fractions will be collected here, with duplicates.
[3] Iterate over the numerators,
[4] and the denominators.
[5] Calculate the fraction and add it to the list.
[6]
We are requested to print the values in ascending
order, so we sort them. (The values are numeric, so they are sorted
numerically.) Then we apply squish to get rid of duplicates (*).
(squish only works on sorted data, which we have here. Use
unique if the data is unsorted.) Then we use map to
explicitly print the numerator, / and the denominator
version of the values, as a comma separated string. (Just printing
the values would have given e.g. 4 instead of 4/1.)
* = 2/4 and 1/2 have the same numeric value, and the
first one is actually reduced to the same numerator and denominator as
the second one internally.
See docs.raku.org/routine/squish for more information about squish.
See docs.raku.org/routine/numerator for more information about numerator.
See docs.raku.org/routine/denominator for more information about denominator.
Running it:
$ ./ufg 3
1/3, 1/2, 2/3, 1/1, 3/2, 2/1, 3/1
$ ./ufg 4
1/4, 1/3, 1/2, 2/3, 3/4, 1/1, 4/3, 3/2, 2/1, 3/1, 4/1
$ ./ufg 1
1/1
$ ./ufg 6
1/6, 1/5, 1/4, 1/3, 2/5, 1/2, 3/5, 2/3, 3/4, 4/5, 5/6, 1/1, 6/5, 5/4, \
4/3, 3/2, 5/3, 2/1, 5/2, 3/1, 4/1, 5/1, 6/1
$ ./ufg 5
1/5, 1/4, 1/3, 2/5, 1/2, 3/5, 2/3, 3/4, 4/5, 1/1, 5/4, 4/3, 3/2, 5/3, \
2/1, 5/2, 3/1, 4/1, 5/1
Looking good.
With verbose mode, showing the duplicates:
$ ./ufg -v 3
: Added: 1/1, 1/2, 1/3, 2/1, 2/2, 2/3, 3/1, 3/2, 3/3
1/3, 1/2, 2/3, 1/1, 3/2, 2/1, 3/1
$ ./ufg -v 4
: Added: 1/1, 1/2, 1/3, 1/4, 2/1, 2/2, 2/3, 2/4, 3/1, 3/2, 3/3, 3/4, \
4/1, 4/2, 4/3, 4/4
1/4, 1/3, 1/2, 2/3, 3/4, 1/1, 4/3, 3/2, 2/1, 3/1, 4/1
$ ./ufg -v 1
: Added: 1/1
1/1
$ ./ufg -v 6
: Added: 1/1, 1/2, 1/3, 1/4, 1/5, 1/6, 2/1, 2/2, 2/3, 2/4, 2/5, 2/6, \
3/1, 3/2, 3/3, 3/4, 3/5, 3/6, 4/1, 4/2, 4/3, 4/4, 4/5, 4/6, 5/1, 5/2, \
5/3, 5/4, 5/5, 5/6, 6/1, 6/2, 6/3, 6/4, 6/5, 6/6
1/6, 1/5, 1/4, 1/3, 2/5, 1/2, 3/5, 2/3, 3/4, 4/5, 5/6, 1/1, 6/5, 5/4, \
4/3, 3/2, 5/3, 2/1, 5/2, 3/1, 4/1, 5/1, 6/1
$ ./ufg -v 5
: Added: 1/1, 1/2, 1/3, 1/4, 1/5, 2/1, 2/2, 2/3, 2/4, 2/5, 3/1, 3/2, \
3/3, 3/4, 3/5, 4/1, 4/2, 4/3, 4/4, 4/5, 5/1, 5/2, 5/3, 5/4, 5/5
1/5, 1/4, 1/3, 2/5, 1/2, 3/5, 2/3, 3/4, 4/5, 1/1, 5/4, 4/3, 3/2, 5/3, \
2/1, 5/2, 3/1, 4/1, 5/1
And that's it.