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!
Re: Replacing values in an array
by toolic (Bishop) on Jan 26, 2013 at 23:07 UTC
|
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
];
| [reply] [d/l] |
|
Thank you, it WORKS !!!!!
I really must overcome my fear of hashes now, thank you also for that encouragement.
-tonto
| [reply] |
Re: Replacing values in an array
by BrowserUk (Patriarch) on Jan 26, 2013 at 23:43 UTC
|
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.
| [reply] [d/l] |
|
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)
| [reply] [d/l] [select] |
|
Brilliant!
Now I have three elegant solutions and a reason to study "map"!
Thank you!
-tonto
| [reply] |
|
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. | [reply] [d/l] [select] |
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";
| [reply] [d/l] |
|
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
| [reply] |
|
# 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! | [reply] [d/l] [select] |
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.
| [reply] [d/l] [select] |
|
|