Unexpected Result in Doctrine GroupBy - doctrine-orm

I have the following repository:
$qb = $this->getEntityManager()->createQueryBuilder();
$qb
->select('r')
->from('MyBundle:Rating', 'r')
->groupBy('r.year');
$result = $qb->getQuery()->execute();
return $result;
Doing a dump in the controller:
array(2) {
[0]=>
object(My\Entity\Rating)#553 (5) {
["id":protected]=>
int(2)
["index":protected]=>
int(1)
["year":protected]=>
int(2010)
["percentage":protected]=>
float(2.5)
}
[1]=>
object(My\Entity\Rating)#550 (5) {
["id":protected]=>
int(1)
["index":protected]=>
int(1)
["year":protected]=>
int(2016)
["percentage":protected]=>
float(0)
}
}
I was expecting an array with the years as indexes, with the values being an array of objects that match the year, Should that not be the case?
there are about 10 records in the db. some how only these 2 show up. Only records for two different years are entered, which may be the reason there are only two records. Does anybody know why?
How do I fulfill the expectation in 1?

Doctrine gives out objects by default. If you want to get an array with the resulting data you can use
$result = $qb->getQuery()->execute(null, Query::HYDRATE_ARRAY);
You need to include Doctrine\ORM\Query for this or you just use
$result = $qb->getQuery()->getArrayResult();
The reason you only get data about those two years is
->groupBy('r.year');
which eliminates duplicate entries. Without it you will get them all.

Related

Linq get element from string list and a position of a char in this list

i want to get an element from a list of string and get the position of a char in this list by using linq ?
Example :
List<string> lines = new List<string> { "TOTO=1", "TATA=2", "TUTU=3"}
I want to extract the value 1 from TOTO in the list
here is the begin of my code
var value= lines.ToList().Single(x =>x.Contains("TOTO=")).ToString().Trim();
How to continue this code to extract 1 ?
Add this :
value = value[(value.LastIndexOf('=') + 1)..];
Using LINQ you can do this:
List<string> lines = new List<string> { "TOTO=1", "TATA=2", "TUTU=3" };
int value = lines
.Select(line => line.Split('='))
.Where(parts => parts[0] == "TOTO")
.Select(parts => int.Parse(parts[1]))
.Single();
If you always expect each item in that list to be in the proper format then this should work, otherwise you'd need to add some validation.
Similar to What #jtate proposed, Some minor enhancements can help.
int value = lines
.Select(line => line.Split(new []{ '=' }, StringSplitOptions.RemoveEmptyEntries))
.Where(parts => string.Equals(parts[0], "TOTO", StringComparison.InvariantCultureIgnoreCase))
.Select(parts => int.Parse(parts[1]))
.SingleOrDefault();
SingleOrDefault - If you don't find any elements matching your constraints, Single() would thow an exception. Here, SingleOrDefault would return 0;
String.Equals - would take care of any upper lowere or any culture related problems.
StringSplitOptions.RemoveEmptyEntries - would limit some unecessary iterations and improve performance.
Also see if you need int.TryParse instead of int.Prase. All these checks would help cover edges cases in production

How can I enforce restrictions on an object's description via e.g. a hash? [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 8 years ago.
Improve this question
If I have a hash that "describes" attributes of objects i.e. names and types like:
{
column_defs => {
serial_id => { type => INTEGER },
first_name => { type => CHAR(40) },
salary => { type => DOUBLE },
},
}
The actual hash might be changed but I don't think that the definition affects my core question.
What is a good way to check if the types of each field is defined correctly? E.g. that salary is not defined FLOAT or serial_id is not string etc. (but what should or should not be should be configurable)
I am not sure what would be the best way to define these actual "restrictions" and how to apply them on the hash.
It sounds like you just want to do string comparison between values in a file and values in a hash:
use strict;
use warnings;
use 5.010;
my %column_defs = (
serial_id => { type => 'INTEGER' },
first_name => { type => 'CHAR(40)' },
salary => { type => 'DOUBLE' },
);
while (<DATA>) {
my ($column, $type) = split;
if (my $def = $column_defs{$column}) {
say "'$column' should be $def->{type} but is $type" if $def->{type} ne $type;
}
}
__DATA__
salary DOUBLE
serial_id FLOAT
first_name CHAR(20)
foo BAR
Note that for simplicity, I made the column definitions from your snippet into their own hash.
Output:
'serial_id' should be INTEGER but is FLOAT
'first_name' should be CHAR(40) but is CHAR(20)
EDIT: Misread the question, or rather, didn't see the relevant comment until it was too late - doh! This answer is for checking the values of the fields conform to the specs. Maybe you'll want to do that later! ;-)
You can do this kind of checking manually by setting up tests for the different types of data you're looking at and then running them on the data, e.g.
## tests
sub integer {
return 1 if $_[0] =~ /^[0-9]+$/;
return 0;
}
sub string {
return 1 if $_[0] =~ /^[\w\s]+$/;
return 0;
}
sub char_40_ {
return 1 if length($_[0]) <= 40;
return 0;
}
sub double {
(etc.)
...
# set up a dispatch hash
my %d_hash = (
serial_id => \&integer,
first_name => \&char_40_,
salary => \&double,
another_field => \&integer,
more_data => \&string,
);
Then you can check that the data in the fields matches the specs by calling the appropriate sub:
$d_hash{ $field_name }->( $value_of_field )
Depending on how many different types of data you're looking at, and the number of each, you may want to set up specific tests, e.g. CHAR(40) runs a specific test for fields of 40 chars or less, or you could parse the type field and apply a test for CHARs and for length 40 or less.
I must admit I'm not 100% clear what you're asking, but perhaps something like this might be helpful...?
use strict;
use warnings;
use Types::Standard qw( Dict );
use Types::XSD::Lite qw( String Integer Float );
my $check = Dict[
serial_id => Integer,
first_name => String[ maxLength => 40 ],
salary => Float,
];
$check->assert_valid({
serial_id => 999,
first_name => "Alice",
salary => 20_000.00,
});
$check->assert_valid({
serial_id => 999,
first_name => "Bob",
salary => "peanuts", # fails
});
Not 100% clear what you're asking...
When you define an object, you're suppose to use your methods to verify an object. Here's a very simple example:
package Local::Employee;
use strict;
use warnings;
use Carp;
use Scalar::Util qw(looks_like_number);
use feature qw(say state);
sub new {
my $class = shift;
my $first_name = shift;
my $salary = shift;
my $self = {};
bless $self, $class;
$self->serial;
$self->first_name( $first_name );
$self->salary( $salary );
return $self;
}
sub serial {
my $self = shift;
my $bad_boy = shift;
state $serial = 0;
if ( defined $bad_boy ) {
croak( qq(Cannot set serial number. You can only retrieve it.) );
}
if ( not exists $self->{SERIAL} ) {
$self->{SERIAL} = sprintf "%04d", ++$serial;
}
return $self->{SERIAL};
}
sub first_name {
my $self = shift;
my $first_name = shift;
if ( defined $first_name ) {
if ( length $first_name > 40 ) {
croak( qq(First name can't be longer than forty characters.) );
}
$self->{FIRST_NAME} = $first_name;
}
return $self->{FIRST_NAME};
}
sub salary {
my $self = shift;
my $salary = shift;
if ( defined $salary ) {
if ( not looks_like_number $salary ) {
croak( qq(Salary must be numeric) );
}
$self->{SALARY} = $salary;
}
return $self->{SALARY};
}
sub employee {
my $self = shift;
my %employee_hash = %{ $self };
return \%employee_hash;
}
1;
Notice that each of my methods verify their data. Also notice that my new constructor doesn't know how the data itself is stored. It merely calls the methods for each piece of data. You can bless an object and use the various methods before you return the object to the main program.
Also notice that if I return the whole structure, I dereference my hash reference, and then return that hash reference. In other words, when I return my structure, you get a copy of that structure and not the structure itself. Futzing with $employee->{NAME} isn't going to change the actual name of the object.
Is this totally protected? No. A bad player could use Data::Dumper, get the structure of my object, and then set $self->{NAME} = $name instead of calling $self->name($name). There is a concept of Inside-Out Objects, but most developers have agreed it's more trouble than it's worth. Makes debugging really difficult.
Maybe what you want is more flexibility, you want to define an object's structure on the fly, and verify it.
Take a close look at all of my methods, and you can see they're all very similar in how they work. I could almost create a template. In fact, there are template classes like Class::Struct that allow you to create classes on the fly. Class::Struct defines subroutines that act as methods based upon your field names and puts those subroutines in a namespace you've defined as you class. If you have a class called Foo with a method bar, a subroutine called Foo::bar will be defined, and of course it will also create a subroutine Foo::new to use as a constructor. Once you've defined a class, you can use it just like a normal class:
my $foo = Foo->new(-bar => "bar value");
say "The value of bar is: " . $foo->bar";
If I were you, I wouldn't bother to create real classes because it's rather complex. Instead, I would have a class that allows me to create other classes.
Autoloading is a way of defining a single subroutine called AUTOLOAD that allows you to implement multiple methods without you having to define each and every one. When you call a method like this:
$employee->first_name("Bob");
Perl looks through your subroutines for one named first_name. If it can't find one, it looks for one called AUTOLOAD and sends it the parameters and sets $AUTOLOAD to the name of the method being called.
Let's say you have a hash of classes that contains two classes, one employee and one car:
$VAR = {
"employee" => "FIELDS" => {
"first_name" => "string:40",
"serial" => "serial:d09",
"salary" => "float:%8.2f",
}
{
"cars" => "FIELDS" => {
"license" => "string:9",
"make" => "string:20",
"model" => "string:20",
"top_speed" => "integer:4",
}
}
Now imagine a way to create a new class like this:
my $employee = Local::Class->new( "employee", {
-first_name => "Bob",
-salary => 342.95,
},
);
my $car = Local::Class->new( "car", {
-make => "Chevy",
-model => "Lumina",
-engine => "1.2",
},
);
You could use your new constructor subroutine to set all of those fields via the AUTOLOAD subroutine. Once done, you can treat each object as a class. Let's say you want to know the name of the employee:
say "The employee's name is " . $employee->name;
This could be handled by an AUTOLOAD subroutine that sees what type of class employee is, then verifies if name is even a valid method. If it is, it returns the value of that hash key. Let's take a look at setting:
$employee->name("Bob, the one and true King of France. All hale his majesty");
It again uses AUTOLOAD to verify that name is a valid field, and that the value you're setting is a valid name. (In this case, the subroutine will croak with a name too long error).
Is that more what you want to do? You'll have to have a way of creating your class hash, and making sure that it cannot be changed once set. Then, you simply have to be able to use that hash to verify each object you're creating.

How to use regular expression to find keys in hash

I have 6mio hashes and need to count how many of these have keys that start with AA00, AB10 and how many of them have keys starting with with both strings.
For each hash I have done this:
if (exists $hash{AA00}) {
$AA00 +=1;
}
if (exists $hash{AB10}) {
$AB10 += 1;
}
if (exists $hash{AA00} and exists $hash{AA10}) {
$both += 1;
}
but then I count only the number of hashes that contains exactly AA00 or AB10 as keys, but I would also like to count hashes that contain, say AA001. Can I use regular expression for this?
I completely misunderstood your question. To find the number of hashes with keys matching a regex (as opposed to the number of keys matching a regex in a single hash), you can still use the grep approach I outlined in my earlier answer. This time, however, you need to loop through your hashes (I assume you're storing them in an array if you have 6 million of them) and run grep twice on each one:
#!/usr/bin/perl
use strict;
use warnings;
use feature 'say';
my #array = (
{ AA00 => 'foo' },
{ AB10 => 'bar' },
{ AA001 => 'foo' },
{ AA00 => 'foo', AB10 => 'bar' }
);
my ($hashes_with_aa00, $hashes_with_ab10, $hashes_with_both) = (0, 0, 0);
foreach my $hash (#array) {
my $aa_count = grep { /^AA00/ } keys %$hash;
my $ab_count = grep { /^AB10/ } keys %$hash;
$hashes_with_aa00++ if $aa_count;
$hashes_with_ab10++ if $ab_count;
$hashes_with_both++ if $aa_count and $ab_count;
}
say "AA00: $hashes_with_aa00";
say "AB10: $hashes_with_ab10";
say "Both: $hashes_with_both";
Output:
AA00: 3
AB10: 2
Both: 1
This works, but is pretty poor in terms of performance: grep loops through every element in the list of keys for each hash, and we're calling it twice per hash!
Since we don't care how many keys match in each hash, only whether there is a match, a better solution would be any from List::MoreUtils. any works much like grep but returns as soon as it finds a match. To use any instead of grep, change this:
foreach my $hash (#array) {
my $aa_count = grep { /^AA00/ } keys %$hash;
my $ab_count = grep { /^AB10/ } keys %$hash;
$hashes_with_aa00++ if $aa_count;
$hashes_with_ab10++ if $ab_count;
$hashes_with_both++ if $aa_count and $ab_count;
}
to this:
use List::MoreUtils 'any';
foreach my $hash (#array) {
my $aa_exists = any { /^AA00/ } keys %$hash;
my $ab_exists = any { /^AB10/ } keys %$hash;
$hashes_with_aa00++ if $aa_exists;
$hashes_with_ab10++ if $ab_exists;
$hashes_with_both++ if $aa_exists and $ab_exists;
}
Note that I changed the variable names to better reflect their meaning.
This is much better in terms of performance, but as Borodin notes in a comment on your question, you're losing the speed advantage of hashes by not accessing them with specific keys. You might want to change your data structure accordingly.
Original Answer: Counting keys that match a regex in a single hash
This is my original answer based on a misunderstanding of your question. I'm leaving it up because I think it could be useful for similar situations.
To count the number of keys that match a regex in a single hash, you can use grep:
my $aa_count = grep { /^AA00/ } keys %hash;
my $ab_count = grep { /^AB10/ } keys %hash;
my $both = $aa_count + $ab_count;
As HunterMcMillen points out in the comments, there's no need to search through the hash keys again to get the total count; in this case, you can simply add the two subtotals. You can get away with this because the two patterns you're searching for are mutually exclusive; in other words, you cannot have a key that both begins with AA00 and AB10.
In the more general case, it might be possible for a single key to match both patterns (thanks Borodin). In that case, you cannot simply add up the two subtotals. For example, if you wanted your keys to merely contain AA00 or AB10 anywhere in the string, not necessarily at the beginning, you would need to do something like this:
my $aa_count = grep { /AA00/ } keys %hash;
my $ab_count = grep { /AB10/ } keys %hash;
my $both = grep { /(?:AA00|AB10)/ } keys %hash;
Note that this calls grep multiple times, which means traversing the entire hash multiple times. This could be done more efficiently using a single for loop like FlyingFrog and Kenosis did.

Perl regex & data extraction/manipulation

I'm not sure where to start with this one... my client gets stock figures from his supplier but they are now being sent in a different format, here is a sample snippet:
[["BLK",[["Black","0F1315"]],[["S","813"],["M","1378"],["L","1119"],["XL","1069"],["XXL","412"],["3XL","171"]]],["BOT",[["Bottle","15451A"]],[["S","226"],["M","425"],["L","772"],["XL","509"],["XXL","163"]]],["BUR",[["Burgundy","73002E"]],[["S","402"],["M","530"],["L","356"],["XL","257"],["XXL","79"]]],["DNA",[["Deep Navy","000F33"]],[["S","699"],["M","1161"],["L","1645"],["XL","1032"],["XXL","350"]]],["EME",[["Emerald","0DAB5E"]],[["S","392"],["M","567"],["L","613"],["XL","431"],["XXL","97"]]],["HEA",[["Heather","C0D4D7"]],[["S","374"],["M","447"],["L","731"],["XL","386"],["XXL","115"],["3XL","26"]]],["KEL",[["Kelly","0FFF00"]],[["S","167"],["M","285"],["L","200"],["XL","98"],["XXL","45"]]],["NAV",[["Navy","002466"]],[["S","451"],["M","1389"],["L","1719"],["XL","1088"],["XXL","378"],["3XL","177"]]],["NPU",[["Purple","560D55"]],[["S","347"],["M","553"],["L","691"],["XL","230"],["XXL","101"]]],["ORA",[["Orange","FF4700"]],[["S","125"],["M","273"],["L","158"],["XL","98"],["XXL","98"]]],["RED",[["Red","FF002E"]],[["S","972"],["M","1186"],["L","1246"],["XL","889"],["XXL","184"]]],["ROY",[["Royal","1500CE"]],[["S","1078"],["M","1346"],["L","1102"],["XL","818"],["XXL","135"]]],["SKY",[["Sky","91E3FF"]],[["S","567"],["M","919"],["L","879"],["XL","498"],["XXL","240"]]],["SUN",[["Sunflower","FFC700"]],[["S","843"],["M","1409"],["L","1032"],["XL","560"],["XXL","53"]]],["WHI",[["White","FFFFFF"]],[["S","631"],["M","2217"],["L","1666"],["XL","847"],["XXL","410"],["3XL","74"]]]]
Firstly the inital [ and end ] can be removed
Then it needs be be broken down into segments of colours, i.e.:
["BLK",[["Black","0F1315"]],[["S","813"],["M","1378"],["L","1119"],["XL","1069"],["XXL","412"],["3XL","171"]]]
The BLK is needed here, the next block [["Black","0F1315"]] can be disregarded.
Next I need to take the stock data for each size ["S","813"] etc
Therefore I should have a data such as:
$col = BLK
$size = S
$qty = 813
$col = BLK
$size = M
$qty = 1278
and repeat this segment for every colour seqment in the data.
The amount of colour segments in the data will vary, as will the amount of sizing segements within. Also the amount of sizing segments will vary colour to colour, i.e. there maybe 6 sizes for BLK but only 5 for RED
The data will be written out while in the loop for these so something like print "$col:$size:$qty" will be fine as this would then be in a format ready to be processed.
Sorry for the long message, I just can't seem to get my head round this today!!
Regards,
Stu
This looks like valid JSON to me, why not use a JSON parser instead of trying to solve this with a regex?
use JSON;
my $json_string = '[["BLK",[["Black","0F1315"]],[["S","813"...<snip>';
my $deserialized = from_json( $json_string );
Then you can iterate over the array and extract the pieces of information you need.
Building on Tim Pietzcker's answer:
...
my $deserialized = from_json( $json_string );
foreach my $group ( #$deserialized ) {
my ( $color, undef, $sizes ) = #$group;
print join( ":", $color, #$_ ), "\n" for #$sizes;
}
(And yes, for this particular format, eval should do as well as from_json, although the latter is safer. However, you should really try to find an official spec for the format: is it really JSON or something else?)
Assuming you have your data in $str, then eval(EXPR) (Danger Will Robinson!) and process the resulting data structure:
my $struct = eval $str;
foreach my $cref (#$struct) {
my($color, undef, $sizerefs) = #$cref; # 3 elements in each top level
foreach my $sizeref (#$sizerefs) {
my($size, $qty) = #$sizeref;
print "$color:$size:$qty\n";
}
}

What's the point of Perl's map?

Not really getting the point of the map function. Can anyone explain with examples its use?
Are there any performance benefits to using this instead of a loop or is it just sugar?
Any time you want to generate a list based another list:
# Double all elements of a list
my #double = map { $_ * 2 } (1,2,3,4,5);
# #double = (2,4,6,8,10);
Since lists are easily converted pairwise into hashes, if you want a hash table for objects based on a particular attribute:
# #user_objects is a list of objects having a unique_id() method
my %users = map { $_->unique_id() => $_ } #user_objects;
# %users = ( $id => $obj, $id => $obj, ...);
It's a really general purpose tool, you have to just start using it to find good uses in your applications.
Some might prefer verbose looping code for readability purposes, but personally, I find map more readable.
First of all, it's a simple way of transforming an array: rather than saying e.g.
my #raw_values = (...);
my #derived_values;
for my $value (#raw_values) {
push (#derived_values, _derived_value($value));
}
you can say
my #raw_values = (...);
my #derived_values = map { _derived_value($_) } #raw_values;
It's also useful for building up a quick lookup table: rather than e.g.
my $sentence = "...";
my #stopwords = (...);
my #foundstopwords;
for my $word (split(/\s+/, $sentence)) {
for my $stopword (#stopwords) {
if ($word eq $stopword) {
push (#foundstopwords, $word);
}
}
}
you could say
my $sentence = "...";
my #stopwords = (...);
my %is_stopword = map { $_ => 1 } #stopwords;
my #foundstopwords = grep { $is_stopword{$_} } split(/\s+/, $sentence);
It's also useful if you want to derive one list from another, but don't particularly need to have a temporary variable cluttering up the place, e.g. rather than
my %params = ( username => '...', password => '...', action => $action );
my #parampairs;
for my $param (keys %params) {
push (#parampairs, $param . '=' . CGI::escape($params{$param}));
}
my $url = $ENV{SCRIPT_NAME} . '?' . join('&', #parampairs);
you say the much simpler
my %params = ( username => '...', password => '...', action => $action );
my $url = $ENV{SCRIPT_NAME} . '?'
. join('&', map { $_ . '=' . CGI::escape($params{$_}) } keys %params);
(Edit: fixed the missing "keys %params" in that last line)
The map function is used to transform lists. It's basically syntactic sugar for replacing certain types of for[each] loops. Once you wrap your head around it, you'll see uses for it everywhere:
my #uppercase = map { uc } #lowercase;
my #hex = map { sprintf "0x%x", $_ } #decimal;
my %hash = map { $_ => 1 } #array;
sub join_csv { join ',', map {'"' . $_ . '"' } #_ }
See also the Schwartzian transform for advanced usage of map.
It's also handy for making lookup hashes:
my %is_boolean = map { $_ => 1 } qw(true false);
is equivalent to
my %is_boolean = ( true => 1, false => 1 );
There's not much savings there, but suppose you wanted to define %is_US_state?
map is used to create a list by transforming the elements of another list.
grep is used to create a list by filtering elements of another list.
sort is used to create a list by sorting the elements of another list.
Each of these operators receives a code block (or an expression) which is used to transform, filter or compare elements of the list.
For map, the result of the block becomes one (or more) element(s) in the new list. The current element is aliased to $_.
For grep, the boolean result of the block decides if the element of the original list will be copied into the new list. The current element is aliased to $_.
For sort, the block receives two elements (aliased to $a and $b) and is expected to return one of -1, 0 or 1, indicating whether $a is greater, equal or less than $b.
The Schwartzian Transform uses these operators to efficiently cache values (properties) to be used in sorting a list, especially when computing these properties has a non-trivial cost.
It works by creating an intermediate array which has as elements array references with the original element and the computed value by which we want to sort. This array is passed to sort, which compares the already computed values, creating another intermediate array (this one is sorted) which in turn is passed to another map which throws away the cached values, thus restoring the array to its initial list elements (but in the desired order now).
Example (creates a list of files in the current directory sorted by the time of their last modification):
#file_list = glob('*');
#file_modify_times = map { [ $_, (stat($_))[8] ] } #file_list;
#files_sorted_by_mtime = sort { $a->[1] <=> $b->[1] } #file_modify_times;
#sorted_files = map { $_->[0] } #files_sorted_by_mtime;
By chaining the operators together, no declaration of variables is needed for the intermediate arrays;
#sorted_files = map { $_->[0] } sort { $a->[1] <=> $b->[1] } map { [ $_, (stat($_))[8] ] } glob('*');
You can also filter the list before sorting by inserting a grep (if you want to filter on the same cached value):
Example (a list of the files modified in the last 24 hours sorted the last modification time):
#sorted_files = map { $_->[0] } sort { $a->[1] <=> $b->[1] } grep { $_->[1] > (time - 24 * 3600 } map { [ $_, (stat($_))[8] ] } glob('*');
The map function is an idea from the functional programming paradigm. In functional programming, functions are first-class objects, meaning that they can be passed as arguments to other functions. Map is a simple but a very useful example of this. It takes as its arguments a function (lets call it f) and a list l. f has to be a function taking one argument, and map simply applies f to every element of the list l. f can do whatever you need done to every element: add one to every element, square every element, write every element to a database, or open a web browser window for every element, which happens to be a valid URL.
The advantage of using map is that it nicely encapsulates iterating over the elements of the list. All you have to do is say "do f to every element, and it is up to map to decide how best to do that. For example map may be implemented to split up its work among multiple threads, and it would be totally transparent to the caller.
Note, that map is not at all specific to Perl. It is a standard technique used by functional languages. It can even be implemented in C using function pointers, or in C++ using "function objects".
The map function runs an expression on each element of a list, and returns the list results. Lets say I had the following list
#names = ("andrew", "bob", "carol" );
and I wanted to capitalize the first letter of each of these names. I could loop through them and call ucfirst of each element, or I could just do the following
#names = map (ucfirst, #names);
"Just sugar" is harsh. Remember, a loop is just sugar -- if's and goto can do everything loop constructs do and more.
Map is a high enough level function that it helps you hold much more complex operations in your head, so you can code and debug bigger problems.
To paraphrase "Effective Perl Programming" by Hall & Schwartz,
map can be abused, but I think that it's best used to create a new list from an existing list.
Create a list of the squares of 3,2, & 1:
#numbers = (3,2,1);
#squares = map { $_ ** 2 } #numbers;
Generate password:
$ perl -E'say map {chr(32 + 95 * rand)} 1..16'
# -> j'k=$^o7\l'yi28G
You use map to transform a list and assign the results to another list, grep to filter a list and assign the results to another list. The "other" list can be the same variable as the list you are transforming/filtering.
my #array = ( 1..5 );
#array = map { $_+5 } #array;
print "#array\n";
#array = grep { $_ < 7 } #array;
print "#array\n";
It allows you to transform a list as an expression rather than in statements. Imagine a hash of soldiers defined like so:
{ name => 'John Smith'
, rank => 'Lieutenant'
, serial_number => '382-293937-20'
};
then you can operate on the list of names separately.
For example,
map { $_->{name} } values %soldiers
is an expression. It can go anywhere an expression is allowed--except you can't assign to it.
${[ sort map { $_->{name} } values %soldiers ]}[-1]
indexes the array, taking the max.
my %soldiers_by_sn = map { $->{serial_number} => $_ } values %soldiers;
I find that one of the advantages of operational expressions is that it cuts down on the bugs that come from temporary variables.
If Mr. McCoy wants to filter out all the Hatfields for consideration, you can add that check with minimal coding.
my %soldiers_by_sn
= map { $->{serial_number}, $_ }
grep { $_->{name} !~ m/Hatfield$/ }
values %soldiers
;
I can continue chaining these expression so that if my interaction with this data has to reach deep for a particular purpose, I don't have to write a lot of code that pretends I'm going to do a lot more.
It's used anytime you would like to create a new list from an existing list.
For instance you could map a parsing function on a list of strings to convert them to integers.
As others have said, map creates lists from lists. Think of "mapping" the contents of one list into another. Here's some code from a CGI program to take a list of patent numbers and print hyperlinks to the patent applications:
my #patents = ('7,120,721', '6,809,505', '7,194,673');
print join(", ", map { "$_" } #patents);
As others have said, map is most useful for transforming a list. What hasn't been mentioned is the difference between map and an "equivalent" for loop.
One difference is that for doesn't work well for an expression that modifies the list its iterating over. One of these terminates, and the other doesn't:
perl -e '#x=("x"); map { push #x, $_ } #x'
perl -e '#x=("x"); push #x, $_ for #x'
Another small difference is that the context inside the map block is a list context, but the for loop imparts a void context.