Rajesh Kumar
Rajesh Kumar

Reputation: 355

Delete an entry from array based on custom duplicate value

I am looking for some help in one senario.

Requirement:

Through some validation, I have strored following kinds of value in one array.

@array_name = ("Rajesh","Raju","Ram","John","peter");

Now I know from some background that "Rajesh", "Ram", "peter", are duplicate entries so I would expect my output to be:

@array_name = ("Rajesh","Raju","John");
# or 
@array_name = ("Ram","Raju","John");
# or 
@array_name = ("peter","Raju","John");

I have done example program such as below but it does not satisfy me...

    my $spcific_output ="";
    my $output ="";

    foreach my $name (@array_name) 
    {
        if($name eq "Rajesh" || $name eq "Ram" || $name eq "peter")
        {
            $spcific_output = "Rajesh and Ram and peter");
        }
        else
        {
            $output .= "My Name is $name";
        }
    } 
    $output .= $spcific_output;

Any best way to achieve this?

Upvotes: 2

Views: 211

Answers (2)

Greg Bacon
Greg Bacon

Reputation: 139451

With Perl, whenever you want unique values from some collection, think about how you can use a hash to help you automatically collapse duplicates or at least help you remember which values you have already seen. For example, see How can I get the unique keys from two hashes? in section 4 of the Perl FAQ.

Your case is a little trickier because you have sets of interchangeable names, so you must record this information.

sub add_names {
  my $equivalent = shift;

  for (@_) {
    my @names = map lc, @$_;
    for (@names) {
      die "$0: overlap on name '$_'" if exists $equivalent->{$_};
      $equivalent->{$_} = \@names;
    }
  }

  $equivalent;
}

Here, $equivalent is a reference to a hash. After calling

add_names $equivalent, [ qw/ Rajesh Ram peter / ];

the hash will have keys 'rajesh', 'ram', and 'peter' whose values are all [ 'rajesh', 'ram', 'peter' ]. Structuring it this way means we can get to the full set of names no matter which name we encounter first.

Note also that you can stack multiple sets of names in a single call, as in

add_names $equivalent, [ qw/ Rajesh Ram peter / ],
                       [ qw/ Jim Bob Bubba / ];

With the names mapped out, we can now process a list and keep the first name from each set that we find. For a given name, check whether we have seen it or any of its equivalents previously. If we have not seen it, save the name and mark all equivalents as seen.

sub remove_duplicates {
  my $equivalent = shift;

  my %seen;
  my @uniques;
  foreach my $name (@_) {
    my $normal = lc $name;
    unless ($seen{$normal}) {
      push @uniques, $name;
      ++$seen{$_} for @{ $equivalent->{$normal} };
    }
  }

  wantarray ? @uniques : \@uniques;
}

The wantarray bit at the bottom is a common Perl idiom for adapting the return value to the calling context. If the caller wants an array, we return the array. If not, we return a scalar, namely a reference to our array of unique names.

Putting it all together makes

my $equivalent = {};
add_names $equivalent, [qw/ Rajesh Ram peter /];

my @array_name = ("Rajesh","Raju","Ram","John","peter");
print $_, "\n" for remove_duplicates $equivalent, @array_name;

Output:

Rajesh
Raju
John

Upvotes: 2

flesk
flesk

Reputation: 7579

If you're using v5.10 or higher you can use smart matching on an array with your duplicate names:

#!/usr/bin/env perl
use strict;
use warnings;
use Data::Dumper;

my @names = qw(Rajesh Raju Ram John Peter);
my @dupl = qw(Rajesh Ram Peter);
my $seen;

my @names = grep {$_ ~~ @dupl ? !$seen++ : 1} @names;

print Dumper \@names;

Output:

$VAR1 = [
          'Rajesh',
          'Raju',
          'John'
        ];

The condition in grep evaluates !$seen++ if a name from @names is in @dupl, and keeps $_ only when $seen is 0. Otherwise 1 (true) is evaluated and $_ is kept.

Upvotes: 2

Related Questions