rodinski has asked for the wisdom of the Perl Monks concerning the following question:
I humbly seek wisdom in the form of a better idiom(s).
I am regularly find myself needing to review ordered list. I'm not so
interested in the nodes but gap from one list item to the next. I have
heard this referred to as the fence vs. posts problem. I seek an idiom for
@ = "A", "B", "C";
my @gaps = mesh @l, @l;
pop @gaps; shift @gaps;
producing the even numbered list "A","B" ,"B","C"?
Then I work with these using the pairs function from List::Util and List::MoreUtils
In code form:
use strict;
use warnings;
use feature 'say';
use List::MoreUtils 'mesh';
use List::Util 'pairs', 'pairfirst', 'pairgrep', 'pairmap';
my @trip = ("Chicago", "Saint Looey", "Joplin", "OKC", "Amarillo", "
+Gallup",
"Flagstaff", "Winona", "Kingman", "Barstow", "San Bernandi
+no", "LA" ) ;
# I seek, not info on the cities, but info on the
# differences in going from one city to city.
my @legs = mesh @trip, @trip;
pop @legs;
shift @legs;
# I seek an idiom for the above three lines.
map { printf "%15s to %-15s\n", $_->[0],$_->[1] } pairs @legs;
# Further
# I might have city info as such:
# $info{Joplin}{state}=>"MO"
# map { if ($info{$_->[0]}{state} ne $info{$_->[1]}{state}) {say "Man
+n Act!"} } pairs @legs;
# given the above @cities array how might I produce
# $info{Joplin}{state}=>"MO"
#
my @states = ("IL", "MO", "MO", "OK", "TX", "TX",
"AZ", "AZ", "AZ", "CA", "CA", "CA" ) ;
my %info;
# the following does not work!!!
map { $info{$_[0]}{"state"}=$_[1] } pairs mesh @trip, @states;
say "print the hash:";
say join " ", %info;
Thank you
Re: Fence vs. Posts
by haukex (Archbishop) on Jun 08, 2018 at 08:00 UTC
|
This feels like an XY Problem to me. Why create lists with a bunch of duplicates, when you can just walk through @trip, looking at the current item and the next? Also, NetWallah made an excellent point about graphs - although in this case, if your "trips" are always linear, you don't really need a graph. I don't quite understand your question about the @states array - why not build the hash with city/state information first? So if you could explain the background of your question some more, we could probably suggest even better solutions.
Here's how I might have approached it - as you can see, no arrays with duplicate elements involved. In the "alternative" I go a step further and make the elements of @trip references to the anonymous hashes. So that I don't lose the city name, I store it in those hashes as well. This has the advantage that you know beforehand that all your cities are spelled correctly, and all the city information is available via the elements of the @trip array (notice how I no longer need %cities in the loop).
use warnings;
use strict;
use Data::Dump;
my @trip = ("Chicago", "Saint Looey", "Joplin", "OKC", "Amarillo",
"Gallup", "Flagstaff", "Winona", "Kingman", "Barstow",
"San Bernandino", "LA");
my %cities = ( "Amarillo" => { state => "TX" },
"Barstow" => { state => "CA" }, "Chicago" => { state => "IL" },
"Flagstaff" => { state => "AZ" }, "Gallup" => { state => "TX" },
"Joplin" => { state => "MO" }, "Kingman" => { state => "AZ" },
"LA" => { state => "CA" }, "OKC" => { state => "OK" },
"Saint Looey" => { state => "MO" },
"San Bernandino" => { state => "CA" },
"Winona" => { state => "AZ" }, );
for my $i (0..$#trip-1) {
my ($from,$to) = @trip[$i,$i+1];
print "$from, $cities{$from}{state} to $to, $cities{$to}{state}",
$cities{$from}{state} ne $cities{$to}{state}
? " - Mann Act!" : (), "\n";
}
print "\n##### Alternative #####\n";
$cities{$_}{city} = $_ for keys %cities;
$_ = $cities{$_}//die("bad city $_") for @trip;
dd \%cities, \@trip;
for my $i (0..$#trip-1) {
my ($from,$to) = @trip[$i,$i+1];
print "$from->{city}, $from->{state} to $to->{city}, "
."$to->{state}", $from->{state} ne $to->{state}
? " - Mann Act!" : (), "\n";
}
Anyway, to answer your original question, the idiom I would have used is map {@arr[$_,$_+1]} 0..$#arr-1. Note that using map purely for its side-effects is considered bad style by some, and usually a for statement modifier is also a bit easier to read. <update> To clarify, I mean that using map purely for its side effects as you've done in your original code - what I'm doing here does not fall into that category (see also my reply lower down in the thread). </update> The reason your creation of the %info hash isn't working in the original code is that you're not dereferencing the pairs.
use warnings;
use strict;
use Data::Dump;
use List::Util 'pairs';
use List::MoreUtils 'mesh';
my @trip = ("Chicago", "Saint Looey", "Joplin", "OKC", "Amarillo",
"Gallup", "Flagstaff", "Winona", "Kingman", "Barstow",
"San Bernandino", "LA");
my @states = ("IL", "MO", "MO", "OK", "TX", "TX", "AZ", "AZ",
"AZ", "CA", "CA", "CA" );
my @legs = map {@trip[$_,$_+1]} 0..$#trip-1;
printf "%15s to %-15s\n", $_->[0], $_->[1] for pairs @legs;
my %info;
$info{$_->[0]}{state} = $_->[1] for pairs mesh @trip, @states;
dd \%info;
__END__
Chicago to Saint Looey
Saint Looey to Joplin
Joplin to OKC
OKC to Amarillo
Amarillo to Gallup
Gallup to Flagstaff
Flagstaff to Winona
Winona to Kingman
Kingman to Barstow
Barstow to San Bernandino
San Bernandino to LA
{
"Amarillo" => { state => "TX" },
"Barstow" => { state => "CA" },
"Chicago" => { state => "IL" },
"Flagstaff" => { state => "AZ" },
"Gallup" => { state => "TX" },
"Joplin" => { state => "MO" },
"Kingman" => { state => "AZ" },
"LA" => { state => "CA" },
"OKC" => { state => "OK" },
"Saint Looey" => { state => "MO" },
"San Bernandino" => { state => "CA" },
"Winona" => { state => "AZ" },
}
| [reply] [d/l] [select] |
|
I like your suggestion of the 'for modifier'. Note however, that it requires the 'push' function to do the same thing as map.
C:\Users\Bill\forums\monks>type rodinski.pl
use strict;
use warnings;
use Data::Dumper;
my @arry = qw(A B C);
my @gaps;
push @gaps, [$arry[$_], $arry[$_+1]] for 0..$#arry-1;
print Dumper(\@gaps);
C:\Users\Bill\forums\monks>perl rodinski.pl
$VAR1 = [
[
'A',
'B'
],
[
'B',
'C'
]
];
| [reply] [d/l] |
|
Sorry, I realize now that putting those two sentences next to each other probably made my post a bit confusing. When I said "using map purely for its side-effects", I was referring to these lines in the OP's code:
map { printf "%15s to %-15s\n", $_->[0],$_->[1] } pairs @legs;
map { if ($info{$_->[0]}{state} ne $info{$_->[1]}{state}) {say "Mann
+Act!"} } pairs @legs;
map { $info{$_[0]}{"state"}=$_[1] } pairs mesh @trip, @states;
The return value of map is completely ignored, and instead actions are taken inside the code block that have side effects. These are the cases where I think a for statement modifier would be better.
On the other hand, the code that I showed, map {@arr[$_,$_+1]} 0..$#arr-1 does not make any modifications to the @arr, and I do use the return value - so this is a case where I would perfer map over for.
| [reply] [d/l] [select] |
|
you wrote: "I would have used is map"
{@arr[$_,$_+1]} 0..$#arr-1
Thanks! this is just the sort of thing I was seeking . . . new knowledge. I was unaware of $_+1 .
Also later you noted:
"The reason your creation of the %info hash isn't working in the original code is that you're not dereferencing the pairs."
Nice, I thought I was close to getting it to work, this did the trick.
As the lyric goes . . . I am hip to your timely tip!! Thannk again.
However, the while pare of the code below looks like Devilry I will have to spend more time with it to figure it out
local $_ = join "\n", @trip;
printf "%15s to %-15s\n", $1, $2 while /^(.*)\n(?=(.*))/gm;
| [reply] [d/l] [select] |
|
local $_ = join "\n", @trip;
printf "%15s to %-15s\n", $1, $2 while /^(.*)\n(?=(.*))/gm;
Let's remove the while for a moment and see what happens:
my $test = "Chicago
Saint Looey
Joplin
OKC
Amarillo
Gallup
Flagstaff
Winona
Kingman
Barstow
San Bernandino
LA" ;
if ( $test =~ /^(.*)\n(?=(.*))/gm ) {
printf "%15s to %-15s\n", $1, $2 ;
}
__END__
Chicago to Saint Looey
(.*) = Chicago
\n = enter
(?=pattern) = Look around assertion. It is used to look ahead after the enter without changing the position where the last match ended. This is done so that the last arrival location becomes the depart location in the next search. https://perldoc.perl.org/perlre.html#Extended-Patterns
(.*) = Saint Looey
g = is in this case used to change the position where the last match ended. If you leave it out, you get a never ending loop (it will match the first found text over and over again) edit:See https://perldoc.perl.org/perlrequick.html#More-matching
m = Treat string as multiple lines | [reply] [d/l] [select] |
|
{@arr[$_,$_+1]} 0..$#arr-1
Pardon me restating the obvious here.
There is no new functionality that I was unaware of. In this case $_ is an integer and you are just incrementing it. I was somehow originally thinking you where feeding the block a list of cities. This is still good stuff and I appreciate it.
I had started reading about the routines available in List::Utils and had dealing with the list values in my head. Looping over the indices may make more seance in this case.
| [reply] [d/l] |
Re: Fence vs. Posts
by NetWallah (Canon) on Jun 08, 2018 at 03:53 UTC
|
I think the idea/idiom for your City1 -> City2 is a "Graph", where the cities are Nodes(or vertices's), and the distance between them is an "Edge".
In your case, it is a simple directed linear graph that can be implemented using Graph, or its derivative Graph::Simple.
Memory fault -- brain fried
| [reply] |
Re: Fence vs. Posts
by QM (Parson) on Jun 08, 2018 at 09:57 UTC
|
I often have a list where I need to do adjacent pairs.
my @trip = ("Chicago", "Saint Looey", "Joplin", "OKC", "Amarillo", "
+Gallup",
"Flagstaff", "Winona", "Kingman", "Barstow",
+"San Bernandino", "LA" );
map { printf "%15s to %-15s\n", @trip[$_..$_+1] } 0..$#trip-1;
which emits:
Chicago to Saint Looey
Saint Looey to Joplin
Joplin to OKC
OKC to Amarillo
Amarillo to Gallup
Gallup to Flagstaff
Flagstaff to Winona
Winona to Kingman
Kingman to Barstow
Barstow to San Bernandino
San Bernandino to LA
If you did this often, you might want a pairs_overlapping function. Or, going with a generic version: subsequence(length=>3, step=>1, list=>\@trip);
a function that returns an arbitrary list of adjacent elements, with an arbitrary step size.
As to the next bit, I think several good solutions have been shown by others.
-QM
--
Quantum Mechanics: The dreams stuff is made of
| [reply] [d/l] [select] |
Re: Fence vs. Posts
by kcott (Archbishop) on Jun 08, 2018 at 09:05 UTC
|
G'day rodinski,
Here's a possible solution.
It doesn't require loading any modules.
In the spirit of an idiom, I've greatly simplified the data:
now you can see more idiom, less geography.
#!/usr/bin/env perl
use strict;
use warnings;
my @posts = qw{a b c d};
my %fence = (
$posts[0],
(map { $_ => $_ } @posts[1..$#posts-1]),
$posts[-1]
);
print "$_ to $fence{$_}\n" for @posts[0..$#posts-1];
my @extra = qw{w x y z};
my %info;
$info{$posts[$_]}{extra} = $extra[$_] for 0..$#posts;
print "$_~$info{$_}{extra}\n" for @posts;
Output:
a to b
b to c
c to d
a~w
b~x
c~y
d~z
| [reply] [d/l] [select] |
Re: Fence vs. Posts
by BrowserUk (Patriarch) on Jun 08, 2018 at 10:00 UTC
|
Seems like a lot of complicated and expensive solutions are being offered for a problem that is very simple.
NO duplicated arrays or mashed together strings and expensive regex iterations; just one line of standard perl:
#! perl -slw
use strict;
use Data::Dump qw[ pp ];
my @trip = ("Chicago", "Saint Looey", "Joplin", "OKC", "Amarillo", "
+Gallup", "Flagstaff", "Winona", "Kingman", "Barstow", "San Bernandino
+", "LA" );
printf "%15s to %-15s\n", @trip[ $_-1 .. $_ ] for 1 .. $#trip;
__END__
C:\test>junk38
Chicago to Saint Looey
Saint Looey to Joplin
Joplin to OKC
OKC to Amarillo
Amarillo to Gallup
Gallup to Flagstaff
Flagstaff to Winona
Winona to Kingman
Kingman to Barstow
Barstow to San Bernandino
San Bernandino to LA
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.
In the absence of evidence, opinion is indistinguishable from prejudice.
Suck that fhit
| [reply] [d/l] |
Re: Fence vs. Posts
by tybalt89 (Monsignor) on Jun 08, 2018 at 02:45 UTC
|
#!/usr/bin/perl
# https://perlmonks.org/?node_id=1216162
use strict;
use warnings;
my @trip = ("Chicago", "Saint Looey", "Joplin", "OKC", "Amarillo", "
+Gallup",
"Flagstaff", "Winona", "Kingman", "Barstow", "San Bernandi
+no", "LA" );
local $_ = join "\n", @trip;
printf "%15s to %-15s\n", $1, $2 while /^(.*)\n(?=(.*))/gm;
my @states = ("IL", "MO", "MO", "OK", "TX", "TX",
"AZ", "AZ", "AZ", "CA", "CA", "CA" );
my %info = map { $trip[$_], { state => $states[$_] } } 0..$#trip;
use Data::Dump 'pp'; print pp \%info;
print "\n", $info{Joplin}{state}, "\n";
Outputs:
Chicago to Saint Looey
Saint Looey to Joplin
Joplin to OKC
OKC to Amarillo
Amarillo to Gallup
Gallup to Flagstaff
Flagstaff to Winona
Winona to Kingman
Kingman to Barstow
Barstow to San Bernandino
San Bernandino to LA
{
"Amarillo" => { state => "TX" },
"Barstow" => { state => "CA" },
"Chicago" => { state => "IL" },
"Flagstaff" => { state => "AZ" },
"Gallup" => { state => "TX" },
"Joplin" => { state => "MO" },
"Kingman" => { state => "AZ" },
"LA" => { state => "CA" },
"OKC" => { state => "OK" },
"Saint Looey" => { state => "MO" },
"San Bernandino" => { state => "CA" },
"Winona" => { state => "AZ" },
}
MO
| [reply] [d/l] [select] |
|
if you only have a hammer, everything looks like a nail
| [reply] |
Re: Fence vs. Posts
by hdb (Monsignor) on Jun 08, 2018 at 11:41 UTC
|
Here is another complicated "perlish" solution to avoid simple C-style loops as proposed above (and which is the most sensible approach IMHO), this time using closures:
se warnings;
use strict;
sub create_scheduler {
my $list = shift;
return sub {
my $i = shift;
return () if $i > $#$list - 1 or $i < 0;
return ( $list->[$i], $list->[$i+1] );
}
};
my $leg = create_scheduler [ "Chicago", "Saint Looey", "Joplin", "OKC"
+, "Amarillo", "Gallup", "Flagstaff", "Winona", "Kingman", "Barstow",
+"San Bernandino", "LA" ];
my $i = 0;
while( my @pair = $leg->($i++) ) {
print join( " to ", @pair ), "\n";
}
You could also "enclose" the index into the closure if you want to create a one-time iterator over your array.
| [reply] [d/l] |
|
|