DEV Community

Cover image for Weekly Challenge #081 Task #2 :: Raku
Myoungjin Jeon
Myoungjin Jeon

Posted on

Weekly Challenge #081 Task #2 :: Raku

TASK #2 › Frequency Sort

You are given file named "input".

Write a script to find the frequency of all the words.

It should print the result as first column of each line should be
the frequency of the the word followed by all the words of that
frequency arranged in lexicographical order. Also sort the words in 
the ascending order of frequency.

INPUT file looks like

West Side Story

The award-winning adaptation of the classic romantic tragedy "Romeo and Juliet". The feuding families become two warring New York City gangs, the white Jets led by Riff and the Latino Sharks, led by Bernardo. Their hatred escalates to a point where neither can coexist with any form of understanding. But when Riff's best friend (and former Jet) Tony and Bernardo's younger sister Maria meet at a dance, no one can do anything to stop their love. Maria and Tony begin meeting in secret, planning to run away. Then the Sharks and Jets plan a rumble under the highway--whoever wins gains control of the streets. Maria sends Tony to stop it, hoping it can end the violence. It goes terribly wrong, and before the lovers know what's happened, tragedy strikes and doesn't stop until the climactic and heartbreaking ending.

NOTE

For the sake of this task, please ignore the following in the input file:

. " ( ) , 's --
OUTPUT

1 But City It Jet Juliet Latino New Romeo Side Story Their Then 
West York adaptation any anything at award-winning away become 
before begin best classic climactic coexist control dance do 
doesn't end ending escalates families feuding form former friend 
gains gangs goes happened hatred heartbreaking highway hoping in
know love lovers meet meeting neither no one plan planning point 
romantic rumble run secret sends sister streets strikes terribly 
their two under understanding until violence warring what when 
where white whoever wins with wrong younger
2 Bernardo Jets Riff Sharks The by it led tragedy
3 Maria Tony a can of stop
4 to
9 and the

Reading a file in Raku

reading a file in Raku is really easy and straight forward
this task states that the file name which we want to read is already "input"

sub MAIN {
    if "input".IO.r.not {
        die "`input' is not readable";
    }
}

This example shows how we can test "input" file path is readable or throw an error(or exception?) we can write shorter by using "or"

    "input".IO.r or die "`input' is not readable";
    # or ...
    die "`nope!" if "input".IO.r.not;
    # or ...
    die "`I said nope!" unless "input".IO.r;

Raku also sounds more natural language if you want to write like that. (but this is only preference.)

like ".r" there is some subroutines make our job easier.
".lines" and ".words"

for "input".IO.lines -> line-number, line-string {
    say "$line-number: line-string";
}

in this task we can use ".words" of course.

What I'm impressed is the next step.

Bag

Raku provides in the their native language. "yes, we can use a hash as a bag" but this makes job look clear and pleasant.

> raku -e '"yes, we can use a hash as a bag".words.Bag.raku.say'
("hash"=>1,"as"=>1,"we"=>1,"use"=>1,"bag"=>1,"yes,"=>1,"can"=>1,"a"=>2).Bag

each value indicates that the frequency of words.
(note: you can use '-e' option to execute one liner programme)

Now we might need to invert key and value so that we can sort on frequency.

sub MAIN {
    my $in = "input".IO;
    $in.r or die "no!" x 5;

    my %r;
    for bag($in.words) -> $as-pair {
        say "will change to {$as-pair.value} => {$as-pair.key}";
    }
}
shell> raku ch-2.raku | head
will change to 1 => planning
will change to 1 => end
will change to 2 => it
will change to 1 => run
will change to 1 => warring
will change to 3 => stop
will change to 1 => award-winning
will change to 1 => City
will change to 1 => York
will change to 1 => happened

there is ".invert" but I don't think we need it here.

so I'm going to save the result as a hash variable

sub MAIN {
    my $in = "input".IO;
    $in.r or die "no!" x 5;

    my %r;
    %r{.value}.push(.key) for bag($in.words);
}

we can use .map but .map will always return something even if we don't use at all so for is sometimes better choice.

Sort

and sorting is easy just because we have sort

sub MAIN {
    my $in = "input".IO;
    $in.r or die "no!" x 5;

    my %r;
    %r{.value}.push(.key) for bag($in.words);
    %r.sort.put;
}

I was about to finish the task but we need the words in lexicographical order so ...

sub MAIN {
    my $in = "input".IO;
    $in.r or die "no!" x 5;

    my %r;
    %r{.value}.push(.key) for bag($in.words);
    for %r -> $as-pair {
        %r{$as-pair.key} = sort %r{$as-pair.key}
    }
    %r.sort.join("\n").put;
}

This is good enough. 😎 filtering words is skipped in order to focus on how to make a bag and sort them.

Final Code

final code is different as always because I use more shortcuts.

with "input".IO {
    .r or die "no `input' file";
    my %r;
    %r{.value}.push(.key).=sort
        for bag(.words>>.subst(/"'s"||<[\W]-[-]>||"--"/,'',:g));
    %r.sort.join("\n").put;
}

but I leave some reference here. 🧐
with
Regex
subst
>>

Thank you for reading!
please visit https://perlweeklychallenge.org/ and contribute your code if interested~!! 🙏

Discussion (0)