http://www.perlmonks.org?node_id=1010894


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

for ( keys %{ $bible{'Genesis'}{'chapter 1'} }) { print $_, ": ", @{ $bible{'Genesis'}{'chapter 1'}{$_} }, $/; }

That this loop prints your example data in the correct order is an artifact of the small number of verses in the data. Try adding the verses
    3 Three.
    4 Four.
    ...
    10 Ten.
to Genesis Chapter 1 and you will see the verses printed in apparently random order (really, the hashing order). Numerically sort the 'verse' keys before printing to get the correct order. (Tested.)

for (sort { $a <=> $b } keys %{ $bible{'Genesis'}{'chapter 1'} }) { print $_, ": ", @{ $bible{'Genesis'}{'chapter 1'}{$_} }, $/; }

Also, I would question your departure from the array-based nature of verse storage in the first place. johnko's OP example implies that each string/line is a verse and is stored in an array in verse order, and this is the way toolic's reply treats it. In your example, you store one and only one verse per anonymous array with the verse number as the hash key to the array, which seems a bit wasteful and, more importantly, potentially rather confusing. Are there, in fact, 'verses' that consist in more than one line of data?

I would also question how your book/chapter/verse parsing would handle books like "1 Corinthians", "2 Thessalonians", etc., but, as you say, this is all critically dependent on the exact format of the data johnko is parsing, which is not revealed to us.

Replies are listed 'Best First'.
Re^3: join all elements in array reference by hash
by 2teez (Vicar) on Dec 30, 2012 at 08:24 UTC

    • "..Numerically sort the 'verse' keys before printing to get the correct order..."

      Agreed 100%. Original POST corrected.
    • "..I would question your departure from the array-based nature of verse storage in the first place.."

      Reference for each of the verses separately informed that decision of mine.
      If I have done, like so:
      ... else { push @{ $bible{$book}{$chapter}{'verses'} }, $_; }
      Then output would have been like so:
      "Genesis" => { "chapter 1" => { verses => [ "1 In the beginning God created the heavens and + the earth.", "2 The earth was without form, and void; and da +rkness was on the face of the deep. And the Spirit of God was hoverin +g over the face of the waters.", ], }, "chapter 2" => { verses => [ "1 Thus the heavens and the earth, and all the +host of them, were finished. ", "2 And on the seventh day God ended His work wh +ich He had done, and He rested on the seventh day from all His work w +hich He had done.", ], }, },
      which ofcourse is cool and probably faster [ not tested though ]. But I thought, what if we decides to refer to each of the verses?
    • "..I would also question how your book/chapter/verse parsing would handle books like "1 Corinthians", "2 Thessalonians", etc.,.."

      One can of course use the roman numbering like "1 Corinthians", "2 Thessalonians" would then be "I Corinthians", "II Thessalonians", "III John", etc

    Finally, like I said in mine OP and you rightly pointed out this is all critically dependent on the exact format of the data johnko is parsing, which is not revealed to us

    If you tell me, I'll forget.
    If you show me, I'll remember.
    if you involve me, I'll understand.
    --- Author unknown to me
      Thank you all for your responses, and my apologies on not providing a sample input format. My "verses" is not in hash form, though the chapters are (which I don't know how to change just yet, but it doesn't matter as I've used the $a <=> $b syntax to sort them). I hadn't figured out how to handle the ones like "1 Corinthians", "2 Thessalonians" yet but that was on the "to do" list. The perldsc, "Hash of arrays" seems to be what I wanted and I've now successfully printed out that when I first parse it (line 173 - marked in comment), but I seem to be having difficulty in saving the information properly, so that when I try to retrieve it a second time (line 270 - also marked in comment), my arrays are "empty"(original part 2 of my question). Do I need to start 'bless'ing verses? Here's the code and sample input:
      #!/usr/bin/perl use strict; use warnings; use English; use Data::Dumper; my $input; my %bible; my $userinput; my $prologue; my @books; my $book; my $bookname; my @booknames; my @chapters; my $chapter; my $title; my $more; my @paragraphs; my $paragraph; my @verses; my $verse; my $chapterVerses; my $bookVerses; my $bibleVerses; my @comments; my @lines; my $line; my $bookindex = 0; #book my $c = 0; #chapter my $p = 0; #paragraph my $v = 0; #verse my $x = 0; #extra comments by Challoner while (<>) { $input .= $_; } @books = split (/BOOK: |THE HOLY GOSPEL OF JESUS CHRIST ACCORDING TO S +T. /, $input); #print $books[-1]; print scalar(@books) . " books\n"; $prologue = shift @books; #print "prologue: " . $prologue . "\n"; print '-'x80 . "\n"; print scalar(@books) . " books\n"; my $debug = 0; my $text; foreach $book (@books) { # print $book if $debug == 1; @lines = split(/^/, $book); # print scalar(@lines) . " lines: first few lines:\n" if $debug == +1; # print join("",@lines[1..5]) if $debug == 1; # print "\n\n" if $debug == 1; # print "last few lines:\n" if $debug == 1; # print join("",@lines[-5..-1]) if $debug == 1; # print "\n\n" if $debug == 1; $bookname = shift @lines; chomp($bookname) if $bookname; # next if !$bookname; $bookindex++; $booknames[$bookindex] = $bookname; # print "Book '$bookindex': $bookname\n"; # print "Book/Epistle/Prophecy: '$bookname'\n"; #Press <ENTER>; # print "$bookname\n"; #Press <ENTER>; # print join(@lines); @chapters = split('[a-zA-Z0-9]+ Chapter|Ruth \d*\n', $book); $prologue = shift @chapters; print "$bookname has " . scalar(@chapters) . " chapters\n"; # print "prologue: $prologue\n"; $c = 0; $v = 0; $bookVerses = 0; foreach $chapter (@chapters) { $c++ unless $chapter =~ m/according to the Hebrews/; # print "splitting chapter $c\n"; @paragraphs = split (/\n\n/,$chapter); # print "first few paragraphs:" . join("\n",@paragraphs[0..3]) + . "\n\n"; $title = shift @paragraphs; # check to see if it really was a title or was a verse. if ($title !~ m/(\d+):(\d+)\. /) { while ($more = shift @paragraphs) { if ($more !~ m/(\d+):(\d+)\. /) { $title .= "\n\n" . $more . "\n\n"; #make sure to m +ake this multiple paragraphs in html } else { # print "putting back $more into paragraphs\n"; unshift (@paragraphs,$more); last; } } # print "title: $title\n"; # make it in italics or bold # print "Chapter $c title: <italics>" . $title . "</italic +s>\n"; # print "Chapter $c title: " . $title . "\n"; } $p = 0; $chapterVerses = 0; $x = 0; foreach $paragraph (@paragraphs) foreach $paragraph (@paragraphs) { $p++; # print "paragraph #$p '$paragraph'\n"; # verse if it begins with a number if ($paragraph =~ m/(\d+):(\d+)\. /) { #print "verse: $paragraph\n\n"; #print +"mismatch of expected chapter $c vs. parsed $1\n" if $1 != $c; $verses[$chapterVerses] = $paragraph; # save on per ch +apter basis $chapterVerses++ unless $chapter =~ m/115| +147/; #Psalms that have fun ny continuation. # print "\$1 is $1 \$2 pattern match f +ailed\n" && next if !defined($2 ); # print "mismatch of expected verse $chapterVerses vs. + parsed $2\n" i f $2 != $chapterVerses; # print "in $paragraph\n" if $2 != $chapterVerses; $bookVerses++; $bibleVerses++; #print "verse $chapterVerses " . $verses[$chapterVerse +s] . "\n"; # print $verses[$chapterVerses] . "\n\n"; } elsif ($paragraph !~ m/^\s*$/) { #print "commentary: '$paragraph'\n"; # +x as in extra? $x++; $comments[$x] = $paragraph; # save these somehow or ma +ke font/color d ifferent # print "$x: '" . $comments[$x] . "'\n"; } } #print "Chapter $c has $chapterVerses verses.\n"; # print "$bookname has $bookVerses verses in total so far.\n"; # print "The bible has $bibleVerses verses in total so far.\n" +; # print "Chapter $c has $x comments.\n"; # save book name, verses, comments before going to next book # print "\nsaving book name($bookname), chapter $c, verses, an +d commentary\n" ; #my $allverses = join("\n\n",@verses); #print "The verses(" . @verses . "): $allverses\n"; # gotta figure out how to pass this in a sub routine. # save($bookname,$c,\@verses,\@comments); # my @saved_verses = @verses; # print "setting \$bible[$bookname][$c]['verses'] to " . \@ver +ses . "\n"; $bible{"$bookname"}{"$c"}{'verses'} = \@verses; my $size = $#verses; # print "size=$size\n"; #my @returned = $bible{$bookname}{$c}{'verses'}; #print join("\n\n",@{$bible{$bookname}{$c}{'verses'}{$_}}) . " +\n"; # Hash of arrays? my $i; foreach $i ( 0..$#{$bible{$bookname}{$c}{'verses'}}) { print "$bible{$bookname}{$c}{'verses'}[$i]\n\n"; #line 173 } #print join("\n\n",@returned) . "\n"; @verses = (); # clear it out so we can start verses for next c +hapter. # $bible{$bookname}{$c}{'comments'} = join ("\n\n",@comments); #$userinput = <STDIN>; } # foreach $chapter # print "\n===========END OF $bookname ========================\n"; # $userinput = <STDIN>; } #print join(', ',@booknames) . " book names\n"; #print scalar(@books) . " books in the bible \n"; #foreach $book (keys %bible) #{ # print "$book\n"; # @verses{$book}{$chapter} = split('\n\n', @{$chapters{$chapter}}); # print "\n============END OF CHAPTER=======================\n"; # $userinput = <STDIN>; #} while (1) { #Take in values like Gn 1:3, or Genesis, or Gn 3 and display appro +priate # Chapter and verse, book, or Chapter in book. print ("Please enter the Book, Chapter, and/or Verse you want: "); $userinput = <STDIN>; # stop when user types quit. or maybe "amen" :-). chomp $userinput; last if ($userinput eq 'quit'); last if ($userinput eq 'amen'); # Need a mapping for Gn->Genesis, Zeph->Zephaniah etc. my ($qbook,$qchapter,$qverse); if ($userinput =~ m/([A-Z]*)/) { print("Got book $1\n"); $qbook = $1; } elsif ($userinput =~ m/(\w+) (\d)/) { print("Got book $1, chapter $2\n"); $qbook = $1; $qchapter = $2; } elsif ($userinput =~ m/[\s]*(\w+)[ ]*[(\d+)[:]*(\d+)][\s]*/) { $qbook = $1; $qchapter = $2; $qverse = $3; printf("got all 3 \n"); } else { print "Doh!\n"; last; } next unless $qbook; print("$userinput: You're looking for: $qbook\n") if $qbook; print("Chapter: $qchapter\n") if $qchapter; print("Verse: $qverse\n") if $qverse; # remember to check that the verse, chapter, and book exist. if ($qverse) { print("verse specified: Book: $qbook doesn't exist") && next if (!exists($bible{"$qbook"})); print("chapter specified: $qbook Chapter: $qchapter doesn't ex +ist") && next if (!exists($bible{"$qbook"}{"$qchapter"})); print("book specified: $qbook $qchapter:$verse doesn't exist. +$qbook $qchapte r has only " . scalar($bible{"$qbook"}{"$qchapter"}{'verses'}) . " ver +ses.\n") && nex t if (!exists($bible{$book}{$chapter}{$verse})); print $bible{$qbook}{$qchapter}{'verses'}[$verse] if $verse; } elsif ($qchapter) { print("no verse: Book: $book doesn't exist\n") && next if + (!exists($bible{$qb ook})); print("no chapter: $qbook Chapter: $qchapter doesn't exist\n") + && next if (!e xists($bible{$qbook}{$qchapter})); print $bible{$qbook}{$qchapter} if $qchapter; } elsif ($qbook) { print ("Book: $qbook doesn't exist\n") && next if (!exists($bi +ble{$qbook})); #print ("Book is: " . join("\n",@books)); foreach $chapter ( sort {$a <=> $b} keys ($bible{$qbook}) ) { print ("joining chapter $chapter verses\n"); #my $allverses = join("\n\n",$bible{$qbook}{$chapter}{'ver +ses'}); #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 } } } else { print("Couldn't parse input '$userinput'\n"); } } #while ($userinput != "amen"); # ok do it 2 ways. exit 1; sub save { my ($bookname,$chapter,$verses,$comments) = @ARG; my @saved_verses = \$verses; print "$bookname, Chapter $chapter verses are: " . join("\n",@save +d_verses) . "\n "; $bible{$bookname}{$chapter}{'verses'} = \@saved_verses; # $bible{$bookname}{$chapter}{'comments'} = @comments; } foreach $book (keys %bible) { print "book: $book\n"; # @verses{$book}{$chapter} = split('\n\n', @{$chapters{$chapter}}); # print "\n============END OF CHAPTER=======================\n"; # $userinput = <STDIN>; }
      Sample input: (massaged a bit from the Gutenberg EBook #8300)
      The Project Gutenberg EBook of The Bible, Douay-Rheims, Old and New Testaments, Complete This eBook is for the use of anyone anywhere at no cost and with almost no restrictions whatsoever. You may copy it, give it away or re-use it under the terms of the Project Gutenberg License included with this eBook or online at www.gutenberg.net Title: The Bible, Douay-Rheims, Old and New Testaments, Complete The Challoner Revision Author: Release Date: December 17, 2004 [EBook #8300] Language: English Character set encoding: ISO-8859-1 *** START OF THIS PROJECT GUTENBERG EBOOK DOUAY-RHEIMS BIBLE *** Produced by David Widger from PG etext #1581 prepared by Dennis McCarthy, Atlanta, Georgia and Tad Book, student, Pontifical North American College, Rome THE HOLY BIBLE Translated from the Latin Vulgate Diligently Compared with the Hebrew, Greek, and Other Editions in Diverse Languages THE OLD TESTAMENT First Published by the English College at Douay A.D. 1609 & 1610 and THE NEW TESTAMENT First Published by the English College at Rheims A.D. 1582 With Annotations The Whole Revised and Diligently Compared with the Latin Vulgate by Bishop Richard Challoner A.D. 1749-1752 HISTORY This e-text comes from multiple editions of Challoner's revised Douay- Rheims Version of the Holy Bible. In 1568 English exiles, many from blah, blah, blah... Oxford, established the English College of Douay (Douai/Doway), Flande +rs, The notes included in this electronic edition are generally attributed to Bishop Challoner. CONTENTS The Old Testament Book of Genesis Book of Exodus Book of Leviticus ... BOOK: GENESIS This book is so called from its treating of the GENERATION, that is, of the creation and the beginning of the world. The Hebrews call it BERESHIT, from the Word with which it begins. It contains not only the history of the Creation of the world; but also an account of its progress during the space of 2369 years, that is, until the death of JOSEPH. Genesis Chapter 1 God creates Heaven and Earth, and all things therein, in six days. 1:1. In the beginning God created heaven, and earth. 1:2. And the earth was void and empty, and darkness was upon the face +of the deep; and the spirit of God moved over the waters. 1:3. And God said: Be light made. And light was made. Genesis Chapter 2 God rests on the seventh day and blesses it. The earthly paradise, in which God places man. He commands him not to eat of the tree of knowledge. And forms a woman of his rib. 2:1. So the heavens and the earth were finished, and all the furniture of them. 2:2. And on the seventh day God ended his work which he had made: and +he rested on the seventh day from all his work which he had done. He rested, etc... That is, he ceased to make or create any new kinds o +f things. Though, as our Lord tells us, John 5.17, "He still works", viz., by conserving and governing all things, and creating souls. BOOK: EXODUS The Second Book of Moses is called EXODUS, from the Greek word EXODOS, which signifies going out: because it contains the history of the goin +g out of the children of Israel out of Egypt. The Hebrews, from the word +s with which it begins, call it VEELLE SEMOTH: These are the names. It contains transactions for 145 years; that is, from the death of Joseph to the erecting of the tabernacle. Exodus Chapter 1 The Israelites are multiplied in Egypt. They are oppressed by a new king, who commands all their male children to be killed. 1:1. These are the names of the children of Israel, that went into Egy +pt with Jacob: they went in every man with his household: 1:2. Ruben, Simeon, Levi, Juda, Exodus Chapter 2 Moses is born and exposed on the bank of the river; where he is taken +up by the daughter of Pharaoh, and adopted for her son. He kills an Egyptian, and flees into Madian; where he marries a wife. 2:1. After this there went a man of the house of Levi; and took a wife of his own kindred. 2:2. And she conceived, and bore a son: and seeing him a goodly child, hid him three months. Exodus Chapter 3 God appears to Moses in a bush, and sends him to deliver Israel. 3:1. Now Moses fed the sheep of Jethro, his father in law, the priest +of Madian: and he drove the flock to the inner parts of the desert, and came to the mountain of God, Horeb. 3:2. And the Lord appeared to him in a flame of fire out of the midst +of a bush: and he saw that the bush was on fire, and was not burnt. The Lord appeared... That is, an angel representing God, and speaking +in his name. BOOK: LEVITICUS This Book is called LEVITICUS, because it treats of the Offices, Ministries, Rites and Ceremonies of the Priests and Levites. The Hebre +ws call it VAICRA, from the word with which it begins. Leviticus Chapter 1 Of holocausts or burnt offerings. 1:1. And the Lord called Moses, and spoke to him from the tabernacle o +f the testimony, saying: 1:2. Speak to the children of Israel, and you shall say to them: The man among you that shall offer to the Lord a sacrifice of the cattle, that is, offering victims of oxen and sheep:
      Sample output:
      john-kos-computer:bible john$ ./chapters.pl sample_bible.txt 4 books ---------------------------------------------------------------------- +---------- 3 books GENESIS has 2 chapters 1:1. In the beginning God created heaven, and earth. 1:2. And the earth was void and empty, and darkness was upon the face +of the deep; and the spirit of God moved over the waters. 1:3. And God said: Be light made. And light was made. 2:1. So the heavens and the earth were finished, and all the furniture of them. 2:2. And on the seventh day God ended his work which he had made: and +he rested on the seventh day from all his work which he had done. EXODUS has 3 chapters 1:1. These are the names of the children of Israel, that went into Egy +pt with Jacob: they went in every man with his household: 1:2. Ruben, Simeon, Levi, Juda, 2:1. After this there went a man of the house of Levi; and took a wife of his own kindred. 2:2. And she conceived, and bore a son: and seeing him a goodly child, hid him three months. 3:1. Now Moses fed the sheep of Jethro, his father in law, the priest +of Madian: and he drove the flock to the inner parts of the desert, and came to the mountain of God, Horeb. 3:2. And the Lord appeared to him in a flame of fire out of the midst +of a bush: and he saw that the bush was on fire, and was not burnt. LEVITICUS has 1 chapters 1:1. And the Lord called Moses, and spoke to him from the tabernacle o +f the testimony, saying: 1:2. Speak to the children of Israel, and you shall say to them: The man among you that shall offer to the Lord a sacrifice of the cattle, that is, offering victims of oxen and sheep: Please enter the Book, Chapter, and/or Verse you want: GENESIS Got book GENESIS GENESIS: You're looking for: GENESIS joining chapter 1 verses -1 verses joining chapter 2 verses -1 verses Please enter the Book, Chapter, and/or Verse you want:
        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.