2

I have a JSON file containing a bunch of songs with the associated song lines, verse number etc. My goal is to print each distinct verse. I'm having difficulty constructing a loop that works. Here is a sample of the JSON file:

[{ "song": 1, "verse": 1, "lineNum": 1, "line": "Mato Joe Tsinki"},
 { "song": 1, "verse": 1, "lineNum": 2, "line": "Chiti chiti"},
 { "song": 1, "verse": 2, "lineNum": 1, "line": "Raro Raro"},
 { "song": 1, "verse": 2, "lineNum": 2, "line": "Shinan Kibi"},
 { "song": 1, "verse": 2, "lineNum": 3, "line": "Bewa bewa"},
 { "song": 2, "verse": 1, "lineNum": 1, "line": "Ramo Kano"},
 { "song": 2, "verse": 2, "lineNum": 1, "line": "Choro Choro"},
 { "song": 3, "verse": 1, "lineNum": 1, "line": "Pisha Pisha"}]

In this JSON there are 5 verses total.

  1. Song 1, Verse 1: "Mato Joe Tsinki" / "Chiti Chiti"
  2. Song 1, Verse 2: "Raro Raro" / "Shinan Kibi" / "Bewa Bewa"
  3. Song 2, Verse 1: "Ramo Kano"
  4. Song 2, Verse 2: "Choro Choro"
  5. Song 3, Verse 1: "Pisha Pisha"

I've written the following which successfully prints the 1st verse of every song but not subsequent verses.

var fs = require('fs');
var colors = require('colors');
fs.readFile('stackoverflow.json', 'utf8', function (err,data) {
data = JSON.parse(data); 

for(var i in data) { 
    var item = data[i];
    // construct string song:verse:line
    var lineIndex = item.song   
    lineIndex += ":"+item.verse
    lineIndex += ":"+item.lineNum

    var numSongs = 20
    var songNum = 1
    var verseNum = 1
    var lineNum = 1
    // console.log(verseNum)

        while(item.song <= numSongs && item.verse == verseNum)
        {       
            console.log(lineIndex.green + ' ' + item.line);
            verseNum++;
        }
}
});

Updated to demonstrate desired output a console log:

Song 1 Verse 1: "Mato Joe Tsinki" "Chiti Chiti"

Song 1 Verse 2: "Raro Raro" "Shinan Kibi" "Bewa Bewa"

Song 2 Verse 1: ....

Dave
  • 75
  • 7
  • Hi Dave, before I type up an answer, do you have access to the lodash library (or underscore) in your project? Also are you able to use ecmascript 6? – JSager Feb 22 '19 at 04:31
  • FYI: https://stackoverflow.com/questions/500504/why-is-using-for-in-with-array-iteration-a-bad-idea – gman Feb 22 '19 at 04:36
  • I am ok using other libraries like lodash. Thanks for quick replies. – Dave Feb 22 '19 at 04:47
  • Adiga, grouping based on verse. – Dave Feb 22 '19 at 04:48
  • Have updated question with desired output. Thanks for responses. – Dave Feb 22 '19 at 04:57
  • 1
    @Dave What is that, exactly? An array of strings? Multiple `console.log()`s? One long string with line breaks? That doesn't really make it any clearer. The desired output should be represented as *code*, much like your input array. – Tyler Roper Feb 22 '19 at 04:59
  • Desired output is multiple console logs (updated question also). – Dave Feb 22 '19 at 07:06

3 Answers3

2

You could use reduce to group the verses and create an accumulator object with each unique combination of song and verse as it's keys. And, then use Object.values to get the final output. This creates an array of lines for each combo of song and verse:

var verses = 
[{ "song": 1, "verse": 1, "lineNum": 1, "line": "Mato Joe Tsinki"},
 { "song": 1, "verse": 1, "lineNum": 2, "line": "Chiti chiti"},
 { "song": 1, "verse": 2, "lineNum": 1, "line": "Raro Raro"},
 { "song": 1, "verse": 2, "lineNum": 2, "line": "Shinan Kibi"},
 { "song": 1, "verse": 2, "lineNum": 3, "line": "Bewa bewa"},
 { "song": 2, "verse": 1, "lineNum": 1, "line": "Ramo Kano"},
 { "song": 2, "verse": 2, "lineNum": 1, "line": "Choro Choro"},
 { "song": 3, "verse": 1, "lineNum": 1, "line": "Pisha Pisha"}]
 
 
 const merged = verses.reduce((acc, { song, verse, line }) => {
  const key = `${song}-${verse}`
  acc[key] = acc[key] || {song, verse, lines: []};
  acc[key]["lines"].push(line);
  return acc
 }, {})
 
 const output = Object.values(merged)
 
 console.log(output)

If you want the lines separated by a /, then use a string property and concatenate each line:

var verses = 
[{ "song": 1, "verse": 1, "lineNum": 1, "line": "Mato Joe Tsinki"},
 { "song": 1, "verse": 1, "lineNum": 2, "line": "Chiti chiti"},
 { "song": 1, "verse": 2, "lineNum": 1, "line": "Raro Raro"},
 { "song": 1, "verse": 2, "lineNum": 2, "line": "Shinan Kibi"},
 { "song": 1, "verse": 2, "lineNum": 3, "line": "Bewa bewa"},
 { "song": 2, "verse": 1, "lineNum": 1, "line": "Ramo Kano"},
 { "song": 2, "verse": 2, "lineNum": 1, "line": "Choro Choro"},
 { "song": 3, "verse": 1, "lineNum": 1, "line": "Pisha Pisha"}]
 
 
 const merged = verses.reduce((acc, { song, verse, line }) => {
  const key = `${song}-${verse}`;
  
  if(acc[key])
    acc[key]["lines"] += " / " + line;
  else
    acc[key] = {song, verse, lines: line};
    
  return acc
 }, {})
 
 const output = Object.values(merged)
 
 console.log(output)
adiga
  • 34,372
  • 9
  • 61
  • 83
1

Instead of using a while and for, consider using Array.reduce().

As an array of lines with line and lineNum:

var arr = [{ "song": 1, "verse": 1, "lineNum": 1, "line": "Mato Joe Tsinki"}, { "song": 1, "verse": 1, "lineNum": 2, "line": "Chiti chiti"}, { "song": 1, "verse": 2, "lineNum": 1, "line": "Raro Raro"}, { "song": 1, "verse": 2, "lineNum": 2, "line": "Shinan Kibi"}, { "song": 1, "verse": 2, "lineNum": 3, "line": "Bewa bewa"}, { "song": 2, "verse": 1, "lineNum": 1, "line": "Ramo Kano"}, { "song": 2, "verse": 2, "lineNum": 1, "line": "Choro Choro"}, { "song": 3, "verse": 1, "lineNum": 1, "line": "Pisha Pisha"}];
 
let result = arr.reduce((out, i) => {
    let found = out.find(s => s.song === i.song && s.verse === i.verse);
    let {line, lineNum, ...newSong} = i;
    let {song, verse, ...newLine} = i;
    
    found 
      ? found.lines.push(newLine)
      : out.push({...newSong, lines: [newLine]});
      
    return out;
  }, []);
   
console.log(result);

Comma-separated:

var arr = [{ "song": 1, "verse": 1, "lineNum": 1, "line": "Mato Joe Tsinki"}, { "song": 1, "verse": 1, "lineNum": 2, "line": "Chiti chiti"}, { "song": 1, "verse": 2, "lineNum": 1, "line": "Raro Raro"}, { "song": 1, "verse": 2, "lineNum": 2, "line": "Shinan Kibi"}, { "song": 1, "verse": 2, "lineNum": 3, "line": "Bewa bewa"}, { "song": 2, "verse": 1, "lineNum": 1, "line": "Ramo Kano"}, { "song": 2, "verse": 2, "lineNum": 1, "line": "Choro Choro"}, { "song": 3, "verse": 1, "lineNum": 1, "line": "Pisha Pisha"}];
 
let result = arr.reduce((out, i) => {
    var found = out.find(s => s.song === i.song && s.verse === i.verse);
    
    found 
      ? found.line += ", " + i.line  //Song found - combine the lines
      : out.push(i);                 //Song not found - add it as new item

    return out;
  }, []);
   
console.log(result);

reduce() will loop through each song in the array to build a collection. If it finds the same song and verse in the collection already, it adds the lines together. If not, it adds the entire song to the collection.

That said, you haven't clearly defined what you expect the output to look like.

Tyler Roper
  • 21,445
  • 6
  • 33
  • 56
0

Here you have another version that uses first Array.forEach() to group the lines by song and verses taking care of put they in order on an array first (just in case the original array is not ordered). Then we use Array.map() to get your expected output:

let input = [
 { "song": 1, "verse": 1, "lineNum": 2, "line": "Chiti chiti"},
 { "song": 1, "verse": 1, "lineNum": 1, "line": "Mato Joe Tsinki"},
 { "song": 1, "verse": 2, "lineNum": 1, "line": "Raro Raro"},
 { "song": 1, "verse": 2, "lineNum": 3, "line": "Bewa bewa"},
 { "song": 1, "verse": 2, "lineNum": 2, "line": "Shinan Kibi"},
 { "song": 2, "verse": 1, "lineNum": 1, "line": "Ramo Kano"},
 { "song": 2, "verse": 2, "lineNum": 1, "line": "Choro Choro"},
 { "song": 3, "verse": 1, "lineNum": 1, "line": "Pisha Pisha"}
];

let verses = {};

input.forEach(o =>
{
    let k = `${o.song}_${o.verse}`;
    verses[k] = verses[k] || {song: o.song, verse: o.verse};
    verses[k]["lines"] = verses[k]["lines"] || [];
    verses[k]["lines"][o.lineNum] = o.line;
});

let res = Object.values(verses).map(
    o => `song ${o.song}, verse ${o.verse}, lines: ${o.lines.filter(Boolean).join(" / ")}`
);

console.log(res);
Shidersz
  • 16,846
  • 2
  • 23
  • 48