Re: How to make references more like aliases?
by blokhead (Monsignor) on Sep 28, 2004 at 00:46 UTC
|
When you return a list and assign it to an array, the list elements are copied across the assignment operator. So any solution involving:
while ( my @group = $next->() )
is not going to preserve aliasing. @group is going to get copies of whatever was used in the return statement of the iterator.
Instead, you'll have to return an array reference. As for getting the aliases, the only way to get aliases is through argument passing. An idiomatic way is with:
my $arrayref_of_aliases = sub { \@_ }->( $stuff, $to, $alias );
In terms of your code, you change the return line of the iterator to:
return sub { \@_ }->( @$list[ $start .. $stop ] );
Then it works as you'd expect (with the appropriate changes in the calling code's while loop).
| [reply] [d/l] [select] |
|
|
| [reply] [d/l] [select] |
Re: How to make references more like aliases?
by ihb (Deacon) on Sep 28, 2004 at 03:25 UTC
|
{
package Array::Grouped;
sub TIEARRAY { bless { n => $_[1], array => $_[2] } => $_[0] }
sub FETCHSIZE { @{$_[0]{array}} / $_[0]{n} }
sub FETCH {
my $self = shift;
my ($i) = @_;
my $n = $self->{n};
return sub { \@_ }->(@{$self->{array}}[$i*$n .. $i*$n + $n
+ - 1]);
}
}
my @bar = 0 .. 9;
tie my @foo, Array::Grouped::, 2, \@bar;
for (@foo) {
$_->[0] = 'x';
}
print @bar; # x1x3x5x7x9
### Update:
sub by_groups_of { tie my @foo, Array::Grouped::, @_ }
my $foo = by_groups_of(2, \@bar);
for (@$foo) {
$_->[0] = 'y';
}
print @bar; # y1y3y5y7y9
The advantage with this approach is it's memory efficiency and ease of use. If you just want to know e.g. the last or a specific interval you can do that using regular slices. It doesn't consume a lot of memory since it uses the original array and produces the groups when needed.
An overall problem with both this solution and your solution is what to do when the original array is modified, and in particular, spliced upon. Since the problem may be easily solved by hand and you then can explicitly define how you want to handle it, I'm afraid that an abstraction soon will end up with so many options and flags (or limitations) you'd end up with something more complicated than the hand-rolled solution and it's a loss using it. "Over-engineered" comes to mind. That's not to say there are no cases where an abstraction like this is suited.
Personally I find this tie solution to be overkill for everyday use and I would most likely either use my simple &group (see Re: loop thru 3-element array?) or manually set up a for (to handle edge cases). My &group has proven to cover my needs well while being extremely simple. Actually, it's sub pair { group(2, @_) } I most often use and that's when I want to use an each analogy for lists.
Update: Added a wrapper subroutine to show it's just as easy to use as of the other functions in this thread.
ihb
Read argumentation in its context! | [reply] [d/l] [select] |
|
|
ihb,
I had thought of a tied solution myself, but it doesn't really fall into the user-friendly category. You have to tie it each time you want to have a different number of elements per loop. As far as the splice problem - I originally copied the array, which is what lead to this post. I mostly handled it since each iteration I check to see if I am falling over the edge - the one case where it could still bite me is if the starting position is already in the abyss. That is easily solved with :
return () if $done || $pos > $#$list;
| [reply] [d/l] |
|
|
it doesn't really fall into the user-friendly category
By adding a simple wrapper subroutine it gets just as user-friendly as the other functions in this thread. See update.
As far as the splice problem / ... / I mostly handled it since each iteration I check to see if I am falling over the edge
There are other problems as well. You need to consider whether or not you want unshifts to effect your iterator (after you've done a couple of iterations); compare with foreach's behaviour. What's best here may very well depend on the situation. One solution is to just document that you may not do any size-changing modifications of the original array while using the iterator.
ihb
Read argumentation in its context!
| [reply] [d/l] [select] |
Re: How to make references more like aliases?
by exussum0 (Vicar) on Sep 28, 2004 at 00:33 UTC
|
xmath is working on something that may be up your alley. Check his scratchpad for a note on his module Data::Alias.
----
Then B.I. said, "Hov' remind yourself
nobody built like you, you designed yourself"
| [reply] |
|
|
sporty,
Thanks. I am well aware of xmath's work and am quite impressed by it. It falls into the "scary hack" category IMO. It requires 5.8.1 and doesn't work on Win32 without Cygwin.
| [reply] |
Re: How to make references more like aliases?
by Prior Nacre V (Hermit) on Sep 28, 2004 at 03:57 UTC
|
I didn't really have a problem with the FUGLY line; however, I did think the closure was doing more than necessary and I generally don't like passing whole arrays, so here's yet another variation on this theme.
I have noted what appears to be a bug (but it may be the intended behaviour).
use strict;
use warnings;
my @array = 0 .. 30;
my $rc_next = by_groups_of( 3, [ \(@array) ] );
while ( my $ra_group = $rc_next->() ) {
last unless @$ra_group;
${$ra_group->[1]} = 'foo';
}
print "$_\n" for @array;
sub by_groups_of {
my ($by, $ra_list) = @_;
$by ||= 0;
$ra_list ||= [];
my $rc_return = sub { [] };
if ($by && $by !~ /\D/ && @$ra_list)
{
my $pos = 0;
my $done;
$rc_return = sub {
return [] if $done;
my $start = $pos;
my $stop = $pos + $by - 1 > $#{$ra_list} ? $#{$ra_list} :
+$pos + $by - 1;
#$pos += $by - 1; # ?? bug ?? - changed to "$pos += $by;
+"
$pos += $by;
$done = 1 if $stop == $#{$ra_list};
return [ @{$ra_list}[$start .. $stop] ];
};
}
return $rc_return;
}
| [reply] [d/l] |
|
|
Prior Nacre V,
I generally don't like passing whole arrays
Neither do I. But what you did is likely even more work than passing the whole array. Your code is equivalent to the following:
- Make a reference to every element in the array
- Make a copy for the anonymous array
- Pass the reference to the anonymous array
To avoid making copies and only passing a reference, that should have been by_groups_of( 3, \@array ). Additionally, an iterator should stop when it is finished. In your code, you have to explicitly force it to abandon the infinite loop.
I do appreciate the reply and finding the fencepost bug. See this node for the latest version.
| [reply] |
|
|
by_groups_of( 3, \@array );
by_groups_of( 3, [ \(@array) ] );
The closure is called multiple times - your code does more here:
return sub { \@_ }->( @$list[ $start .. $stop ] );
return [ @{$ra_list}[$start .. $stop] ];
I benchmarked on a P3/Win98 system using both Cygwin/Perl5.6 and Msys/Perl5.8. I got comparable results for both versions (no discernible speed differences). These results were repeated when volume testing with array sizes between 100,000 and 500,000.
At the end of the day, either will do nicely :-)
| [reply] [d/l] [select] |
|
|
|
|
Re: How to make references more like aliases?
by Zed_Lopez (Chaplain) on Sep 28, 2004 at 05:25 UTC
|
Here's another tied variable solution. I didn't implement most of the tied array methods; it's ill-defined what push, pop, etc. should do in this case.
All told, I think I prefer your solution after blokhead's suggestions.
package Array::AliasRange;
sub TIEARRAY {
my ($class, $array, $offset, $length) = @_;
bless {
array => $array,
offset => $offset,
length => $length,
}, $_[0];
}
sub _real_index {
my ($self, $index) = @_;
if ($index >= 0) {
return undef if ($index + $self->{offset}) >= ($self->{offset} + $
+self->{length});
return $index + $self->{offset};
} else {
return undef if (scalar @{$self->{array}} - ($index + $self->{offs
+et})) < 0;
return scalar @{$self->{array}} - ($index + $self->{offset});
}
}
sub FETCH {
my ($self, $index) = @_;
my $real_index = _real_index($self, $index);
return defined $real_index ? $self->{array}[$real_index] : undef;
}
sub STORE {
my ($self, $index, $newval) = @_;
my $real_index = _real_index($self, $index);
return defined $real_index ? ($self->{array}[$real_index] = $newval)
+ : undef;
}
sub FETCHSIZE {
my $self = shift;
return $self->{length};
}
package main;
sub by_groups_of {
my ($by, $arrayref) = @_;
return sub { () } if ! $by || $by =~ /\D/ || ! @$arrayref;
my $offset = 0;
return sub {
return () if $offset >= scalar @$arrayref;
my @new_array;
my $length = ($offset + $by >= scalar @$arrayref) ? (scalar @$arra
+yref - $offset) : $by;
tie @new_array, "Array::AliasRange", $arrayref, $offset, $length;
$offset += $by;
return \@new_array;
}
}
my @array = 0 .. 10;
my $next = by_groups_of( 3, \@array );
while ( my $group = $next->() ) {
$group->[1] = 'foo';
}
print join ' ', @array, "\n";
| [reply] [d/l] |
Re: How to make references more like aliases?
by rir (Vicar) on Sep 28, 2004 at 15:43 UTC
|
Update: I went off on a different problem.
This is more to the point.
To batch through an array I'd collect indexes so there
is no doubt but that I am dealing with the actual elements
in the loop body.
Like:
my $i;
my @ar = 0 .. 10;
($get_idx_grp, $fin_idx_grp) = mk_idx_funcs( 4, \$i, \@ar );
for ( $i = 0; $i < @ar; ++$i) {
my @idx = &$get_idx_grp;
$ar[ $idx[0] ] = "YYY";
&$fin_idx_grp;
}
sub mk_idx_funcs { # XXX bad name
my ( $grp_size, $iter, $array ) = @_;
my ( @group, $group_end );
my $first_call = 1;
return ( sub {
if ($first_call) {
$first_call = 0;
$group_end = $$iter + $grp_size;
}
if ( $$iter < $group_end and $$iter < @$array ) {
push @group, $$iter;
no warnings;
next unless @group==$grp_size or $$iter==@$array-1;
}
return @group;
},
sub {
$group_end += @group;
@group = ();
return;
}
);
}
Original post follows
I would be interested in the aliasing most likely
to avoid changing the identifiers in a loop body. Your
code does not do this, so I do not understand what you
are trying to achieve.
This seems simpler:
#!/usr/bin/perl
use strict;
use warnings;
my @array = 0 .. 30;
my $interval = make_interval();
for my $val ( @array ) {
&$interval( 4);
$val = "--";
};
local $, = " ";
print @array, $/;
sub make_interval {
my $ctr = 0;
my $start = 1;
return sub {
if ( $start) { $start = 0; return }
$ctr++;
if ( $ctr == $_[0]) { $ctr = 0 } else { no warnings; next }
}
}
| [reply] [d/l] [select] |