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.
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... | [reply] [d/l] |
|
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.
| [reply] [d/l] |
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. | [reply] [d/l] |
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,
| [reply] [d/l] [select] |
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.
| [reply] [d/l] [select] |
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
| [reply] [d/l] [select] |
Re: Manipulating Array Indexes
by BillKSmith (Monsignor) on Sep 08, 2020 at 03:36 UTC
|
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
| [reply] [d/l] [select] |
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: <%-{-{-{-<
| [reply] [d/l] [select] |
Re: Manipulating Array Indexes
by tybalt89 (Monsignor) on Sep 07, 2020 at 22:16 UTC
|
#!/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,
| [reply] [d/l] [select] |
Re: Manipulating Array Indexes
by tybalt89 (Monsignor) on Sep 08, 2020 at 13: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 $want = 0;
for ( @data )
{
$want-- > 0 and print "$_\n";
$_ == $target and $want = 3;
}
| [reply] [d/l] |
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. | [reply] [d/l] [select] |
|
| [reply] |
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. | [reply] [d/l] [select] |
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
| [reply] [d/l] [select] |
|
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
| [reply] [d/l] [select] |
|
#!/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";
| [reply] [d/l] |
|
|
|