Beefy Boxes and Bandwidth Generously Provided by pair Networks
Syntactic Confectionery Delight
 
PerlMonks  

Fence vs. Posts

by rodinski (Novice)
on Jun 08, 2018 at 02:06 UTC ( [id://1216162]=perlquestion: print w/replies, xml ) Need Help??

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

Replies are listed 'Best First'.
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" }, }
      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' ] ];
      Bill

        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.

      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;

        Hi rodinski,

        I'll try to explain that last one:

        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

        Now that I have had time to look at this:

        {@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.

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

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

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

    — Ken

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.
    "Science is about questioning the status quo. Questioning authority". The enemy of (IT) success is complexity.
    In the absence of evidence, opinion is indistinguishable from prejudice. Suck that fhit
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
      if you only have a hammer, everything looks like a nail
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.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://1216162]
Approved by Athanasius
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others avoiding work at the Monastery: (3)
As of 2024-04-19 23:32 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found