http://www.perlmonks.org?node_id=1015541

tonto has asked for the wisdom of the Perl Monks concerning the following question:

I am trying to renumber an array. I want the duplicated values in the original array to have the same new values in the renumbered array, and the first value should be changed to 1 and the next to 3 and so on, using odd numbers. The order of the array must be preserved. Here is my code:
#!/usr/bin/perl use strict; use warnings; use Data::Dumper; my @array = ("M94202", "M94150", "M94297", "M94150", "M94161", "M94161 +", "M94162"); my $z = 1; foreach my $item (@array) { if ($item =~ m/M/g) { my $uniqitem = $item; foreach $uniqitem (@array) { $uniqitem =~ s/$uniqitem/$z/g; $z = $z +2; } } } print Dumper \@array; print "\n";
The output I am getting is (1,3,5,7,9,11,13) What I want is (1,3,5,3,7,7,9) Where have I gone wrong, please?
Thank you for reading!

Replies are listed 'Best First'.
Re: Replacing values in an array
by toolic (Bishop) on Jan 26, 2013 at 23:07 UTC
    Use a hash:
    use strict; use warnings; use Data::Dumper; my @array = ("M94202", "M94150", "M94297", "M94150", "M94161", "M94161 +", "M94162"); my %uniq; my $z = 1; for (@array) { if (/M/) { if (exists $uniq{$_}) { $_ = $uniq{$_}; } else { $uniq{$_} = $z; $_ = $z; $z += 2; } } } print Dumper(\@array); __END__ $VAR1 = [ 1, 3, 5, 3, 7, 7, 9 ];
      Thank you, it WORKS !!!!!

      I really must overcome my fear of hashes now, thank you also for that encouragement.
      -tonto
Re: Replacing values in an array
by BrowserUk (Patriarch) on Jan 26, 2013 at 23:43 UTC

    Somewhat simpler version:

    my( $n, %h ) = -1; my @a = map{ $h{$_} //= $n+=2 } qw[ M94202 M94150 M94297 M94150 M94161 + M94161 M94162 ];; print @a;; 1 3 5 3 7 7 9

    With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.

      Using for instead of map:

      use strict; use warnings; use Data::Dumper; my @a = ("M94202", "M94150", "M94297", "M94150", "M94161", "M94161", " +M94162"); my ( $n, %h ) = -1; $_ = $h{$_} //= $n+=2 for @a; print Dumper \@a;

      I suppose if you were playing golf, without use strict, you might write:

      my ( $n, %h ) = -1; $_ = $h{$_} //= $n+=2 for @a;
      in one-line as:
      $_=$h{$_}//=$}+=2-!$}for@a;
      (/me ducks)

      Brilliant!
      Now I have three elegant solutions and a reason to study "map"!
      Thank you!
      -tonto

        map and grep can be an intimidating functions, but quite useful once you understand them.

        Let's consider grep first, since this one is a little simpler to grasp — or at least, that was my experience. grep takes two arguments, the second being a list of elements to work with, the first being the work that you want to have done on that list. You can specify that as a BLOCK, a code reference, or just the name of a function. grep then loops over the list, aliasing $_ to each element in turn, and calls the given piece of code. Then it returns every element for which the code returned a true value.

        my @numbers = (0, 0.5, 1, 1.5, 2, 2.5); # Calling grep with a BLOCK: my @integers = grep {int($_) == $_} @numbers; print join(", ", @integers), " are integers.\n"; # Calling grep with a function name: my @basket = ("apple", undef, undef, undef, "banana", "cherry", undef, + "date"); print "The number of elements in \@basket is ", scalar(@basket), "\n"; my @basket_1 = grep defined, @basket; print "The number of *defined* elements in \@basket is ", scalar(@bask +et_1), "\n";

        See what happens? grep returns the elements of the list you gave it, for which the piece of code returns true. The non-grep equivalents would be:

        my @numbers = (0, 0.5, 1, 1.5, 2, 2.5); # Calling grep with a BLOCK: my @integers; for (@numbers) { push @integers, $_ if int($_) == $_; } print join(", ", @integers), " are integers.\n"; # Calling grep with a function name: my @basket = ("apple", undef, undef, undef, "banana", "cherry", undef, + "date"); print "The number of elements in \@basket is ", scalar(@basket), "\n"; my @basket_1; for (@basket) { push @basket_1, $_ if defined; } print "The number of *defined* elements in \@basket is ", scalar(@bask +et_1), "\n";

        Now, map is pretty similar, except that it allows you to change the elements:

        my @numbers = 1..10; my @times_ten = map { $_ * 10 } @numbers; print join(", ", @times_ten), "\n";

        Of course, these are just the basics — the range of things you can do with them is astonishing. I hope this helps you along.

Re: Replacing values in an array
by eyepopslikeamosquito (Archbishop) on Jan 26, 2013 at 23:26 UTC

    When dealing with duplicates in Perl, you should normally use a hash (perldoc -q duplicate). My solution (written before seeing toolic's) is essentially the same as his, though I excluded the check for "M" since all your test data contains "M".

    use strict; use warnings; use Data::Dumper; my @array = ("M94202", "M94150", "M94297", "M94150", "M94161", "M94161 +", "M94162"); my %seen; my $z = 1; foreach my $item (@array) { if (exists $seen{$item}) { $item = $seen{$item}; } else { $seen{$item} = $z; $item = $z; $z += 2; } } print Dumper \@array; print "\n";

      Seeing the same solution written differently makes it more clear to me, I am very grateful to you. I will study hashes until I get them through my thick skull! I have spent days on this.
      Again, my sincere thanks!
      -tonto

        You can consider a hash to be much like an array, except instead of numerical indexes to access individual elements, you use strings as keys.

        # Define an array: my @basket = qw(apple banana cherry); # Get an element from the array: # (remember that indexes are 0-based) print "The second kind of fruit in the basket is $basket[1]\n"; # Change an element: $basket[1] = "date"; print "Now it is $basket[1]\n";

        The above example shouldn't be unfamiliar. Now, instead of keeping a @basket that tells us what kinds of fruit we have in the basket, let's keep a %basket that can also tell us how much of that kind of fruit we have.

        # Define the hash: my %basket = ( apple => 12, banana => 6, cherry => 32, # This final comma is optional, ); # but makes it easier to add more lines in the f +uture. # Get an element from the basket: print "There are $basket{cherry} cherries in the basket.\n"; # Modify elements: $basket{cherry}--; print "Now there are $basket{cherry}.\n"; $basket{banana} *= 2; print "Double Banana Bonus! $basket{banana} bananas in the basket!\n"; $basket{apple} = 10; # Add an element: $basket{date} = 16; # Get all keys in the hash: print "Fruits in my basket: ", join(", ", sort keys %basket), "\n"; # Using a variable as a key: for my $fruit (sort keys %basket) { print "You want a(n) $fruit? I have $basket{$fruit} in my basket.\ +n"; } # The 'each' function: while (my ($fruit, $amount) = each %basket) { print "There are $amount ${fruit}s in my basket.\n"; } # Getting rid of an element: delete $basket{apple}; print "Fruits in my basket: ", join(", ", sort keys %basket), "\n";

        That pretty much covers the basics of hashes. Nothing to be afraid of, and quite a useful data type!

Re: Replacing values in an array
by johngg (Canon) on Jan 27, 2013 at 00:05 UTC

    A slight variation on toolic's and eyepopslikeamosquito's solutions in that I use a closure to generate the odd numbers (it returns the next odd number every time it is called) and I wrap the logic for finding duplicates in a do block to avoid leaving the %seen hash lying around after it is no longer needed.

    $ perl -Mstrict -Mwarnings -MData::Dumper -e ' > my $oddNos = do { > my $val = -1; > sub { $val += 2; }; > }; > > my @array = qw{ > M94202 > M94150 > M94297 > M94150 > M94161 > M94161 > M94162 > }; > > @array = do { > my %seen; > map { > exists $seen{ $_ } > ? $seen{ $_ } > : do { > $seen{ $_ } = $oddNos->(); > $seen{ $_ }; > }; > } @array; > }; > > print Data::Dumper->Dumpxs( [ \ @array ], [ qw{ *array } ] );' @array = ( 1, 3, 5, 3, 7, 7, 9 ); $

    I hope this is of interest.

    Cheers,

    JohnGG