The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.
package Word::Anagram;

use 5.012000;
use strict;
use warnings;
use open IN => ":encoding(utf8)", OUT => ":utf8";

our $VERSION = '0.01';
my @anagram = ();

sub new {
	my $class = shift;
	my $self->{'word'} = shift || '';
	return bless $self, $class;
}

sub select_anagrams {
	my $self = shift;
	my @langs = @{ shift() };
	
	my @out = ();
	my %langs = ();
	@langs{0..$#langs} = @langs;
	
	for my $i (0..$#langs) {
		for my $j (0..$#langs) {
			next if $i == $j;
			my $is = $self->are_anagrams($langs{$i}, $langs{$j});
			if ($is) {
				push @out, $langs{$i};
				last;
			}
		}
	}
	return \@out;
}

sub are_anagrams {
	my $self = shift;
	my ($in1, $in2) = @_;
	
	return 0 if length($in1) != length($in2); 
	
	# compared as UTF-8 and case-insensitive
	$in1 = pack('U*', sort unpack('U*', uc $in1));
	$in2 = pack('U*', sort unpack('U*', uc $in2));
	
	return $in1 eq $in2 ? 1 : 0;
	
}

sub get_anagrams_of {
	my $self = shift;
	my $word = shift;
	@anagram = ();
	anagrams('',split('',$word));
	my %anagram;
	@anagram{@anagram} = @anagram;
	@anagram = sort keys %anagram;
	return \@anagram;
}

sub find_word_in {
	my $self = shift;
	my $word = shift;
	my @langs = @{ shift() };
	
	my @out = ();
	my %langs = ();
	@langs{0..$#langs} = @langs;
	
	for my $i (0..$#langs) {
		my $is = $self->are_anagrams($word, $langs{$i});
		push @out, $langs{$i} if $is;
	}
	return \@out;
	
}

# Private sub
sub anagrams {
	my ($p,@s) = @_;
	if (@s) {
		my $r = @s;
		while ($r--) {
			my $c = shift @s;
			anagrams($p.$c,@s);
			push @s,$c
		}
	} else {
		push @anagram, $p;
	}
}


1;

__END__

=pod

=head1 NAME

Word::Anagram - Perl extension for find anagrams of a word

=head1 SYNOPSIS

  use Word::Anagram;
  my $obj = Word::Anagram->new;

=head1 DESCRIPTION

A class to search anagrams of a word in different contexts.
E.g. find the anagrams of a word inside a list of words (dictionary).
Or find if two words are anagrams. Or, to filter all anagrams inside
a list of words that are anagrams of each other. 

Be aware that inside is used a recursive sub and this require a lot of
memory during the increase of the word's lenght, while are find all 
anagrams of a given word. 

All search are case-insensitive.
	
=head1 METHODS

=over

=item are_anagram()

	# check if two words, $word1 $word2, are anagram
	my $obj = Word::Anagram->new;
	my ($word1, $word2) = qw( donna danno );
	my $bool = $obj->are_anagrams($word1, $word2); # return true/false
	
=item select_anagrams()

	# select all words inside @words that are anagrams
	my $obj = Word::Anagram->new;
	my @words = qw(Donna  Danno Pluto Toplu Paperino Minni);
	my $anag = $obj->select_anagrams(\@words); # return an array ref
	
=item get_anagrams_of()

	# find all anagrams of $word
	my $obj = Word::Anagram->new;
	my $word = 'ABC';
	my $anag = $obj->get_anagrams_of($word); # return an array ref

=item find_word_in()

	# find all anagrams of $word inside @words
	my $obj = Word::Anagram->new;
	my $word = 'nonda';
	my @words = qw(donna  danno pluto toplu paperino minni des nodan );
	my $anag = $obj->find_word_in($word, \@words); # return an array ref

=back
	
=head1 AUTHOR

Leo Manfredi, <manfredi@cpan.org>

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2012 by Leo Manfredi

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself, either Perl version 5.12.0 or,
at your option, any later version of Perl 5 you may have available.

=cut