Beefy Boxes and Bandwidth Generously Provided by pair Networks
The stupid question is the question not asked

Re^5: join all elements in array reference by hash

by AnomalousMonk (Canon)
on Dec 30, 2012 at 19:00 UTC ( #1010954=note: print w/replies, xml ) Need Help??

in reply to Re^4: join all elements in array reference by hash
in thread join all elements in array reference by hash

foreach $chapter ( sort {$a <=> $b} keys ($bible{$qbook}) ) { print ("joining chapter $chapter verses\n"); #my $allverses = join("\n\n",$bible{$qbook}{$chapter}{'verses'}); #print $allverses . "\n\n"; my $i; print "$#{$bible{$qbook}{$chapter}{'verses'}} verses\n"; foreach $i (0..$#{$bible{$qbook}{$chapter}{'verses'}}) { print "$bible{$qbook}{$chapter}{'verses'}[$i]\n\n"; #line 270 } }

I have not had much time to look at your post (it is kinda big; a small, self-contained, runnable code example will help your fellow monks find problems, and may very well help you to find problems just by preparing it!) and may not be able to do so thoroughly today, but a few things strike me about the code quoted above, which includes the problematic line 270:

  • The expression  $#{$bible{$qbook}{$chapter}{'verses'}} will yield -1 if the referenced array is empty. That this is the case is shown by the following print statement
        print "$#{$bible{$qbook}{$chapter}{'verses'}} verses\n";
    which produces "-1 verses". The expression  0 .. -1 produces an empty range, which means that the loop body, line 270, is never executed. You have to go back and find where this array was emptied, or else why nothing was ever put into it in the first place.
  • An expression like  $#array or  $#$array_reference evaluates to the highest array index, not to the number of elements, verses in this case, in the array.
  • A stylistic point: a loop like
        foreach $i (0..$#{$bible{$qbook}{$chapter}{'verses'}})
            print "$bible{$qbook}{$chapter}{'verses'}[$i]\n\n";
    is, IMHO, better written as
        foreach my $verse (@{$bible{$qbook}{$chapter}{'verses'}}) {
            print $verse, "\n\n";

Update: print statement in 'better' for-loop changed from  print "$verse\n\n"; to  print $verse, "\n\n"; and loop variable  $verse made lexical  my $verse instead.

Replies are listed 'Best First'.
Re^6: join all elements in array reference by hash
by johnko (Initiate) on Dec 31, 2012 at 08:29 UTC

    Thanks for your prompt response and patience/wisdom. I didn't expect so many quick and detailed responses during the Christmas season. I've broken down my exceedingly long 300 lines or so into several subroutines to make for better readability.

    I found the answer to my memory problems - "make an anonymous copy" via a post from Randal Shwartz. At: I basically needed to put [] around my @verses array so that I create a copy of it when I point my hash reference there.

    In terms of the foreach stylistic point (@{$bible...), that was sort of the syntax I was looking for for my join, but my issue had been that I had an empty array. Would not the following be even cleaner? Comments on efficiency?

    print join("\n\n",@{$bible{$qbook}{$chapter}{'verses'}});
    That seems to be working for me now. I may have future questions regarding my little project but now I have some experience regarding "too little" and "too much" so I'll try to ensure I follow your advice of making it "just right" and using <readmore> as appropriate.
      In terms of the foreach stylistic point (@{$bible...), that was sort of the syntax I was looking for for my join... Would not the following be even cleaner? Comments on efficiency?

      print join("\n\n",@{$bible{$qbook}{$chapter}{'verses'}});

      The salient difference between a statement like
          print join("\n\n",@{$bible{$qbook}{$chapter}{'verses'}});
      and a loop like
          foreach my $verse (@{$bible{$qbook}{$chapter}{'verses'}}) {
              print $verse, "\n\n";
      is that the join built-in creates a copy in memory of all the concatenated elements of the array, and the for-loop does not. In fact, the for-loop does not even create a copy of any element of the array, but rather aliases  $verse to each element in turn.

      I vaguely recall that the Bible consists in fewer than 900,000 words. Even with commentaries included and using the hairiest possible UTF character set, it's hard for me to imagine the whole thing being longer than a few score MBs as a single string, and this is easily accomodated by Perl (in addition to whatever is still sitting in the array) on any remotely modern machine/OS I'm aware of. Furthermore, certain things, e.g., multi-line regex operations, often become quite simple with such a string. OTOH, you're only talking about join-ing all the verses of a single chapter, amounting to a still relatively short string, and modern operating systems are quite well adapted to I/O operations involving many, relatively short 'lines' of data.

      IOW, the join-versus-for-loop question is one of scalability, and the task you are dealing with does not seem likely to encounter scaling problems. (If you were dealing with genomics problems, the situation would be different; such problems very often involve processing files with many MB or GB of large records, so one must be very sensitive to scaling issues.)

      So for me, the chief considerations in dealing with your code would be readability and maintainability, with efficiency a distant third and scalability nowhere in sight. Based on these considerations, my personal preference would be the for-loop: it's highly idiomatic and familiar. (But I can't help saying that my guess would be that the for-loop would also be slightly more efficient in terms of speed and certainly in terms of memory usage.)

      HTH, and best wishes for the new year.

      Update: If you want to throw readability and maintainability to the four winds and go with cute, the following might be the most efficient of all:
          { local $, = "\n\n";  print @{$bible{$qbook}{$chapter}{'verses'}}; }

Log In?

What's my password?
Create A New User
Node Status?
node history
Node Type: note [id://1010954]
[choroba]: What about [metadoc:// Algorithm::Loops]?
[Corion]: choroba: Yeah, but handing off the request to Dancer,Plack, Mojolicious,LWP is easy once I have the data filled into some structure ;))
[choroba]: Algorithm::Loops
[Corion]: choroba: I'm using that to generate the permutations, but I don't know how the user can pass the intended values to my function in a sane way
[Corion]: I have a prototype that permutes the get_parameters, but the values used for the get parameters should be different from the values used for the headers and potentially for parts of the URL
[Corion]: But yes, in general, my approach will be "split the URL into another set of parameters, generate an array of allowed values for each parameter and then NestedLoops() over the set"
[choroba]: hmm... so you need something like bag from Test::Deep, but not for checking, but for generation
[Corion]: This has the dual use of easily requesting sequential URLs and also being suitable for testing
[Corion]: For testing, I want to skip all tests with the same value(s) once one test fails to cut down on the number of failing tests
[Corion]: choroba: Yes, in a way I

How do I use this? | Other CB clients
Other Users?
Others wandering the Monastery: (9)
As of 2017-01-17 08:16 GMT
Find Nodes?
    Voting Booth?
    Do you watch meteor showers?

    Results (152 votes). Check out past polls.