2

I have an array like this (it's just a little overview but it has 2000 and more lines like this):

@list = (
        "affaire,chose,question",
        "cause,chose,matière",
);

I'd like to have this output:

%te = (
affaire => "chose", "question",
chose => "affaire", "question", "cause", "matière", 
question => "affaire", "chose",
cause => "chose", "matière",
matière => "cause", "chose"
);

I've created this script but it doesn't work very well and I think is too much complicated..

use Data::Dumper;
@list = (
        "affaire,chose,question",
        "cause,chose,matière",
);

%te;

for ($a = 0; $a < @list; $a++){
    @split_list = split (/,/,$list[$a]);
}

foreach $elt (@split_list){
print "SPLIT ELT : $split_list[$elt]\n";

for ($i = 0; $i < @list; $i++){

    $test = $list[$i]; #$test = "affaire,chose,question"

    if (exists $te{$split_list[$elt]}){ #if exists affaire in %te

        @t = split (/,/,$test); # @t = affaire chose question
        print "T : @t\n";

        @temp = grep(!/$split_list[$elt]/, @t); 
        print "GREP : @temp\n";#@temp = chose question

        @fin = join(', ', @temp); #@fin = chose, question;

        for ($k = 0; $k < @fin; $k++){
            $te{$split_list[$elt]} .= $fin[$k]; #affaire => chose, question
        }

    }
    else {

                @t = split (/,/,$test); # @t = affaire chose question
        print "T : @t\n";

        @temp = grep(!/$split_list[$elt]/, @t); 
        print "GREP : @temp\n";#@temp = chose question

        @fin = join(', ', @temp); #@fin = chose, question;

        for ($k = 0; $k < @fin; $k++){
                $te{$split_list[$elt]} = $fin[$k];
                }
    }
}

}



print Dumper \%te;

OUTPUT:

SPLIT ELT : cause
T : affaire chose question
GREP : affaire chose question
T : cause chose matière
GREP : chose matière
SPLIT ELT : cause
T : affaire chose question
GREP : affaire chose question
T : cause chose matière
GREP : chose matière
SPLIT ELT : cause
T : affaire chose question
GREP : affaire chose question
T : cause chose matière
GREP : chose matière
$VAR1 = {
          'cause' => 'affaire, chose, questionchose, matièreaffaire, chose, questionchose, matièreaffaire, chose, questionchose, matière'
        };
G. Cito
  • 6,210
  • 3
  • 29
  • 42
KeyPi
  • 516
  • 5
  • 20

2 Answers2

3

For each element in @list, split it at ,, and use each field as key of %te, push others to the value of that key:

#!/usr/bin/perl

use strict;
use warnings;

use Data::Dumper;

my @list = (
    "affaire,chose,question",
    "cause,chose,matière",
);

my %te;

foreach my $str (@list) {
    my @field = split /,/, $str;
    foreach my $key (@field) {
        my @other = grep { $_ ne $key } @field;
        push @{$te{$key}}, @other;
    }
}

print Dumper(\%te);

Ouput:

$ perl t.pl
$VAR1 = {
          'question' => [
                          'affaire',
                          'chose'
                        ],
          'affaire' => [
                         'chose',
                         'question'
                       ],
          'matière' => [
                          'cause',
                          'chose'
                        ],
          'cause' => [
                       'chose',
                       'matière'
                     ],
          'chose' => [
                       'affaire',
                       'question',
                       'cause',
                       'matière'
                     ]
        };
Lee Duhem
  • 14,695
  • 3
  • 29
  • 47
  • Nice ... +1 for all your solutions - I checked the edit history :-) What's the best practice or pattern for checking specific values for keys in a HoA like this? Some combination of `List::Util` and normal hash access? – G. Cito May 22 '14 at 04:14
  • @G.Cito I guess both `List::Util` and `List::MoreUtils` are useful in this case. – Lee Duhem May 22 '14 at 04:20
  • 1
    Or the marvelous, recently discovered (at least by me) [`List::AllUtils`](https://metacpan.org/pod/List::AllUtils)! No more `use List::Utils` oops `use List::Util` oops `use List::MoreUtils` oops `use List::Util ; use List::MoreUtils ...` :-), cheers – G. Cito May 22 '14 at 12:56
2

I think I see what you're trying to do: index semantic links between words followed by lists of synonyms. Am I correct? :-)

Where a word appears in more than one synonym list, then for that word you create a hash entry with the word as a key and using the keywords for which it was originally a synonym as values ... or something like that. Using a hash of arrays - as in the solution by @Lee Duhem - you get a list (array) of synonyms for each key word. This is a common pattern. You do end up with a lot of hash entries though.

I've been playing with a neat module by @miygawa called Hash::MultiValue that takes a different approach to accessing a list of values associated with each hash key: multi-value hash. A few nice features are that you can create hash of array references on the fly from the multi-value hash, "flatten" the hash, write callbacks to go with the ->each() method, and other neat things so it's pretty flexible. I believe the module has no dependencies (other than for testing). Plus it's by @miyagawa (and other contributors) so using it and reading it is good for you :-)

I'm no expert and I'm not sure it's appropriate for what you want - as a variation on Lee's approach you might have something like:

#!/usr/bin/env perl
use strict;
use warnings;
use Hash::MultiValue;

my $words_hash = Hash::MultiValue->new();

# set up the mvalue hash
for my $words (<DATA>) {
  my @synonyms = split (',' , $words) ; 
  $words_hash->add( shift @synonyms => (@synonyms[0..$#synonyms]) ) ;
};

for my $key (keys %{ $words_hash } ) {
  print "$key --> ", join(", ",  $words_hash->get_all($key)) ;
};

print "\n";

sub synonmize {
  my $bonmot = shift;
  my @bonmot_syns ;

  # check key "$bonmot" for word to search and show values
  push @bonmot_syns , $words_hash->get_all($bonmot);

  # now grab values but leave out synonym's synonyms
  foreach (keys %{ $words_hash } ) {
    if ($_ !~ /$bonmot/ && grep {/$bonmot/} $words_hash->get_all($_)) {
      push @bonmot_syns, grep {!/$bonmot/} $words_hash->get_all($_);
    }
  }

  # show the keys with values containing target word
  $words_hash->each(
    sub { push @bonmot_syns,  $_[0] if grep /$bonmot/ ,  @_[1..$#_] ; }
  );

  chomp @bonmot_syns ;
  print "synonymes pour \"$bonmot\": @bonmot_syns \n" ;
}

# find synonyms 
synonmize("chose");
synonmize("truc");
synonmize("matière");

__DATA__
affaire,chose,question
cause,chose,matière
chose,truc,bidule
fille,demoiselle,femme,dame

Output:

fille --> demoiselle, femme, dame
affaire --> chose, question
cause --> chose, matière
chose --> truc, bidule

synonymes pour "chose": truc bidule question matière affaire cause 
synonymes pour "truc": bidule chose 
synonymes pour "matière": chose cause

Tie::Hash::MultiValue is another alternative. Kudos to @Lee for a quick clean solution :-)

G. Cito
  • 6,210
  • 3
  • 29
  • 42
  • 1
    argh ... reading this code again I see this is a pretty roundabout approach - a possible anti-pattern if more people abused things the way I do ... :-\ Since `Plack` depends on this module it's possible to see correct usage in action. You can learn a lot from CPAN! – G. Cito May 22 '14 at 04:19
  • it's useful ! I didn't know it ..great ! – KeyPi May 22 '14 at 22:24