in reply to An iterator for (not "iterating") a recursive data structure.

This is pretty rough, and only works if undef is not a possible value, and empty arrays are not possible. But you should have no problem to adapt it:

#! perl use v5.14;; use Data::Dump qw[ pp ]; sub genNested { my $n = shift; map{ rand() < 0.5 ? [ rand(1000), genNested( $n-1 ) ] : rand( 1000 + ) } 1 .. $n; } my @nested = genNested 3; pp \@nested; my @flat = 1..10; sub genIterator(+) { my $array = shift; my $index = 0; # Current index in the list my $child; # Iterator for the level below if the current element is + an array return sub { my $element; if (defined $child) { $element = $child->(); } if (!defined $element) { $child = undef; $element = $index < @$array ? $array->[$index++] : undef; if (ref $element eq 'ARRAY') { $child = genIterator($element); $element = $child->(); } } return $element; } } my $next = genIterator @nested; say while $_ = $next->();

Replies are listed 'Best First'.
Re^2: An iterator for (not "iterating") a recursive data structure.
by ikegami (Patriarch) on Jul 09, 2019 at 04:29 UTC

    This [...] only works if undef is not a possible value, and empty arrays are not possible.

    It doesn't even handle false values (without a minor change).

    The following handles false values (including undef) as well as empty arrays:

    sub genIterator { my $array = shift; my $index = 0; # Current index into $array my $child_iter; # Iterator for array at $array[$index] return sub { while (1) { # If we're iterating through descendants of a child, if (defined $child_iter) { # If the there is a descendant we haven't returned, if ( my ($element) = $child->() ) { return $element; } # No more descendants for this child. $child_iter = undef; } # Signal end of iteration if done. return if $index >= @$array; # Return next child if it's not an array ref. my $element = $array->[$index++]; return $element if ref $element ne 'ARRAY'; # Prepare to iterate through descendants of child. $child_iter = genIterator($element); } } } my $iter = genIterator \@nested; say while ($_) = $iter->();

      ++. It looks like I forgot to test my code for false values (it took me a while to see where they wouldn't work, it was the very last line where I iterate while the value is true).

      Thanks for the correction, for some reason I'm still not sure I would have thought of it even nowadays. I suppose I don't really think about using the number of returned values when I'm expecting a scalar, rather than a list

Re^2: An iterator for (not "iterating") a recursive data structure.
by LanX (Saint) on Feb 13, 2015 at 13:45 UTC
    > and only works if undef is not a possible value

    If you are suffering from the undef is a value problem¹, than call iterators in list context. Re: list context (iterators).

    I have to admit that I didn't try to understand your code , though! :)

    Cheers Rolf

    PS: Je suis Charlie!

    ¹) aka "0 but true" nonsense

      Though it kind of bothered me at first, now I really like the way python handles the semipredicate problem, which is to throw an exception at the end of the iteration rather than try to return a specific value (which may end up as part of an iterable). Besides, I think that python's yield would have been one of the easiest ways to solve this problem. For those who don't know about yield it stops the execution of a function (it can be in the middle of an infinite loop) which will be resumed when the next value is fetched. Perl 6's lazy lists can be used in a similar way according to its faq

      As for understanding my code, the concept is actually fairly simple (well, to me it is, but I'm used to working with closures). I started with a flat list iterator: a closure that holds the array and the current index. In the nested list version, each flat list iterator has a children which is an iterator for the current element if the element itself is an array. You could see it as equivalent to this:

      def next: if array[index].isAnArray() and array[index].hasNext() ret = array[index].next(); else ret = array[index] endif index++ end
      . This means that the calling stack for a deeply nested list will be something like:
      parent.next() child.next() grandChild.next() ...
      And so each time a value is fetched, which is why an iterative solution like MidLifeXis's can be far more effective.

        »»» This post is about alpha status Perl 6, not rock solid Perl 5 «««

        Though it kind of bothered me at first, now I really like the way python handles the semipredicate problem

        P6's design for run-time warnings, errors and delayed exception handling is anchored on Failure objects:

        ... dynamic context can determine the treatment of failures that in other languages would always throw exceptions. This gives Perl 6 programs the flexibility to handle exceptions either in-band or out-of-band. It is particularly important to be able to handle exceptions in-band when you are trying to perform parallel operations, so that the failure of one computation does not result in fratricide of all its fellow computations. (You can think of this as analogous to the way NaN propagates through floating-point calculations.)

        (from the design doc / section S02 / Undefined types)

        A simple use of this flexibility is the 'fatal' pragma that controls whether "built-ins ... automatically throw exceptions on failure" in its dynamic scope:

        use fatal;

        The fail function responds to the caller's use fatal state. It either returns an unthrown exception, or throws the exception. Before you get too happy about this pragma, note that Perl 6 contains various parallel processing primitives that will tend to get blown up prematurely by thrown exceptions. Unthrown exceptions are meant to provide a failsoft mechanism in which failures can be treated as data and dealt with one by one, without aborting execution of what may be perfectly valid parallel computations. If you don't deal with the failures as data, then sink context will automatically throw any unhandled Failure that you try to discard.

        In any case, the overriding design principle here is that no unhandled exception is ever dropped on the floor, but propagated outward until it is handled. If no explicit handler handles it, the implicit outermost exception handler will eventually decide to abort and print all unhandled exceptions passed in as its current @! list.

        It is possible to fail with a resumable exception, such as a warning. If the failure throws its exception and the exception resumes, the thrower by default returns the null string ('') to whatever caused the failure to throw its exception. This may be overridden by attaching a .resume_value to the warning. Hence numeric coercions such as +"42foo" can be forced to return 42 after issuing a warning.

        (from the design doc / section S04 / Exceptions)

        > Perl 6's lazy lists can be used in a similar way according to its faq

        search for "gather / take"

        edit

        > which is to throw an exception at the end of the iteration rather than try to return a specific value

        you can use goto for a poor man's solution here.

        Cheers Rolf

        PS: Je suis Charlie!

Re^2: An iterator for (not "iterating") a recursive data structure.
by BrowserUk (Patriarch) on Feb 12, 2015 at 15:29 UTC

    This is akin to the route I was going, but I just couldn't get it right. Thanks.


    With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority". I'm with torvalds on this
    In the absence of evidence, opinion is indistinguishable from prejudice. Agile (and TDD) debunked