sub word_to_struct { my ( $word ) = @_; my %out; $out{$_}++ for split //, $word; return \%out; } sub read_dict { my ( $dict_file ) = @_; my %out; $dict_file ||= '/usr/share/dict/words'; open my $dict_fh, '<', $dict_file or die "Can't read '$dict_file': $!\n"; while ( my $word = <$dict_fh> ) { chomp $word; if ( $word =~ m{ \A [a-z]+ \z }xmsi ) { $out{$word} = word_to_struct($word); } } close $dict_fh or die "close failed: $!\n"; return \%out; } #### ... 'expletive' => { 'l' => 1, 'e' => 3, 'p' => 1, 'v' => 1, 'x' => 1, 'i' => 1, 't' => 1 }, 'measles' => { 'l' => 1, 'e' => 2, 'a' => 1, 'm' => 1, 's' => 2 }, ... #### sub possible { my ( $tiles_ref, $dict_ref ) = @_; my @out; my %tile_count; $tile_count{$_}++ for @{$tiles_ref}; my $longest_word = scalar @{$tiles_ref}; WORD: foreach my $word ( keys %{$dict_ref} ) { next WORD if length $word > $longest_word; foreach my $letter ( keys %{$dict_ref->{$word}} ) { next WORD if ! defined $tile_count{$letter}; next WORD if $dict_ref->{$word}{$letter} > $tile_count{$letter}; } push @out, $word; } return @out; } # example: use Data::Dumper; print Dumper( possible( [ qw( a b c d e ) ], read_dict() ) );