Rearrange a number into non-English alphabetical order in Raku

Viewed 159

I'm trying to rearrange a number into non-English alphabetical order.

my @numDE = < null eins zwei drei  vier fünf sechs sieben acht  neun >;
# english   < zero one  two  three four five six   seven  eight nine >;

my %numrank; # The lookup hash: number => its_alphabetical_order

for @numDE.sort.kv -> $k,$v {%numrank{%(@numDE.kv.reverse){"$v"}} = "$k"};

my %temp; # number => reassigned order value  
%temp{"$_"}= %numrank{"$_"} for "2378".comb; # 2378 sample input
say %temp.sort(*.values); # this prints:
# (8 => 0 3 => 1 7 => 7 2 => 9) it's in order but unformatted

my %target= %temp.sort(*.values); # Attempt to print it in order
for %target.kv -> $k,$v {print $k}; # that prints in random order

1- How to print the %temp hash in order?

2- (optional) Is there a way to start using the hash variable without declaring it with my i.e. as it is in Python?

3- (optional) I've chosen the lookup approach to solve this problem and the resulting code looks a bit convoluted. Could the solution be made shorter using any other approaches?

2 Answers

So there are a couple of things that I'd take a look at, but it does look like you're doing a few extra steps (in the comments you said you've tried a few different things, so I imagine you didn't initial do some many extra).

You've first given yourself the german numbers:

my @numDE = <null eins zwei drei vier fünf sechs sieben acht neun>;

Next, you want to be able to order things based on the German spelling. I'll try to use the method you've approached, but I'll show you a simplier way at the end. Your next step is to effectively cache the sort order of them, and store it into a variable "numrank". Building off of @numDE.sort.kv, we get

my %numrank;
for @numDE.sort.kv -> $k, $v {
    %numrank{$v} = $k;
}
say %numrank;
# {acht => 0, drei => 1, eins => 2, fünf => 3, neun => 4, null => 5, sechs => 6, sieben => 7, vier => 8, zwei => 9}

Okay, not bad. Also note that while the output of %numrank appears to be ordered, as a hash, it is inherently unordered. It just happens to print keys in alphabetical as a rule and our keys and values are sorted on those lines. Now we just need to use the actual number as the key, rather than the German name instead.

my %numrank;
for @numDE.sort.kv -> $k, $v {
    my $id == @numDE.first: $v;
    %numrank{$id} = $k;
}
say %numrank;

Oops, we get the same thing. This is because .first returns the actually object. For its index, we just attach the :k adverb:

my %numrank;
for @numDE.sort.kv -> $k, $v {
    my $id == @numDE.first: $v, :k;
    %numrank{$id} = $k;
}
say %numrank;
# {0 => 5, 1 => 2, 2 => 9, 3 => 1, 4 => 8, 5 => 3, 6 => 6, 7 => 7, 8 => 0, 9 => 4}

Perfect, now we can see that the value for 8 (acht) is 0 as it's first, and for 2 (zwei) is 9, as it's last. Note that we could also have used an array here, since our indices are numbers (using @numrank and then doing @numrank[$id] = $k)

Now to sort some stuff. In your code you have

%temp{"$_"}= %numrank{"$_"} for "2378".comb; # 2378 sample input

This creates an unordered hash, wherein the name of each key is a digit, and its value is its rank. That's basically what we had above in the first attempt at making %numrank. But because %temp is a hash, if you have any two digits that repeat, you'll lose the extras:

%temp{"$_"}= %numrank{"$_"} for "222".comb;
# {2 => 9} 

Instead, I think you want to create an array which can allow for ordering:

my @temp = ($_ => %numrank{"$_"}) for "22378".comb;
# ^^ both 2s are preserved

Now you can simply sort on the values:

say @temp.sort: *.values;

You could loop directly on this:

for @temp.sort(*.values) {
   print .key;
}

The simpler way

"2378".comb.sort: { @numDE[$^digit] }
# (8 3 7 2) # acht drei seiben zwei 

Here we sort the combed digits based on the German text form of each number. @numDE as the names of the numbers, and $^digit is an implicit variable that holds the digit (the [ ] automatically coerces it to a number for us). If you plan on using it regularly, you can actually store the block in a variable, like this:

my &sort-de = sub ($digit) { @numDE[$digit] };
"87446229".comb.sort: &sort-de;
# (8 9 6 7 4 4 2 2)

And as mentioned above, you can do the for loop directly on this, if you want to style it in some other manner:

for "87446229".comb.sort(&sort-de) {
   say $_
}

One way to think about this problem is as a translation issue: we need to translate one ordering convention into another ordering convention.

Start by setting up the first of a pair of indices that allow ordering of numeric elements alphabetically, in German (code snippets performed in the Raku REPL):

> my @a = @numDE
[null eins zwei drei vier fünf sechs sieben acht neun]
> my @b = @a.pairs.sort(*.values)>>.keys.flat;
[8 3 1 5 9 0 6 7 4 2]
> say @a[@b]
(acht drei eins fünf neun null sechs sieben vier zwei)

Above, we show that we can reorder by index @b. But how to get @b back to its original order? By deriving a second 'originating' index @c:

> my @c = @b.pairs.sort(*.values)>>.keys.flat;
[5 2 9 1 8 3 6 7 0 4]
> say @b[@c]
(0 1 2 3 4 5 6 7 8 9)

Okay, now we have the correct 'translation' that we need to solve the problem:

> "2378".comb.trans( 0..9 => @c ).comb.sort.trans( @c => 0..9 ).trim.say
8 3 7 2

EDIT--Second example below:

> "87446229".comb.trans( 0..9 => @c ).comb.sort.trans( @c => 0..9 ).trim.say
8 9 6 7 4 4 2 2

Basically we translate the original value to an 'originating index' value, comb, sort, and then finally we back-translate the 'originating index' value to the original.

( "8 3 7 2" is "acht drei seiben zwei". Thanks for your examples @user0721090601 ! ).
( Note, trim is used to clean up some formatting issues ).

HTH.

Related