Beefy Boxes and Bandwidth Generously Provided by pair Networks
No such thing as a small change
 
PerlMonks  

Manipulating Array Indexes

by TJ777 (Initiate)
on Sep 07, 2020 at 15:57 UTC ( [id://11121442]=perlquestion: print w/replies, xml ) Need Help??

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

Hello Perl Monks...I come today on bended knee humbly begging for any wisdom you can provide...

I am trying to find elements in an array that are positioned 1, 2 and 3 places behind my 'target' element. So if my list is: 1,2,3,4,5,6,7,8,9,8,7,6,5,4,3,2,1, and my 'target' number was 5, I am looking for a way to return elements 6,7,8, and 4,3,2, I need to get the three elements after every time a 5 appears in the list. In actual practice the list will not be sequential and will be somewhat longer, but nowhere near consuming all my memory kinda long, maybe 200-300 items max.

I can get the index of my target number easily enough, however I am wondering if there is a way I can manipulate the index values to 'look ahead'. I've been trying many variations of:

$idxvalue = (pop(@indexes)+1); print "$lines[$indexes[$idxvalue]]\n";

but I am having a hard time finding something that works. It's odd because when I print @indexes I get a list of the index numbers as expected, but then at times I am getting the actual item from the list, rather than the index position number I wanted. Please forgive my ignorance...I am trying my best....which admittedly is not very good :-(

I'm using Strawberry Perl on Windows 10 in case it matters.... Thank you in advance to any who can offer suggestions or advice! TJ.

Replies are listed 'Best First'.
Re: Manipulating Array Indexes
by Corion (Patriarch) on Sep 07, 2020 at 16:16 UTC

    If you want array elements, why do you use pop ? This modifies the array in question...

    Maybe you can show us your code that "almost" works and tell us how it fails for you.

    The simplest approach to get the next three items after a given index I can think of is:

    use feature 'signatures'; sub next_three_items( $array_ref, $index ) { return $array_ref->[$index+1], $array_ref->[$index+2], $array_ref->[$index+3], ; }

    ... but maybe I have not understood your requirements well...

      I noticed the use of pop, also. It's also taking elements from the RHS of the list which may not be intuitive in this application. The index of elements to the left of the tail of the array would not be affected, but the overall size of the array certainly is so any number derived from it will certainly change.
Re: Manipulating Array Indexes
by roboticus (Chancellor) on Sep 07, 2020 at 16:19 UTC

    TJ777:

    Yes, you can access other elements in the array by doing math with the index. There are lots of ways you could do it, but I'll demonstrate with two: you can do it as a two pass operation to first find all the targets, and then loop over your target list to print your results; or you could try to do it in a single pass over the array.

    use strict; use warnings; my @list = (1,2,3,4,5,6,7,8,9,8,7,6,5,4,3,2,1); my @indexes; ### Method 1: two passes over the list ### # Find all the fives for my $idx (0 .. $#list) { push @indexes, $idx if $list[$idx] == 5; } # For each five we find, show it (position and value) and # the three following values for my $idx (@indexes) { print "list[$idx]=$list[$idx]: "; for my $t (1 .. 3) { print $list[$idx + $t], " "; } print "\n"; } ### Method 2: one pass over the list ### for my $idx (0 .. $#list) { # Skip printing unless we're at a five next unless $list[$idx] == 5; print "list[$idx]=$list[$idx]: ", # use a list slice to get the elements join(", ", @list[$idx+1 .. $idx+3]), "\n"; }

    It looks like you're trying to use the first method, but you didn't show enough code for me to figure you what difficulty you're having. The second method can work well also, but can be a bit more confusing at first.

    Other things that can make your life tricky are:

    • modifying the list while you're iterating over it, and
    • modifying your index variable while you're iterating.

    So try not to do either of those.

    ...roboticus

    When your only tool is a hammer, all problems look like your thumb.

Re: Manipulating Array Indexes
by Athanasius (Archbishop) on Sep 07, 2020 at 16:30 UTC

    Hello TJ777, and welcome to the Monastery!

    The PerlX::Window module by the Monastery’s own tobyink might provide you with another useful approach:

    use strict; use warnings; use PerlX::Window; my @array = (1, 2, 3, 4, 5, 6, 7, 8, 9, 8, 7, 6, 5, 4, 3, 2, 1); while (defined window @array, 7) { if ($window[3] == 5) { print @window[4 .. 6], ', ', reverse(@window[0 .. 2]), "\n"; } }

    Output:

    2:27 >perl 2054_SoPW.pl 678, 432 432, 678 2:27 >

    Hope that helps,

    Athanasius <°(((><contra mundum Iustus alius egestas vitae, eros Piratica,

Re: Manipulating Array Indexes
by kcott (Archbishop) on Sep 08, 2020 at 03:55 UTC

    G'day TJ,

    Welcome to the Monastery.

    There are some edge cases you didn't consider (or, at least, didn't tell us about) so here's a couple of options.

    I've extended your posted input to show: 5 at the start; 5 in the set of 3 returned; and, running out of elements at the end.

    $ perl -E ' my @x = (5,1,2,3,4,5,6,7,8,9,8,7,6,5,4,3,2,1,5,9,8,5,7,5,5,5); say "|@$_|" for map [@x[$_+1 .. $_+($#x-$_ >= 3 ? 3 : $#x-$_)]], grep $x[$_] == 5, 0..$#x; ' |1 2 3| |6 7 8| |4 3 2| |9 8 5| |7 5 5| |5 5| |5| ||

    If, on the other hand, you don't want sets of less than 3 returned, you can simplify the map and don't read the last 3 indices.

    $ perl -E ' my @x = (5,1,2,3,4,5,6,7,8,9,8,7,6,5,4,3,2,1,5,9,8,5,7,5,5,5); say "|@$_|" for map [@x[$_+1 .. $_+3]], grep $x[$_] == 5, 0..$#x-3; ' |1 2 3| |6 7 8| |4 3 2| |9 8 5| |7 5 5|

    Both of those solutions work with input such as (), (5), and (1,2,3,4). Although you say that you're expecting long arrays, it's always a good idea to add a sanity check for those times when your process is handed something unexpected, like one of those short lists.

    — Ken

Re: Manipulating Array Indexes
by GrandFather (Saint) on Sep 07, 2020 at 23:08 UTC

    If you end up needing to deal with multiple targets then it may be useful to have an "address map" to the elements. Consider:

    use strict; use warnings; my @list = (1,2,3,4,5,6,7,8,9,8,7,6,5,4,3,2,1); my @targets = (5, 8, 42); my %addresses; push @{$addresses{$list[$_]}}, $_ for 0 .. $#list; for my $target (@targets) { if (! exists $addresses{$target}) { print "There are no entries for $target\n"; next; } print "Target $target\n"; my @targetList = @{$addresses{$target}}; for my $address (@targetList) { my $last = $address + 3 < $#list ? $address +3 : $#list; print ' ', join ', ', @list[$address + 1 .. $last]; print "\n"; } }

    Prints:

    Target 5 6, 7, 8 4, 3, 2 Target 8 9, 8, 7 7, 6, 5 There are no entries for 42

    Update: fixed my interpretation of "behind"!

    Optimising for fewest key strokes only makes sense transmitting to Pluto or beyond
Re: Manipulating Array Indexes
by BillKSmith (Monsignor) on Sep 08, 2020 at 03:36 UTC
    I recommend using indexes of List::MoreUtils to get the indexes and an array slice (Slices) to get the values.
    use strict; use warnings; use List::MoreUtils qw(indexes); my @list = qw(1 2 3 4 5 6 7 8 9 8 7 6 5 4 3 2 1); my $target = 5; my @indexes = indexes {$_ == $target} @list; my @values = map {[@list[($_+1)..($_+3)]]} @indexes; print "@$_\n" foreach @values;

    OUTPUT:

    6 7 8 4 3 2
    Bill
Re: Manipulating Array Indexes
by AnomalousMonk (Archbishop) on Sep 07, 2020 at 22:29 UTC

    Just as a matter of idle curiosity, what do you require to be returned from the following sequences, assuming 5 is the target value?
        (1, 2, 3, 4, 5)
        (1, 2, 3, 4, 5, 6)
        (1, 2, 3, 4, 5, 6, 7)


    Give a man a fish:  <%-{-{-{-<

Re: Manipulating Array Indexes
by tybalt89 (Monsignor) on Sep 07, 2020 at 22:16 UTC

    What do you need an array for? Just use the string of data values you showed us.

    #!/usr/bin/perl use strict; # https://perlmonks.org/?node_id=11121442 use warnings; my $data = '1,2,3,4,5,6,7,8,9,8,7,6,5,4,3,2,1,'; my $target = 5; print $data =~ /\b$target,((?:\d+,){3})/g, "\n";

    Outputs:

    6,7,8,4,3,2,
Re: Manipulating Array Indexes
by tybalt89 (Monsignor) on Sep 08, 2020 at 13:31 UTC

    No need to bother with indexes...

    #!/usr/bin/perl use strict; # https://perlmonks.org/?node_id=11121442 use warnings; my @data = qw( 1 2 3 4 5 6 7 8 9 8 7 6 5 4 3 2 1 ); my $target = 5; my $want = 0; for ( @data ) { $want-- > 0 and print "$_\n"; $_ == $target and $want = 3; }
Re: Manipulating Array Indexes
by perlfan (Vicar) on Sep 07, 2020 at 16:31 UTC
    This is easy enough if you work it out without the complication of getting fancy with a one-liner. And I am not sure a "look ahead*" is useful or just complicates what you wish to do.Algorithmically, there are 2 ways to approach this.

    1. scan array to the end and mark all indexes where there is a 5; then easily calculate the "range" - don't forget these arrays are 0 indexed

    To show what I mean, just scan and collect the indexes where the value is 5:

    my @fives_idx = (); my $i = 0; foreach my $e (@lines) { if (5 == $e) { push @fives_idx, $i; } ++$i; }

    Once you have the indexes that contain 5, you can apply the concept of array slices to extract the values you're wanting. Note, this is not the same as splice, which can be destructive on your arrays (but potentially useful here nonetheless).

    2. scan and store previous elements along the way - there are various ways to do this I can think of that don't require book keeping variables - no example here, but you could use a hash of array references to "scoop" up elementes leading up to each 5, for example.

    * Update: I think I know why you want to "look ahead". As it was pointed out by Corion, don't use pop. Not only is this destructive to the array, but it also screws up your view of the list. pop takes the last element of the array, so there is no looking ahead - only behind. If you wish to destroy the array as you process it, use shift. But be warned, while pop doesn't re-index elements (but affects greatest index), shift will cause all elements' indexes to decrease by 1. So, don't use a destructive operation (pop, shift. splice) to iterate or partition your array until you have it working non-destructively and understand why it is working.

      Thank you all so very much...I am really blown away by both the speed and quality of the replies. You have given me a lot to work with/think about! Thanks to everyone who shared their thoughts and ideas! TJ.

Re: Manipulating Array Indexes
by jcb (Parson) on Sep 07, 2020 at 23:10 UTC

    If chewing up the input list (or a copy of it) is acceptable, here is a solution:

    #!/usr/bin/perl use strict; use warnings; my @List = (1,2,3,4,5,6,7,8,9,8,7,6,5,4,3,2,1); my $Tag = 5; my @Result = (); while (@List) { push @Result, splice @List, 0, 3 if shift @List == $Ta +g } use Data::Dumper; print Data::Dumper->new([\@Result], [qw[Result]])->Indent(0)->Dump,"\n +"; __END__

    Line 11 is the important part. As long as items remain in @List the loop iterates, shifting one item off the left edge of @List and checking if it is the $Tag value (5 in this example). If so, the next three elements are removed from @List and added to the @Result array.

Re: Manipulating Array Indexes
by tybalt89 (Monsignor) on Sep 08, 2020 at 00:31 UTC
    #!/usr/bin/perl use strict; # https://perlmonks.org/?node_id=11121442 use warnings; my @data = qw( 1 2 3 4 5 6 7 8 9 8 7 6 5 4 3 2 1 ); my $target = 5; my @answers = map { $data[$_] == $target ? @data[$_ + 1 .. $_ + 3] : ( +) } 0 .. $#data; print "@answers\n";

    Outputs:

    6 7 8 4 3 2

      Try it with: my @data = qw( 5 );.

      Prints:

      Use of uninitialized value $answers[0] in join or string at 11121473.p +l line 11. Use of uninitialized value $answers[1] in join or string at 11121473.p +l line 11. Use of uninitialized value $answers[2] in join or string at 11121473.p +l line 11.

      Optimising for fewest key strokes only makes sense transmitting to Pluto or beyond

        A) There was no test case for that indicating it is not possible for his data set.
        B) This is one thing I really like about perl - it's easily fixed with just two words and a comma :)

        #!/usr/bin/perl use strict; # https://perlmonks.org/?node_id=11121442 use warnings; my @data = qw( 5 ); my $target = 5; my @answers = grep defined, map { $data[$_] == $target ? @data[$_ + 1 +.. $_ + 3] : () } 0 .. $#data; print "@answers\n";

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others lurking in the Monastery: (3)
As of 2024-04-19 01:03 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found