I’ve been doing the Weekly
Challenges. The
latest
involved character duplication and number to word conversion. (Note
that this ends today.)
Task 1: Echo Chamber
You are given a string containing lowercase letters.
Write a script to transform the string based on the index position
of each character (starting from 0). For each character at
position i, repeat it i + 1 times.
Some languages have string repeats built in. Some don't, either
because a single character is a distinct entity or because it adds too
much complication. Lua does, and even has 1-based indices:
function echochamber(a)
local out = ""
for i, c in ipairs(split(a)) do
out = out .. string.rep(c, i)
end
return out
end
Task 2: Spellbound Sorting
You are given an array of integers.
Write a script to return them in alphabetical order, in any language
of your choosing. Default language is English.
I didn't fancy doing it in French, so I borrowed an algorithm from
here and
adapted it (a) to cope with negative numbers and (b) from en_US to
en_GB (where we put "and" between a quantity of hundreds and a
quantity of less than that, thus: "two hundred and fifty three").
(All right, as I wrote it it won't work for "1001" but never mind.)
One could certainly make a case for outputting "ninety-nine" rather
than "ninety nine", but I didn't try to implement that, (a) for
simplicity and (b) because it should make no difference to the sorting
result. (Style guides tend to favour sticking with figures for
anything over ten anyway.)
After that it's just a matter of implementing a sort by key, and we
did that in 360 task 2.
In Raku:
The outer sort-by-key wrapper, which calls towords on each value in
the input list.
sub spellboundsorting(@a) {
my @o = @a;
@o = @o.sort({towords($_)});
@o;
}
The upper layer of towords.
sub towords($a) {
If it's a zero, just return zero. It's much easier to work out whether
there's anything left to do if we know that any value we have to deal
with will be positive.
if ($a == 0) {
return 'zero';
}
I'm going to build a list of words.
my @components;
If it's negative, stick "minus" on the front and negate it. Again,
simpler than writing the algorithm to deal with this directly.
my $b = $a;
if ($a < 0) {
$b = -$a;
@components.push("minus");
}
Here's the lookup table for values to words. In an array of arrays not
a hash, because I'll be looking at them in this order. Yes, I am being
deliberately archaic here, and if someone's AI nonsense generator
starts talking about milliards I'll know where it came from.
my @vw = [
[1000000000, "milliard"],
[1000000, "million"],
[1000, "thousand"],
[100, "hundred"],
[90, "ninety"],
[80, "eighty"],
[70, "seventy"],
[60, "sixty"],
[50, "fifty"],
[40, "forty"],
[30, "thirty"],
[20, "twenty"],
[19, "nineteen"],
[18, "eighteen"],
[17, "seventeen"],
[16, "sixteen"],
[15, "fifteen"],
[14, "fourteen"],
[13, "thirteen"],
[12, "twelve"],
[11, "eleven"],
[10, "ten"],
[9, "nine"],
[8, "eight"],
[7, "seven"],
[6, "six"],
[5, "five"],
[4, "four"],
[3, "three"],
[2, "two"],
[1, "one"],
];
Break down the value into more words.
for cw($b, @vw) -> $w {
@components.push($w);
}
Join those words for the final output.
@components.join(' ');
}
Here's where we break down a value.
sub cw($n, @vw) {
my @res = [];
Look at each candidate value in turn.
for @vw -> ($val, $word) {
If our value is larger or equal,
if ($n >= $val) {
my $andflag = False;
If there's a hundreds or larger component to it, handle that part
recursively. (E.g. for 405 this will produce "four".)
if ($n >= 100) {
$andflag = True;
for cw(floor($n / $val), @vw) -> $w {
@res.push($w);
}
}
Push on this specific value's word ("hundred").
@res.push($word);
If it's not evenly divisible, handle the smaller part recursively
("and five").
my $p = $n % $val;
if ($p > 0) {
if ($andflag) {
@res.push("and");
}
for cw($p, @vw) -> $w {
@res.push($w);
}
}
Then break out and don't look at any smaller values.
last;
}
}
@res;
}
Of course in PostScript I can just drop each word onto the stack as I
go…
Full code on
github.