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

Let's say I have the arrays @foo = 1 .. 5; and @bar = @foo[0..2]; I want to be able to splice(@bar, 1, 0, 6); and have is_deeply(\@foo, [1,6,2,3,4,5]); pass.

Basically, I want to have an array act as an alias to a sub-array of another array. If it helps, it can be a contiguous slice. I understand that slicing creates a completely new list, unrelated to the old one, so slicing as implemented isn't the answer.

I also know I can use tie for this, but I really want to avoid tie, if at all possible. The application1 of this is going into the refactoring of a heavily-used CPAN module, so performance is more important that a slight memory gain.

Is there a way to alias like I'm talking about? Or is this a Perl6-ism that isn't possible in Perl5? I would think that XS could provide a solution, but I don't know enough about XS to even make a stab at the feasibility of my crackpipe-dreams.

  1. Basically, I have an object which is a hashref that also contains an arrayref. I figured that I could make that into an arrayref as follows:
    • Index 0 is the size of the arrayref. (This would be maintained by hand.)
    • Index 1..M is the arrayref
    • Index M+1..N are the other attributes. They are accessed as so
      sub foo { my $self = shift; return $self->[ $self->[0] + $foo_offset ]; }
    • New attributes from subclasses can be added onto the end.

My criteria for good software:
  1. Does it work?
  2. Can someone else come in, make a change, and be reasonably certain no bugs were introduced?

Replies are listed 'Best First'.
Re: (Expert) Splicing a slice
by Roy Johnson (Monsignor) on Sep 28, 2005 at 14:52 UTC
    While your specific slice might make sense, if you consider that you could have @bar = @foo[2,1,2,0];, it becomes nonsense to suggest that a splice on @bar should affect @foo.

    You want something different from a slice: a reference to a subarray that could be acted on transparently. You could get that effect by making an object that copies the slice, and has a splice method that performs the requested splice and then splices the resulting value back into the reference array.

    Regarding how you plan to use this, I don't see why you don't have an array of two arrayrefs. The first ref would be your arrayref, and the second would be the attributes. Is there some reason you need the structure to be flat? Your description sounds pretty good as a file format, but I'd have the program read it into the structure I describe.


    Caution: Contents may have been coded under pressure.
Re: (Expert) Splicing a slice
by BrowserUk (Patriarch) on Sep 28, 2005 at 16:12 UTC

    I seriously doubt this is possible, even from XS.

    Whilst you could set up two xpvav structures to point to the same SV* array and get two arrays where one is a slice of the other, the moment any modification was made to one of the arrays that changed its length, push,pop, splice etc., the perl library code for performing these operations would not maintain both xpvavs. At best, the modification to the modified array would not be reflected in the other. At worst, you'll get a segfault.

    Eg.

    AV xpvav [(@foo)]->[ARRAY]-------------------| [FILL ] = 4 | [MAX ] = 9 | [... ] | [ALLOC]-----------| | [ARLEN] v v SV*[ | | 1 | 2 | 3 | 4 | 5 | | | ] AV xpvav ^ ^ [(@bar)]->[ARRAY]-------------------| [FILL ] = 4 | [MAX ] = 9 | [... ] | [ALLOC]-----------| [ARLEN]

    After splice @foo 1, 0, 'a' .. 'c';, if you were really lucky and there was enough contiguous freespace following the existing allocation to accomodate the extension to the array, then you might end up with something like this:

    AV xpvav [(@foo)]->[ARRAY]-------------------| [FILL ] = 7 | [MAX ] = 13 | [... ] | [ALLOC]-----------| | [ARLEN] v v SV*[ | | 1 | a | b | c | 2 | 3 | 4 | 5 | | + | ] AV xpvav ^ ^ [(@bar)]->[ARRAY]-------------------| [FILL ] = 4 | [MAX ] = 9 | [... ] | [ALLOC]-----------| [ARLEN]

    but now the FILL & MAX fields in the @BAR xpvav no longer reflect the reality of the memory to which they point because the library code is not going to maintain both structures. Any attempt to use @bar is at best going to result in erroneous results, and worst is likely to segfault.

    And if there is not sufficient contiguous memory following the existing allocation when the modification is made, a completely new piece of memory will be allocated and the existing values from @foo will be copied to the new allocation, but again, the @bar xpvav will not be maintained and any correspondance between the two arrays will be lost.

    There is also the problem of maintain the reference counts. If you take and hold a reference to one array, but the other's reference count falls to zero, then perl will be free to reclaim the space as it will not know about the other array's reference(s).


    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    Lingua non convalesco, consenesco et abolesco. -- Rule 1 has a caveat! -- Who broke the cabal?
    "Science is about questioning the status quo. Questioning authority".
    The "good enough" maybe good enough for the now, and perfection maybe unobtainable, but that should not preclude us from striving for perfection, when time, circumstance or desire allow.
Re: (Expert) Splicing a slice
by xdg (Monsignor) on Sep 28, 2005 at 14:52 UTC

    (There is no spoon, eh?)

    Caveat: what I know of perlguts I mostly understand from reading PerlGuts Illustrated. From the "AV" section of that, the actual array storage data structure consists of pointers to the contents of each element of the array. That would suggest that you could use XS to get the pointers in two separate arrays to point to the same contents. I think that Data::Alias should be able to do that for you like this:

    use Data::Alias; alias @x = @y; $x[1] = 2; # should make $y[1] == 2

    If applied to a slice, that might get part of what you're looking for -- one array that is really just an alias a subsection of another array. The problem is that your example with splice won't work -- as splicing will just screw up the offsets, e.g. you'd get something like $x[2] pointing to $y[3]. You might be able to manually handle it, splicing and re-aliasing or something, but I'm not sure if the overhead of that leaves you with any performance gain.

    Update: Data::Alias doesn't seem to be available for ActiveState. However, Lexical::Alias is and appears to be do the same kind of thing, albeit with different syntax, if that's an important consideration.

    -xdg

    Code written by xdg and posted on PerlMonks is public domain. It is provided as is with no warranties, express or implied, of any kind. Posted code may not have been tested. Use of posted code is at your own risk.

      I keep ruminating on this, trying to understand what you're trying to do. I'm guessing that you want to have one master array that holds data and attributes, but you want to be able to grow/shrink the data portion of it, while maintaining the aliases to the attributes. If so, the splice limitation I mentioned above might not be a problem. For example:

      use strict; use warnings; use Data::Alias; my @data = ( 1 .. 3 ); my @attributes = ( 'a' .. 'd' ); my @master = ( scalar @data, @data, @attributes ); alias my @alias = @master[ 1 + $master[0] .. $#master ]; print "After alias\n"; print "master: ", join(q{,}, @master ), "\n"; print "attrib: ", join(q{,}, @alias ), "\n"; print "\n"; $alias[0] = 'z'; print "After change\n"; print "master: ", join(q{,}, @master ), "\n"; print "attrib: ", join(q{,}, @alias ), "\n"; print "\n"; splice @master, 2, 0, '6'; $master[0]++; print "After splice\n"; print "master: ", join(q{,}, @master ), "\n"; print "attrib: ", join(q{,}, @alias ), "\n"; print "\n";

      Prints

      After alias master: 3,1,2,3,a,b,c,d attrib: a,b,c,d After change master: 3,1,2,3,z,b,c,d attrib: z,b,c,d After splice master: 4,1,6,2,3,z,b,c,d attrib: z,b,c,d

      The splice happens in the data section, which is not aliased, and it doesn't disturb the alias to the attributes. So offsets you define for specific attributes (from the start of @alias) would be preserved.

      -xdg

      Code written by xdg and posted on PerlMonks is public domain. It is provided as is with no warranties, express or implied, of any kind. Posted code may not have been tested. Use of posted code is at your own risk.

        Wow. Not only did you provide the module needed, but you also provided the inside-out thinking that makes the problem solveable.

        Basically, the idea is that I have a set of attributes and an array that grows and shrinks. Instead of having a hashref with an entry pointing to an arrayref, I was thinking I should be able to just have an arrayref. If that's all it was, I could have the attributes up front and the array in the back. But, I want to be able to add attributes in subclasses, so the array has to live up front. I also wanted to use splice for some of the array manipulation sections.

        I'll have to see if I can work this in, but it's definitely a promising idea. THANKS! :-)


        My criteria for good software:
        1. Does it work?
        2. Can someone else come in, make a change, and be reasonably certain no bugs were introduced?
        You're splicing the wrong array. He wants to allow the module's user to splice the array it sees (the slice) and have it affect both the slice and the master array.
Re: (Expert) Splicing a slice
by ikegami (Patriarch) on Sep 28, 2005 at 15:14 UTC

    Close, but no cigar:

    use strict; use warnings; sub slice_ref { return \@_; } my @foo = (1..5); our @bar; *bar = slice_ref @foo[0..2]; print('foo: ', join(', ', @foo), "\n"); print('bar: ', join(', ', @bar), "\n"); print("\n"); $bar[0] = 'a'; print("After changing bar0:\n"); print('foo: ', join(', ', @foo), "\n"); print('bar: ', join(', ', @bar), "\n"); print("\n"); splice(@bar, 1, 0, 6); print("After inserting into bar:\n"); print('foo: ', join(', ', @foo), "\n"); print('bar: ', join(', ', @bar), "\n"); print("\n"); $bar[0] = 'b'; print("After changing bar0:\n"); print('foo: ', join(', ', @foo), "\n"); print('bar: ', join(', ', @bar), "\n"); __END__ output ====== foo: 1, 2, 3, 4, 5 bar: 1, 2, 3 After changing bar0: foo: a, 2, 3, 4, 5 bar: a, 2, 3 After inserting into bar: foo: a, 2, 3, 4, 5 Want: a, 6, 2, 3, 4, 5 bar: a, 6, 2, 3 After changing bar0: foo: b, 2, 3, 4, 5 The magic is still there bar: b, 6, 2, 3 on individual elements.

    By the way, I think you asking for a solution that should not be used in a "heavily-used CPAN module".

    Update: Fixed bug in slice_ref, and simplified it. Added comment at bottom.

Re: (Expert) Splicing a slice
by diotalevi (Canon) on Sep 28, 2005 at 15:54 UTC

    You could alter @bar so that ->ARRAY is a pointer to the start of the slice inside @foo's ->ARRAY. Also adjust ->FILL and ->MAX. This will suffice for allowing a readable window into @foo. You must now prevent perl from doing any bad alterations to the ->ARRAY that is being pointed at. This includes preventing perl from GCing @bar prior to @foo (and then clean up @bar). If splice() is going to need ->ARRAY to be reallocated or such, you'll need to intercept that. I'm not sure that's possible.

    If you marked @bar as read only then you could do your window'd arrangement without vastly too much magic. Mutable @bar will require you to know lots of details about how perl is going to handle its memory allocation and will be version dependant.

Re: (Expert) Splicing a slice
by calin (Deacon) on Sep 28, 2005 at 15:58 UTC

    Here's a failed attempt. It does NOT work correctly (inserting or deleting does not "shift" elements in the original array), also suffers from all the joys of local esp. you can not use the original foo while in the local scope. Just for your amusement.

    $ perl @foo = 1..5; $" = ':'; { # setup *bar = \@foo; # or other aliasing method, or localise from foo itsel +f local @bar[3..$#bar]; splice @bar, 3; # "test" code print "[1] =@bar=\n"; splice @bar, 1, 0, 'new'; print "[2] =@bar=\n"; } # examine @foo after print "[3] =@foo=\n"; ^D [1] =1:2:3= [2] =1:new:2:3= [3] =1:new:2:4:5=

    Update: minor wording update

Re: (Expert) Splicing a slice
by ikegami (Patriarch) on Sep 28, 2005 at 15:35 UTC

    What about:

    sub foo { my $self = shift; # What would be in the slice. my $extra = $extra{$self}; # The hidden stuff. ... } DESTROY { delete($extra{$self}); }

    No magic, so it's reliable.