diff --git a/exercises/practice/anagram/.meta/exercise-data.yaml b/exercises/practice/anagram/.meta/exercise-data.yaml new file mode 100644 index 00000000..30465ce9 --- /dev/null +++ b/exercises/practice/anagram/.meta/exercise-data.yaml @@ -0,0 +1,26 @@ +exercise: Anagram +plan: 15 +subs: match_anagrams +tests: |- + for my $case (@test_cases) { + is match_anagrams($case->{input}), $case->{expected}, $case->{description}; + } + +example: |- + sub match_anagrams { + my ($input) = @_; + + return [ + grep { + lc $_ ne lc $input->{subject} + && join( '', sort( split( //, lc $_ ) ) ) eq + join( '', sort( split( //, lc $input->{subject} ) ) ) + } @{ $input->{candidates} } + ]; + } + +stub: |- + sub match_anagrams { + my ($input) = @_; + return undef; + } diff --git a/exercises/practice/anagram/.meta/solutions/Anagram.pm b/exercises/practice/anagram/.meta/solutions/Anagram.pm index d61d86f0..aa93b22f 100644 --- a/exercises/practice/anagram/.meta/solutions/Anagram.pm +++ b/exercises/practice/anagram/.meta/solutions/Anagram.pm @@ -1,25 +1,19 @@ package Anagram; use strict; use warnings; +use Exporter qw; +our @EXPORT_OK = qw; -sub match { - my ( $word, @words ) = @_; +sub match_anagrams { + my ($input) = @_; - my @results; - my $canonical = _canonize($word); - foreach my $w (@words) { - next if $w eq $word; - my $try = _canonize($w); - if ( $try eq $canonical ) { - push @results, $w; - } - } - return \@results; -} - -sub _canonize { - my ($str) = @_; - return join '', sort split //, lc $str; + return [ + grep { + lc $_ ne lc $input->{subject} + && join( '', sort( split( //, lc $_ ) ) ) eq + join( '', sort( split( //, lc $input->{subject} ) ) ) + } @{ $input->{candidates} } + ]; } 1; diff --git a/exercises/practice/anagram/Anagram.pm b/exercises/practice/anagram/Anagram.pm new file mode 100644 index 00000000..ad9c4a27 --- /dev/null +++ b/exercises/practice/anagram/Anagram.pm @@ -0,0 +1,12 @@ +package Anagram; +use strict; +use warnings; +use Exporter qw; +our @EXPORT_OK = qw; + +sub match_anagrams { + my ($input) = @_; + return undef; +} + +1; diff --git a/exercises/practice/anagram/anagram.t b/exercises/practice/anagram/anagram.t old mode 100644 new mode 100755 index fddf0545..e2813f5e --- a/exercises/practice/anagram/anagram.t +++ b/exercises/practice/anagram/anagram.t @@ -1,98 +1,222 @@ #!/usr/bin/env perl -use strict; -use warnings; +use Test2::V0; +use JSON::PP; +use constant JSON => JSON::PP->new; -use Test2::Bundle::More; -use JSON::PP qw(decode_json); -use FindBin qw($Bin); +use FindBin qw<$Bin>; use lib $Bin, "$Bin/local/lib/perl5"; -my $module = 'Anagram'; +use Anagram qw; -my $cases; -{ - local $/ = undef; - $cases = decode_json scalar ; -} - -plan 3 + @$cases; - -#diag explain $cases; - -ok -e "$Bin/$module.pm", "missing $module.pm" - or BAIL_OUT( - "You need to create a class called $module.pm with an function called match() that gets the original word as the first parameter and a reference to a list of word to check. It should return a referene to a list of words." - ); +my @test_cases = do { local $/; @{ JSON->decode() }; }; +plan 15; -eval "use $module"; -ok !$@, "Cannot load $module.pm" - or BAIL_OUT("Does $module.pm compile? Does it end with 1; ?"); +imported_ok qw or bail_out; -can_ok( $module, 'match' ) - or BAIL_OUT("Missing package $module; or missing sub match()"); - -my $sub = $module . '::match'; - -foreach my $c (@$cases) { - no strict 'refs'; - is_deeply $sub->( $c->{word}, @{ $c->{words} } ), $c->{expected}, - $c->{name}; +for my $case (@test_cases) { + is match_anagrams( $case->{input} ), $case->{expected}, + $case->{description}; } __DATA__ [ { - "word" : "diaper", - "words" : ["hello", "world", "zombies", "pants"], - "expected" : [], - "name" : "no matches" + "description": "no matches", + "expected": [], + "input": { + "candidates": [ + "hello", + "world", + "zombies", + "pants" + ], + "subject": "diaper" + }, + "property": "findAnagrams" + }, + { + "description": "detects two anagrams", + "expected": [ + "stream", + "maters" + ], + "input": { + "candidates": [ + "stream", + "pigeon", + "maters" + ], + "subject": "master" + }, + "property": "findAnagrams" + }, + { + "description": "does not detect anagram subsets", + "expected": [], + "input": { + "candidates": [ + "dog", + "goody" + ], + "subject": "good" + }, + "property": "findAnagrams" + }, + { + "description": "detects anagram", + "expected": [ + "inlets" + ], + "input": { + "candidates": [ + "enlists", + "google", + "inlets", + "banana" + ], + "subject": "listen" + }, + "property": "findAnagrams" + }, + { + "description": "detects three anagrams", + "expected": [ + "gallery", + "regally", + "largely" + ], + "input": { + "candidates": [ + "gallery", + "ballerina", + "regally", + "clergy", + "largely", + "leading" + ], + "subject": "allergy" + }, + "property": "findAnagrams" + }, + { + "description": "detects multiple anagrams with different case", + "expected": [ + "Eons", + "ONES" + ], + "input": { + "candidates": [ + "Eons", + "ONES" + ], + "subject": "nose" + }, + "property": "findAnagrams" }, { - "word" : "ant", - "words" : ["tan", "stand", "at"], - "expected" : ["tan"], - "name" : "detect_simple_anagram" + "description": "does not detect non-anagrams with identical checksum", + "expected": [], + "input": { + "candidates": [ + "last" + ], + "subject": "mass" + }, + "property": "findAnagrams" }, { - "word" : "master", - "words" : ["stream", "pigeon", "maters"], - "expected" : ["stream", "maters"], - "name" : "multiple_anagrams" + "description": "detects anagrams case-insensitively", + "expected": [ + "Carthorse" + ], + "input": { + "candidates": [ + "cashregister", + "Carthorse", + "radishes" + ], + "subject": "Orchestra" + }, + "property": "findAnagrams" }, { - "word" : "galea", - "words" : ["eagle"], - "expected" : [], - "name" : "does_not_confuse_different_duplicates" + "description": "detects anagrams using case-insensitive subject", + "expected": [ + "carthorse" + ], + "input": { + "candidates": [ + "cashregister", + "carthorse", + "radishes" + ], + "subject": "Orchestra" + }, + "property": "findAnagrams" }, { - "word" : "good", - "words" : ["dog", "goody"], - "expected" : [], - "name" : "eliminate_anagram_subsets" + "description": "detects anagrams using case-insensitive possible matches", + "expected": [ + "Carthorse" + ], + "input": { + "candidates": [ + "cashregister", + "Carthorse", + "radishes" + ], + "subject": "orchestra" + }, + "property": "findAnagrams" }, { - "word" : "listen", - "words" : ["enlists", "google", "inlets", "banana"], - "expected" : ["inlets"], - "name" : "detect_anagram" + "description": "does not detect an anagram if the original word is repeated", + "expected": [], + "input": { + "candidates": [ + "go Go GO" + ], + "subject": "go" + }, + "property": "findAnagrams" }, { - "word" : "allergy", - "words" : ["gallery", "ballerina", "regally", "clergy", "largely", "leading"], - "expected" : ["gallery", "regally", "largely"], - "name" : "multiple_anagrams" + "description": "anagrams must use all letters exactly once", + "expected": [], + "input": { + "candidates": [ + "patter" + ], + "subject": "tapper" + }, + "property": "findAnagrams" }, { - "word" : "Orchestra", - "words" : ["cashregister", "Carthorse", "radishes"], - "expected" : ["Carthorse"], - "name" : "anagrams_are_case_insensitive" + "description": "words are not anagrams of themselves (case-insensitive)", + "expected": [], + "input": { + "candidates": [ + "BANANA", + "Banana", + "banana" + ], + "subject": "BANANA" + }, + "property": "findAnagrams" }, { - "word" : "banana", - "words" : ["banana"], - "expected" : [], - "name" : "same_word_isnt_anagram" + "description": "words other than themselves can be anagrams", + "expected": [ + "Silent" + ], + "input": { + "candidates": [ + "Listen", + "Silent", + "LISTEN" + ], + "subject": "LISTEN" + }, + "property": "findAnagrams" } ]