diff options
| author | PerlMonk-Athanasius <PerlMonk.Athanasius@gmail.com> | 2022-05-29 23:36:55 +1000 |
|---|---|---|
| committer | PerlMonk-Athanasius <PerlMonk.Athanasius@gmail.com> | 2022-05-29 23:36:55 +1000 |
| commit | 78ac8ae58db45a4c86c7056af3c1f27062357007 (patch) | |
| tree | 70d55743c5623171dea336958d72e65d07b0be2b /challenge-166 | |
| parent | 5cb0fb12db949edd184f8510cb110fa870e07e20 (diff) | |
| download | perlweeklychallenge-club-78ac8ae58db45a4c86c7056af3c1f27062357007.tar.gz perlweeklychallenge-club-78ac8ae58db45a4c86c7056af3c1f27062357007.tar.bz2 perlweeklychallenge-club-78ac8ae58db45a4c86c7056af3c1f27062357007.zip | |
Perl & Raku solutions to Tasks 1 & 2 for Week 166
Diffstat (limited to 'challenge-166')
| -rw-r--r-- | challenge-166/athanasius/perl/ch-1.pl | 259 | ||||
| -rw-r--r-- | challenge-166/athanasius/perl/ch-2.pl | 280 | ||||
| -rw-r--r-- | challenge-166/athanasius/raku/ch-1.raku | 268 | ||||
| -rw-r--r-- | challenge-166/athanasius/raku/ch-2.raku | 277 |
4 files changed, 1084 insertions, 0 deletions
diff --git a/challenge-166/athanasius/perl/ch-1.pl b/challenge-166/athanasius/perl/ch-1.pl new file mode 100644 index 0000000000..15aab9670a --- /dev/null +++ b/challenge-166/athanasius/perl/ch-1.pl @@ -0,0 +1,259 @@ +#!perl + +############################################################################### +=comment + +Perl Weekly Challenge 166 +========================= + +TASK #1 +------- +*Hexadecimal Words* + +Submitted by: Ryan J Thompson + +As an old systems programmer, whenever I needed to come up with a 32-bit num- +ber, I would reach for the tired old examples like 0xDeadBeef and 0xC0dedBad. I +want more! + +Write a program that will read from a dictionary and find 2- to 8-letter words +that can be “spelled” in hexadecimal, _with_ the addition of the following +letter substitutions: + + • o ⟶ 0 (e.g., 0xf00d = “food”) + • l ⟶ 1 + • i ⟶ 1 + • s ⟶ 5 + • t ⟶ 7 + +You can use your own dictionary or you can simply open +../../../data/dictionary.txt (relative to your script’s location in our +[ https://github.com/manwar/perlweeklychallenge-club |GitHub repository]) to +access the dictionary of common words from +[ https://theweeklychallenge.org/blog/perl-weekly-challenge-161/ |Week #161]. + +Optional Extras (for an 0xAddedFee, of course!) + 1. Limit the number of “special” letter substitutions in any one result + to keep that result at least somewhat comprehensible. (0x51105010 is + an actual example from my sample solution you may wish to avoid!) + + 2. Find _phrases_ of words that total 8 characters in length (e.g., + 0xFee1Face), rather than just individual words. + +=cut +############################################################################### + +#--------------------------------------# +# Copyright © 2022 PerlMonk Athanasius # +#--------------------------------------# + +#============================================================================== +=comment + +Optional Extras +--------------- +1. Limit "special" letter substitutions. Command-line options: + + --single Prevent '1' from representing both 'i' and 'l' in the same word. + Applies before --max or --prop. + --max=i Limit the total number of digits in any word to the given integer + (which must be between 0 and 8 inclusive). Takes precedence over + the --prop flag. + --prop=f Limit the number of digits in any word to the given floating- + point number, which is the proportion of digits to total char- + acters. Must be between 0 and 1 inclusive. Has no effect if the + --max flag is also set. + +2. Find phrases. Command-line option: + + --phrases Only 8-character phrases are output: 8-character words, and + phrases composed of 2 shorter words. Where the phrase contains 2 + words, the shorter of the two appears first. Note that the + command-line flags described above apply to the selection of + individual words, before phrases are constructed. + +=cut +#============================================================================== + +use strict; +use warnings; +use Const::Fast; +use Getopt::Long; + +const my $DICTIONARY => '../../../data/dictionary.txt'; +const my $HEX_LETTERS => 'ABCDEF'; +const my %HEX_NUMBERS => ( I => '1', L => '1', O => '0', S => '5', T => '7' ); +const my $LETTERS => $HEX_LETTERS . join '', sort keys %HEX_NUMBERS; +const my $MIN_LETTERS => 2; +const my $MAX_LETTERS => 8; +const my $USAGE => +"Usage: + perl $0 [--max[=Int]] [--prop[=Real]] [--single] [--phrases] + + --max[=Int] Maximum digits in word + --prop[=Real] Maximum proportion of digits + --single Disallow '1' as both 'I' & 'L' [default: False] + --phrases Find 8-letter phrases [default: False]\n"; + +#------------------------------------------------------------------------------ +BEGIN +#------------------------------------------------------------------------------ +{ + $| = 1; + print "\nChallenge 166, Task #1: Hexadecimal Words (Perl)\n\n"; +} + +#============================================================================== +MAIN: +#============================================================================== +{ + my ($max_nums, $max_prop, $single_def_1, $phrases) = parse_command_line(); + + my $table_re = join '|', sort keys %HEX_NUMBERS; + my @words; + + open my $fh, '<', $DICTIONARY + or die qq[Cannot open file "$DICTIONARY" for reading, stopped]; + + while (my $line = <$fh>) + { + chomp $line; + my $word = ucfirst $line; + my $length = length $word; + + if ($MIN_LETTERS <= $length <= $MAX_LETTERS && + $word =~ / ^ [$LETTERS]+ $ /ix) + { + my $hex = test_word( $word, $table_re, $max_nums, $max_prop, + $single_def_1 ); + + push @words, [ $word, $hex ] if defined $hex; + } + } + + close $fh + or die qq[Cannot close file "$DICTIONARY", stopped]; + + display_words( $phrases ? find_phrases( \@words ) : \@words ); +} + +#------------------------------------------------------------------------------ +sub find_phrases +#------------------------------------------------------------------------------ +{ + my ($words) = @_; + my %len2words; + + for my $word (@$words) + { + push @{ $len2words{ length $word->[ 0 ] } }, $word; + } + + my @phrases = @{ $len2words{ $MAX_LETTERS } }; + + for my $i (2 .. int( $MAX_LETTERS / 2 )) + { + my $j = $MAX_LETTERS - $i; + + for my $word_i (@{ $len2words{ $i } }) + { + for my $word_j (@{ $len2words{ $j } }) + { + push @phrases, [ $word_i->[ 0 ] . $word_j->[ 0 ], + $word_i->[ 1 ] . $word_j->[ 1 ] ]; + } + } + } + + return \@phrases; +} + +#------------------------------------------------------------------------------ +sub test_word +#------------------------------------------------------------------------------ +{ + my ($word, $table_re, $max_nums, $max_prop, $single_def_1) = @_; + + if ($single_def_1) + { + return if $word =~ / I /ix && $word =~ / L /ix; + } + + my $hex = $word =~ s/ ($table_re) /$HEX_NUMBERS{ uc $1 }/girx; + my $from = uc( $HEX_LETTERS ) . lc( $HEX_LETTERS ); + my $nums = length eval "\$hex =~ tr/$from//dr"; + + return if (defined $max_nums && $nums > $max_nums) || + (defined $max_prop && $nums / length( $hex ) > $max_prop); + + my $ch0 = substr $hex, 0, 1; + + substr $hex, 0, 1, uc( $ch0 ) if $ch0 =~ / [$HEX_LETTERS] /ix; + + return $hex; +} + +#------------------------------------------------------------------------------ +sub display_words +#------------------------------------------------------------------------------ +{ + my ($words) = @_; + + print " Word Hex\n"; + print " --------------------\n"; + + for my $word (@$words) + { + printf " %-8s 0x%-10s\n", $word->[ 0 ], $word->[ 1 ]; + } + + print " --------------------\n"; + printf " Count: %d\n", scalar @$words; +} + +#------------------------------------------------------------------------------ +sub parse_command_line +#------------------------------------------------------------------------------ +{ + my ($max_numbers, $max_proportion); + my $single_def_1 = 0; + my $find_phrases = 0; + + GetOptions + ( + 'max=i' => \$max_numbers, + 'prop=f' => \$max_proportion, + 'single' => \$single_def_1, + 'phrases' => \$find_phrases, + + ) or error( 'Error in command line arguments' ); + + my $args = scalar @ARGV; + $args == 0 + or error( "Expected 0 command line arguments, found $args" ); + + if (defined $max_numbers) + { + 0 <= $max_numbers <= $MAX_LETTERS + or error( qq[Invalid value "$max_numbers" for max] ); + } + + if (defined $max_proportion) + { + 0 <= $max_proportion <= 1 + or error( qq[Invalid value "$max_proportion" for prop] ); + } + + return ($max_numbers, $max_proportion, $single_def_1, $find_phrases); +} + +#------------------------------------------------------------------------------ +sub error +#------------------------------------------------------------------------------ +{ + my ($message) = @_; + + die "ERROR: $message\n$USAGE"; +} + +############################################################################### diff --git a/challenge-166/athanasius/perl/ch-2.pl b/challenge-166/athanasius/perl/ch-2.pl new file mode 100644 index 0000000000..160a945a04 --- /dev/null +++ b/challenge-166/athanasius/perl/ch-2.pl @@ -0,0 +1,280 @@ +#!perl + +############################################################################### +=comment + +Perl Weekly Challenge 166 +========================= + +TASK #2 +------- +*K-Directory Diff* + +Submitted by: Ryan J Thompson + +Given a few (three or more) directories (non-recursively), display a side-by- +side difference of files that are missing from at least one of the directories. +Do not display files that exist in every directory. + +Since the task is non-recursive, if you encounter a subdirectory, append a /, +but otherwise treat it the same as a regular file. + +Example + +Given the following directory structure: + + dir_a: + Arial.ttf Comic_Sans.ttf Georgia.ttf Helvetica.ttf Impact.otf + Verdana.ttf Old_Fonts/ + + dir_b: + Arial.ttf Comic_Sans.ttf Courier_New.ttf Helvetica.ttf Impact.otf + Tahoma.ttf Verdana.ttf + + dir_c: + Arial.ttf Courier_New.ttf Helvetica.ttf Impact.otf Monaco.ttf + Verdana.ttf + +The output should look similar to the following: + + dir_a | dir_b | dir_c + -------------- | --------------- | --------------- + Comic_Sans.ttf | Comic_Sans.ttf | + | Courier_New.ttf | Courier_New.ttf + Georgia.ttf | | + | | Monaco.ttf + Old_Fonts/ | | + | Tahoma.ttf | + +=cut +############################################################################### + +#--------------------------------------# +# Copyright © 2022 PerlMonk Athanasius # +#--------------------------------------# + +#============================================================================== +=comment + +Note +---- +This script receives the names of 3 or more actual directories as input, and +prepares the output table according to the files which those directories +contain. However, no attempt is made to verify that files sharing the same name +are in fact identical. + +=cut +#============================================================================== + +use strict; +use warnings; +use Const::Fast; +use List::Util qw( first ); + +const my $USAGE => +"Usage: + perl $0 [<dirs> ...] + + [<dirs> ...] 3 or more directory names\n"; + +#------------------------------------------------------------------------------ +BEGIN +#------------------------------------------------------------------------------ +{ + $| = 1; + print "\nChallenge 166, Task #2: K-Directory Diff (Perl)\n\n"; +} + +#============================================================================== +MAIN: +#============================================================================== +{ + my @dirs = parse_command_line(); + my $dir2files = map_dir_to_files( \@dirs ); + my %file2dirs; + + for my $dir (keys %$dir2files) + { + for my $file (@{ $dir2files->{ $dir } }) + { + push @{ $file2dirs{ $file } }, $dir; + } + } + + my $dir_count = scalar @dirs; + + for my $filename (keys %file2dirs) + { + if (scalar @{ $file2dirs{ $filename } } == $dir_count) + { + delete $file2dirs{ $filename }; + } + } + + display_table( \@dirs, \%file2dirs ); +} + +#------------------------------------------------------------------------------ +sub map_dir_to_files +#------------------------------------------------------------------------------ +{ + my ($dirs) = @_; + my %dir2files; + + for my $dir (@$dirs) + { + if (-d $dir) + { + opendir my $dh, $dir + or die qq[Cannot opendir "$dir"\n]; + + while (my $file = readdir $dh) + { + next if $file =~ / ^ \. /x; + + my $filename = $file; + $filename .= '/' if -d "$dir/$file"; + + push @{ $dir2files{ $dir } }, $filename; + } + + closedir $dh + or die qq[Cannot closedir "$dir"\n]; + } + else + { + error( qq[Directory "$dir" not found] ); + } + } + + return \%dir2files; +} + +#------------------------------------------------------------------------------ +sub display_table +#------------------------------------------------------------------------------ +{ + my ($dirs, $file2dirs) = @_; + + my $dir2width = get_max_widths( $dirs, $file2dirs ); + + display_header( $dirs, $dir2width ); + + for my $file (sort keys %$file2dirs) + { + display_line( $file, $dirs, $file2dirs, $dir2width ); + } +} + +#------------------------------------------------------------------------------ +sub get_max_widths +#------------------------------------------------------------------------------ +{ + my ($dirs, $file2dirs) = @_; + + my %dir2width = map { $_ => 0 } @$dirs; + + for my $filename (keys %$file2dirs) + { + for my $dir (@{ $file2dirs->{ $filename } }) + { + if (length $filename > $dir2width{ $dir }) + { + $dir2width{ $dir } = length $filename; + } + } + } + + return \%dir2width; +} + +#------------------------------------------------------------------------------ +sub display_header +#------------------------------------------------------------------------------ +{ + my ($dirs, $dir2width) = @_; + + my $dir0 = $dirs->[ 0 ]; + + printf ' %-*s', $dir2width->{ $dir0 }, $dir0; + + for my $dir (@$dirs[ 1 .. $#$dirs ]) + { + printf ' | %-*s', $dir2width->{ $dir }, $dir; + } + + printf "\n %s", '-' x $dir2width->{ $dir0 }; + + for my $dir (@$dirs[ 1 .. $#$dirs ]) + { + printf ' | %s', '-' x $dir2width->{ $dir }; + } + + print "\n"; +} + +#------------------------------------------------------------------------------ +sub display_line +#------------------------------------------------------------------------------ +{ + my ($file, $dirs, $file2dirs, $dir2width) = @_; + + my $dir0 = $dirs->[ 0 ]; + + if (first { $_ eq $dir0 } @{ $file2dirs->{ $file } }) + { + printf ' %-*s', $dir2width->{ $dir0 }, $file; + } + else + { + printf ' %s', ' ' x $dir2width->{ $dir0 }; + } + + for my $dir (@$dirs[ 1 .. $#$dirs ]) + { + print ' | '; + + if (first { $_ eq $dir } @{ $file2dirs->{ $file } }) + { + printf '%-*s', $dir2width->{ $dir }, $file; + } + else + { + printf '%s', ' ' x $dir2width->{ $dir }; + } + } + + print "\n"; +} + +#------------------------------------------------------------------------------ +sub parse_command_line +#------------------------------------------------------------------------------ +{ + my $args = scalar @ARGV; + $args >= 3 + or error( 'Expected at least 3 command line arguments, found ' . + $args ); + + my %dirs; + ++$dirs{ $_ } for @ARGV; + + my @dirs = sort keys %dirs; + + scalar @dirs >= 3 + or error( 'Input contains only ' . scalar( @dirs ) . ' distinct ' . + 'directories, must have at least 3' ); + + return @dirs; +} + +#------------------------------------------------------------------------------ +sub error +#------------------------------------------------------------------------------ +{ + my ($message) = @_; + + die "ERROR: $message\n$USAGE"; +} + +############################################################################### diff --git a/challenge-166/athanasius/raku/ch-1.raku b/challenge-166/athanasius/raku/ch-1.raku new file mode 100644 index 0000000000..648c71bf7a --- /dev/null +++ b/challenge-166/athanasius/raku/ch-1.raku @@ -0,0 +1,268 @@ +use v6d; + +############################################################################### +=begin comment + +Perl Weekly Challenge 166 +========================= + +TASK #1 +------- +*Hexadecimal Words* + +Submitted by: Ryan J Thompson + +As an old systems programmer, whenever I needed to come up with a 32-bit num- +ber, I would reach for the tired old examples like 0xDeadBeef and 0xC0dedBad. I +want more! + +Write a program that will read from a dictionary and find 2- to 8-letter words +that can be “spelled” in hexadecimal, _with_ the addition of the following +letter substitutions: + + • o ⟶ 0 (e.g., 0xf00d = “food”) + • l ⟶ 1 + • i ⟶ 1 + • s ⟶ 5 + • t ⟶ 7 + +You can use your own dictionary or you can simply open +../../../data/dictionary.txt (relative to your script’s location in our +[ https://github.com/manwar/perlweeklychallenge-club |GitHub repository]) to +access the dictionary of common words from +[ https://theweeklychallenge.org/blog/perl-weekly-challenge-161/ |Week #161]. + +Optional Extras (for an 0xAddedFee, of course!) + 1. Limit the number of “special” letter substitutions in any one result + to keep that result at least somewhat comprehensible. (0x51105010 is + an actual example from my sample solution you may wish to avoid!) + + 2. Find _phrases_ of words that total 8 characters in length (e.g., + 0xFee1Face), rather than just individual words. + +=end comment +############################################################################### + +#--------------------------------------# +# Copyright © 2022 PerlMonk Athanasius # +#--------------------------------------# + +#============================================================================== +=begin comment + +Optional Extras +--------------- +1. Limit "special" letter substitutions. Command-line options: + + --single Prevent '1' from representing both 'i' and 'l' in the same word. + Applies before --max or --prop. + --max=i Limit the total number of digits in any word to the given integer + (which must be between 0 and 8 inclusive). Takes precedence over + the --prop flag. + --prop=f Limit the number of digits in any word to the given floating- + point number, which is the proportion of digits to total char- + acters. Must be between 0 and 1 inclusive. Has no effect if the + --max flag is also set. + +2. Find phrases. Command-line option: + + --phrases Only 8-character phrases are output: 8-character words, and + phrases composed of 2 shorter words. Where the phrase contains 2 + words, the shorter of the two appears first. Note that the + command-line flags described above apply to the selection of + individual words, before phrases are constructed. +=end comment +#============================================================================== + +my Str constant $DICTIONARY = '../../../data/dictionary.txt'; +my Set[Str] constant $HEX-LETTERS = Set[Str].new: 'a' .. 'f'; +my constant %HEX-NUMBERS = :i('1'), :l('1'), :o('0'), :s('5'), + :t('7'); +my Set[Str] constant $LETTERS = $HEX-LETTERS ∪ + Set[Str].new: %HEX-NUMBERS.keys; +my UInt constant $MIN-LETTERS = 2; +my UInt constant $MAX-LETTERS = 8; + +#------------------------------------------------------------------------------ +BEGIN +#------------------------------------------------------------------------------ +{ + "\nChallenge 166, Task #1: Hexadecimal Words (Raku)\n".put; +} + +#============================================================================== +sub MAIN +( + Int:_ :max(:$max-numbers), #= Maximum digits in word + Real:_ :prop(:$max-proportion), #= Maximum proportion of digits + Bool:D :single(:$single-def1) = False, #= Disallow '1' as both 'I' & 'L' + Bool:D :phrases(:$find-phrases) = False, #= Find 8-letter phrases +) +#============================================================================== +{ + validate-arguments( $max-numbers, $max-proportion ); + + my Array[Str] @words; + + for $DICTIONARY.IO.lines -> Str $line + { + my Str $word = $line.chomp.tc; + my UInt $length = $word.chars; + + if $MIN-LETTERS <= $length <= $MAX-LETTERS + { + my Set[Str] $chars = Set[Str].new: + $word.split( '', :skip-empty ).map: { .lc }; + + if $chars ⊆ $LETTERS + { + my Str $hex = test-word( $word, $max-numbers, + $max-proportion, $single-def1 ); + + @words.push: Array[Str].new( $word, $hex ) if $hex.defined; + } + } + } + + display-words( $find-phrases ?? find-phrases( @words ) !! @words ); +} + +#------------------------------------------------------------------------------ +sub find-phrases +( + Array:D[Array:D[Str:D]] $words +--> Array:D[Array:D[Str:D]] +) +#------------------------------------------------------------------------------ +{ + my Array[Array[Str]] %len2words; + + for @$words -> Array[Str] $word + { + %len2words{ $word[ 0 ].chars }.push: $word; + } + + my Array[Str] @phrases = |%len2words{ $MAX-LETTERS }; + + for 1 .. ($MAX-LETTERS / 2).floor -> UInt $i + { + next if $i == 1; + + my UInt $j = $MAX-LETTERS - $i; + + for %len2words{ $i }.list -> Array[Str] $word-i + { + for %len2words{ $j }.list -> Array[Str] $word-j + { + @phrases.push: Array[Str].new: $word-i[ 0 ] ~ $word-j[ 0 ], + $word-i[ 1 ] ~ $word-j[ 1 ]; + } + } + } + + return @phrases; +} + +#------------------------------------------------------------------------------ +sub test-word +( + Str:D $word, + UInt $max-numbers, + Real $max-proportion, + Bool:D $single-def1 +--> Str +) +#------------------------------------------------------------------------------ +{ + return if $single-def1 && $word ~~ m:i/ I / && $word ~~ m:i/ L /; + + my Str @chars = $word.split: '', :skip-empty; + my Str $hex; + + for @chars -> Str $char + { + if %HEX-NUMBERS{ $char.lc }:exists + { + $hex ~= %HEX-NUMBERS{ $char.lc }; + } + else + { + $hex ~= $char; + } + } + + $hex ~~ m:g/ \d /; + my UInt $nums = $/.elems; + + return if $max-numbers.defined && $nums > $max-numbers; + return if $max-proportion.defined && $nums / $hex.chars > $max-proportion; + + return $hex; +} + +#------------------------------------------------------------------------------ +sub display-words +( + Array:D[Array:D[Str:D]] $words +) +#------------------------------------------------------------------------------ +{ + ' Word Hex'.put; + ' --------------------'.put; + + for @$words -> Array[Str] $word + { + " %-8s 0x%-10s\n".printf: $word[ 0 ], $word[ 1 ]; + } + + ' --------------------'.put; + " Count: %d\n".printf: +@$words; +} + +#------------------------------------------------------------------------------ +sub validate-arguments +( + UInt $max-numbers, + Real $max-proportion +) +#------------------------------------------------------------------------------ +{ + if $max-numbers.defined + { + 0 <= $max-numbers <= $MAX-LETTERS + or error( qq[Invalid value "$max-numbers" for max] ); + } + + if $max-proportion.defined + { + 0 <= $max-proportion <= 1 + or error( qq[Invalid value "$max-proportion" for prop] ); + } +} + +#------------------------------------------------------------------------------ +sub error +( + Str:D $message +) +#------------------------------------------------------------------------------ +{ + "ERROR: $message".put; + + USAGE(); + + exit 0; +} + +#------------------------------------------------------------------------------ +sub USAGE() +#------------------------------------------------------------------------------ +{ + my Str $usage = $*USAGE; + + $usage ~~ s/ ($*PROGRAM-NAME) /raku $0/; + + $usage.put; +} + +############################################################################### diff --git a/challenge-166/athanasius/raku/ch-2.raku b/challenge-166/athanasius/raku/ch-2.raku new file mode 100644 index 0000000000..5f78efdd2d --- /dev/null +++ b/challenge-166/athanasius/raku/ch-2.raku @@ -0,0 +1,277 @@ +use v6d; + +############################################################################### +=begin comment + +Perl Weekly Challenge 166 +========================= + +TASK #2 +------- +*K-Directory Diff* + +Submitted by: Ryan J Thompson + +Given a few (three or more) directories (non-recursively), display a side-by- +side difference of files that are missing from at least one of the directories. +Do not display files that exist in every directory. + +Since the task is non-recursive, if you encounter a subdirectory, append a /, +but otherwise treat it the same as a regular file. + +Example + +Given the following directory structure: + + dir_a: + Arial.ttf Comic_Sans.ttf Georgia.ttf Helvetica.ttf Impact.otf + Verdana.ttf Old_Fonts/ + + dir_b: + Arial.ttf Comic_Sans.ttf Courier_New.ttf Helvetica.ttf Impact.otf + Tahoma.ttf Verdana.ttf + + dir_c: + Arial.ttf Courier_New.ttf Helvetica.ttf Impact.otf Monaco.ttf + Verdana.ttf + +The output should look similar to the following: + + dir_a | dir_b | dir_c + -------------- | --------------- | --------------- + Comic_Sans.ttf | Comic_Sans.ttf | + | Courier_New.ttf | Courier_New.ttf + Georgia.ttf | | + | | Monaco.ttf + Old_Fonts/ | | + | Tahoma.ttf | + +=end comment +############################################################################### + +#--------------------------------------# +# Copyright © 2022 PerlMonk Athanasius # +#--------------------------------------# + +#============================================================================== +=begin comment + +Note +---- +This script receives the names of 3 or more actual directories as input, and +prepares the output table according to the files which those directories +contain. However, no attempt is made to verify that files sharing the same name +are in fact identical. + +=end comment +#============================================================================== + +#------------------------------------------------------------------------------ +BEGIN +#------------------------------------------------------------------------------ +{ + "\nChallenge 166, Task #2: K-Directory Diff (Raku)\n".put; +} + +#============================================================================== +sub MAIN +( + *@dirs where { .all ~~ Str:D && +@dirs >= 3 } #= 3 or more directory names +) +#============================================================================== +{ + my Set[Str] $dirs-set = Set[Str].new: |@dirs; + my Str @dirs-srt = $dirs-set.keys.sort; + + +@dirs-srt >= 3 + or error( "Input contains only { +@dirs-srt } distinct directories, " ~ + "must have at least 3" ); + + my Array[Str] %dir2files = map-dir-to-files( @dirs-srt ); + my Array[Str] %file2dirs; + + for %dir2files.keys -> Str $dir + { + for %dir2files{ $dir }.list -> Str $file + { + %file2dirs{ $file }.push: $dir; + } + } + + my UInt $dir-count = +@dirs-srt; + + for %file2dirs.keys -> Str $filename + { + if %file2dirs{ $filename }.list.elems == $dir-count + { + %file2dirs{ $filename }:delete; + } + } + + display-table( @dirs-srt, %file2dirs ); +} + +#------------------------------------------------------------------------------ +sub map-dir-to-files +( + Array:D[Str:D] $dirs +--> Hash:D[Array:D[Str:D]] +) +#------------------------------------------------------------------------------ +{ + my Array[Str] %dir2files; + + for @$dirs -> Str $dir + { + if $dir.IO.d + { + for $dir.IO.dir -> IO::Path $file + { + my Str $filename = $file.basename; + + $filename ~= '/' if $file.d; + + %dir2files{ $dir }.push: $filename; + } + } + else + { + error( qq[Directory "$dir" not found] ); + } + } + + return %dir2files; +} + +#------------------------------------------------------------------------------ +sub display-table +( + Array:D[Str:D] $dirs, + Hash:D[Array:D[Str:D]] $file2dirs +) +#------------------------------------------------------------------------------ +{ + my UInt %dir2width = get-max-widths( $dirs, $file2dirs ); + + display-header( $dirs, %dir2width ); + + for $file2dirs.keys.sort -> Str $file + { + display-line( $file, $dirs, $file2dirs, %dir2width ); + } +} + +#------------------------------------------------------------------------------ +sub get-max-widths +( + Array:D[Str:D] $dirs, + Hash:D[Array:D[Str:D]] $file2dirs +--> Hash:D[UInt:D] +) +#------------------------------------------------------------------------------ +{ + my UInt %dir2width = $dirs.map: { $_ => 0 }; + + for $file2dirs.keys -> Str $filename + { + for $file2dirs{ $filename }.list -> Str $dir + { + if $filename.chars > %dir2width{ $dir } + { + %dir2width{ $dir } = $filename.chars; + } + } + } + + return %dir2width; +} + +#------------------------------------------------------------------------------ +sub display-header +( + Array:D[Str:D] $dirs, + Hash:D[UInt:D] $dir2width +) +#------------------------------------------------------------------------------ +{ + my Str $dir0 = $dirs[ 0 ]; + + ' %-*s'.printf: $dir2width{ $dir0 }, $dir0; + + for $dirs[ 1 .. * ] -> Str $dir + { + ' | %-*s'.printf: $dir2width{ $dir }, $dir; + } + + "\n %s".printf: '-' x $dir2width{ $dir0 }; + + for $dirs[ 1 .. * ] -> Str $dir + { + ' | %s'.printf: '-' x $dir2width{ $dir }; + } + + put(); +} + +#------------------------------------------------------------------------------ +sub display-line +( + Str:D $file, + Array:D[Str:D] $dirs, + Hash:D[Array:D[Str:D]] $file2dirs, + Hash:D[UInt:D] $dir2width +) +#------------------------------------------------------------------------------ +{ + my Str $dir0 = $dirs[ 0 ]; + + if $file2dirs{ $file }.first: $dir0 + { + ' %-*s'.printf: $dir2width{ $dir0 }, $file; + } + else + { + ' %s'.printf: ' ' x $dir2width{ $dir0 }; + } + + for $dirs[ 1 .. * ] -> Str $dir + { + if $file2dirs{ $file }.first: $dir + { + ' | %-*s'.printf: $dir2width{ $dir }, $file; + } + else + { + ' | %s'.printf: ' ' x $dir2width{ $dir }; + } + } + + put(); +} + +#------------------------------------------------------------------------------ +sub error +( + Str:D $message +) +#------------------------------------------------------------------------------ +{ + "ERROR |
