How do I find the Nth index of array that is (whatever)

U

usenet

Suppose I have an array like this:

my @array = ('foo', '', 'bar', '', '', '', 'baz', '');

and I want to find the index value of the third undefined element. I
can do something really ugly like this:

my $search_for = 3; #find index of third undefined element

my $undefined = 0;
foreach my $index(0..$#array) {
$undefined++ unless $array[$index];
if ($undefined == $search_for) {
print "Index of undefined element #$search_for is $index\n";
next;
}
}

but I HATE THAT CODE. It's ugly. It's inelegant. It's an offense to
human dignity. It increases global warming. Is there a better (and
more environmentally safe) technique?
 
J

John W. Krahn

Suppose I have an array like this:

my @array = ('foo', '', 'bar', '', '', '', 'baz', '');

and I want to find the index value of the third undefined element.

Your array doesn't have ANY undefined elements.


John
 
M

Mirco Wahab

Thus spoke Michele Dondi (on 2006-09-20 11:37):
my $search_for = 3; #find index of third undefined element

print +(grep !$array[$_], 0..$#array)[2], "\n";

This looks nice (I didn't think of pulling all
false elements to a list). Another approach would
be the 'naive' map:

...
my $loc = $search_for;
my ($index) = map !$array[$_] && !--$loc ?$_:(), 1..@array;

print "Index of $search_for. 'false' element is $index\n";
...


Regards

Mirco
 
M

Mirco Wahab

Thus spoke Michele Dondi (on 2006-09-20 11:37):

print +(grep !$array[$_], 0..$#array)[2], "\n";

This looks nice (I didn't think of pulling all
false elements to a list). Another approach would
be the 'naive' map:

...
$loc = $search_for;
($index) = map !$array[$_] && !--$loc ? $_:(), 0..$#array;
...

or
...
$loc = $search_for;
print +(map !$array[$_] && !--$loc ? $_ : (), 0..$#array), "\n";
...

(but your's 's more fancier somehow:)

Regards

Mirco
 
M

Mumia W.

Suppose I have an array like this:

my @array = ('foo', '', 'bar', '', '', '', 'baz', '');

and I want to find the index value of the third undefined element. I
can do something really ugly like this:

my $search_for = 3; #find index of third undefined element

my $undefined = 0;
foreach my $index(0..$#array) {
$undefined++ unless $array[$index];
if ($undefined == $search_for) {
print "Index of undefined element #$search_for is $index\n";
next;
}
}

but I HATE THAT CODE. It's ugly. It's inelegant. It's an offense to
human dignity. It increases global warming. Is there a better (and
more environmentally safe) technique?

Would it be any less ugly to you if you wrapped it up in a sub?

use strict;
use warnings;
use Alias qw(alias);
my @array = ('foo', '', 'bar', '', '', '', 'baz', '');
my $find_empty = nth_empty_mapper(\@array);
my $search_for = 3; # find index of third undefined element
my $search_for_pf = ctrpostfix($search_for);

print "Index of $search_for_pf undefined element"
. " is $find_empty->{$search_for}\n";

sub nth_empty_mapper {
our @array;
alias array => shift();
my %mapper;
my $empty = 0;
foreach my $count (0..$#array) {
unless ($array[$count]) {
$mapper{$empty++} = $count;
}
}
\%mapper;
}

sub ctrpostfix {
my $num = shift();
$num == 1 ? "${num}st" :
$num == 2 ? "${num}nd" :
$num == 3 ? "${num}rd" :
"${num}th" ;
}


Wouldn't the code in ctrpostfix be uglier if it were not wrapped up in a
sub?
 
B

Ben Morrow

Quoth (e-mail address removed):
Suppose I have an array like this:

my @array = ('foo', '', 'bar', '', '', '', 'baz', '');

and I want to find the index value of the third undefined element. I
can do something really ugly like this:

my $search_for = 3; #find index of third undefined element

my $undefined = 0;
foreach my $index(0..$#array) {
$undefined++ unless $array[$index];
if ($undefined == $search_for) {
print "Index of undefined element #$search_for is $index\n";
next;
}
}

but I HATE THAT CODE. It's ugly. It's inelegant. It's an offense to
human dignity. It increases global warming. Is there a better (and
more environmentally safe) technique?

use List::MoreUtils qw/firstidx/;

my @array = (foo => '', bar => '', '', '', baz => '');
my $search_for = 3;

local ($\, $,) = ("\n", ' ');
my $n = 0;
print "Index of false element #$search_for is",
firstidx { !$_ && ++$n == $search_for }
@array;

Ben

P.S. #$search_for really threw me for a second... I don't think there's
anything to be done about that, though. A highlighting editor would of
course have helped :).
 
T

Ted Zlatanov

Suppose I have an array like this:

my @array = ('foo', '', 'bar', '', '', '', 'baz', '');

and I want to find the index value of the third undefined element.

This uses an xor, then adds up the value at $offset until the sum
reaches 3. It can probably be improved and shortened.

Ted

#!/usr/bin/perl

use warnings;
use strict;
use Data::Dumper;

my @array = ('foo', '', 'bar', '', '', '', 'baz', '');
my @barray = map { $_ xor 1 } @array;

my $offset = 0;
my $sum = 0;

foreach (@barray)
{
$sum += $_;
if ($sum >=3)
{
print "Found it, offset is $offset\n";
last;
}
$offset++;
}
 
U

usenet

Michele said:
print +(grep !$array[$_], 0..$#array)[2], "\n";

That's really cool. But I don't get the reason for the "+" - wuzzat?
It doesn't seem to matter with/without the + in my script.

FWIW, this question relates to a little fun script I cobbled up to
generate all 960 possible back-row variations under Fischer Random
Chess (aka Chess960 or FRC):
http://en.wikipedia.org/wiki/Chess960
Under FRC, any backrow is valid provided the bishops are on opposite
colors and the king is between the two rooks (these constraints allow
960 possible unique configurations, and make it difficult to gain an
advantage by memorizing the "opening book" of initial
moves/countermoves). You can create a backrow with dice like this:
- roll d4 and place a bishop on the Nth white square
- roll d4 and place a bishop on the Nth black square
- roll d6 and place the queen on the Nth empty square
- roll d5 and place a knight on the Nth empty square
- roll d4 and place a knight on the Nth empty square
- Place rook-king-rook (in that order) on three remaining emptys
(you can simulate d4 and d5 with a six-sided die (d6) but re-roll
too-high values).

I came up with this approach, which does something fancy with the
knights to prevent duplicate rows:

#!/usr/bin/perl
use strict; use warnings;
use List::Util qw{shuffle}; #if you want to randomize list

my @frc; # All 960 backrow variations of Fischer Random Chess

foreach my $b1 (0..3) { # black bishop (d4)
foreach my $b2 (0..3) { # white bishop (d4)
foreach my $q (0..5) { # queen (d6)
foreach my $n1 (0..3) { # knight (d4!)
foreach my $n2 ( ($n1+1)..4 ) { # the other knight
my @row;
@row[ $b1*2 , $b2*2 + 1 ] = qw{B B};
$row[ (grep !$row[$_], 0..7)[$q] ] = 'Q';
@row[ (grep !$row[$_], 0..7)[$n1,$n2]] = qw{N N};
@row[ (grep !$row[$_], 0..7) ] = qw{R K R};
push @frc, join('', @row);
}
}
}
}
}
print $_ + 1, "\t$frc[$_]\n" for 0 ? shuffle 0..$#frc : 0..$#frc;
#if nonzero, list is shuffled ---^

__END__

It was a fun little project. It is one of the rare times I have used
array indexes in Perl.

I showed this code to another programmer (more skilled than I) and he
suggested a different approach which might work more cleanly than the
greps; I'll play around with his idea (just for fun) and see which I
like better. If I like the other approach, I'll post it also (just for
fun).
 
M

Mirco Wahab

Thus spoke Michele Dondi (on 2006-09-21 12:01):
You know,

map f($_) ? $_ : (), @list;

is just the same as

grep f($_), @list;

Yes, that should be ... :-((
thus even with your approach map() is not necessary
and slightly clumsy:

It was somehow a 'fast shot' - but did (at last) work
(after canceling the initial [1..N] and reposting it).

But the (your) 'indexing+grep' combination:

my $loc = nth_occurence_of_false;
my ($index) = grep !$array[$_] && !--$loc, 0..$#array;

looks to me like the most beautiful,
maybe 'idiomatic' solution.

Regards & thanks

Mirco
 
T

Tad McClellan

Michele Dondi said:
Michele said:
print +(grep !$array[$_], 0..$#array)[2], "\n";

That's really cool. But I don't get the reason for the "+" - wuzzat?
It' like having another pair of parens around the whole
print() args,


Which everybody would understand immediately.

but it's lighter IMHO.


But it introduces "startle factor" that would not be there if you
instead chose to use parenthesis around this function's argument list.

It is lighter for development, but much heavier for maintenance...
 
T

Ted Zlatanov

Interesting approach; but...


(i) Your xor amounts to a C<!>.

Ah, I should have seen that.
(ii) You have *two* loops: one implicit in the map() and this explicit
one, whereas one would suffice:

#!/usr/bin/perl -l

use warnings;
use strict;
use Data::Dumper;

my @array = ('foo', '', 'bar', '', '', '', 'baz', '');

my $cnt;

for (0..$#array) {
$cnt += !$array[$_];
print, last if $cnt == 3;
}

Yes, definitely better than my version :) Thanks for taking the time
to improve it.
Of course if put into a sub it can become quite different:

sub findnth {
my $cnt=shift;
$cnt -= !$_[$_] or return $_ for 0..$#_;
}

print findnth 3, @array;

Cool. I think that's the best solution so far. But it should return
undef (IMO) if it doesn't find the nth false value, so just "return
undef" at the end will suffice.

Ted
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,537
Members
45,021
Latest member
AkilahJaim

Latest Threads

Top