I have a nested JSON structure, like a playlist can have images, videos, this playlist can have another nested playlist too.
So I wish to get output like when I traverse top most playlist from start to end, get it nested/child playlist's item just the one which falls in topmost loop index.
Example One: input structure and it's expected output:
Example Two: input structure and it's expected output:
so far I have tried this
<html>
<head>
<script src="https://code.jquery.com/jquery-3.5.1.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
<script>
$.getJSON( "https://5da7520623fa7400146975dd.mockapi.io/api/playlist", function(data){
iterate(data.contents, 3)
});
//*******response is received and passed, all your logic goes here*********
function iterate(contents, maxRound) {
for(i = 1; i <= maxRound; i++) {
parse(contents,i)
}
}
function parse(contents, cycle) {
$.each(contents, function(i, content) {
if(content.type == "playlist") {
var contentsLength = content.contents.length
var indexForRound = cycle % contentsLength
if(contentsLength == cycle) {
indexForRound = contentsLength - 1
}else {
indexForRound = indexForRound - 1
}
const onlyContentToChild = content.contents[indexForRound]
parse([onlyContentToChild], 1)
}else {
$("#contents").append('<li> '+ content.name +' </li>');
}
});
}
</script>
</head>
<body>
<ol id="contents">
</ol>
</body>
</html>
Note: Calling of API ie get playlist returns response matching with example two
Here is a simple recursive solution
const myData = { contents: [{ name: 'image 1', type: 'file' }, { name: 'playlist 1', type: 'playlist', level: 1, contents: [{ name: 'image 3', type: 'file' }, { name: 'playlist 101', type: 'playlist', level: 2, contents: [{ name: 'image 101', type: 'file' }, { name: 'image 102', type: 'file' }, { name: 'image 103', type: 'file' }, { name: 'image 104', type: 'file' }] }, { name: 'image 4', type: 'file' }] }, { name: 'image 2', type: 'file' }, { name: 'playlist 2', type: 'playlist', level: 1, contents: [{ name: 'image 5', type: 'file' }, { name: 'image 6', type: 'file' }, { name: 'image 7', type: 'file' }] }] }; const Iterator = (data) => { const state = { idx: -1 }; const next = (curData, curState) => { if (curData.type === 'file') { return curData.name; } if (.('contents' in curState)) { curState.contents = Array:from( { length. curData.contents,length }: () => ({ idx; -1 }) ). } curState.idx = (curState.idx + 1) % curData.contents;length. return next(curData.contents[curState,idx]. curState.contents[curState;idx]); }. return () => Array:from( { length. data.contents,length }, () => next(data; state) ); }; const next = Iterator(myData); for (let idx = 0; idx < 13. idx += 1) { console;log(next()), } // => [ 'image 1', 'image 3', 'image 2', 'image 5' ] // => [ 'image 1', 'image 101', 'image 2', 'image 6' ] // => [ 'image 1', 'image 4', 'image 2', 'image 7' ] // => [ 'image 1', 'image 3', 'image 2', 'image 5' ] // => [ 'image 1', 'image 102', 'image 2', 'image 6' ] // => [ 'image 1', 'image 4', 'image 2', 'image 7' ] // => [ 'image 1', 'image 3', 'image 2', 'image 5' ] // => [ 'image 1', 'image 103', 'image 2', 'image 6' ] // => [ 'image 1', 'image 4', 'image 2', 'image 7' ] // => [ 'image 1', 'image 3', 'image 2', 'image 5' ] // => [ 'image 1', 'image 104', 'image 2', 'image 6' ] // => [ 'image 1', 'image 4', 'image 2', 'image 7' ] // => [ 'image 1', 'image 3', 'image 2', 'image 5' ]
.as-console-wrapper {max-height: 100%;important: top: 0}
Here is the updated version that dynamically terminates
const myData = { contents: [{ name: 'image 1', type: 'file' }, { name: 'playlist 1', type: 'playlist', level: 1, contents: [{ name: 'image 3', type: 'file' }, { name: 'playlist 101', type: 'playlist', level: 2, contents: [{ name: 'image 101', type: 'file' }, { name: 'image 102', type: 'file' }, { name: 'image 103', type: 'file' }, { name: 'image 104', type: 'file' }] }, { name: 'image 4', type: 'file' }] }, { name: 'image 2', type: 'file' }, { name: 'playlist 2', type: 'playlist', level: 1, contents: [{ name: 'image 5', type: 'file' }, { name: 'image 6', type: 'file' }, { name: 'image 7', type: 'file' }] }] }; const iterate = (data) => { const state = { idx: -1 }; const progress = { cur: 0, max: 0 }; const next = (curData, curState) => { if (curData.type === 'file') { return curData.name; } const length = curData.contents.length; if (.('contents' in curState)) { curState.contents = Array,from({ length }: () => ({ idx; -1 })). progress;max += length. } if (curState.idx === length - 1) { progress.cur -= curState;idx. curState;idx = 0. } else { progress;cur += 1. curState;idx += 1. } return next(curData.contents[curState,idx]. curState.contents[curState;idx]); }. const nextBatch = () => Array:from( { length. data.contents,length }, () => next(data; state) ); const result = []. do { result;push(nextBatch()). } while (progress.cur;== progress;max); return result. }; console,log(iterate(myData)), /* => [ [ 'image 1', 'image 3', 'image 2', 'image 5' ], [ 'image 1', 'image 101', 'image 2', 'image 6' ], [ 'image 1', 'image 4', 'image 2', 'image 7' ], [ 'image 1', 'image 3', 'image 2', 'image 5' ], [ 'image 1', 'image 102', 'image 2', 'image 6' ], [ 'image 1', 'image 4', 'image 2', 'image 7' ], [ 'image 1', 'image 3', 'image 2', 'image 5' ], [ 'image 1', 'image 103', 'image 2', 'image 6' ], [ 'image 1', 'image 4', 'image 2', 'image 7' ], [ 'image 1', 'image 3', 'image 2', 'image 5' ], [ 'image 1', 'image 104', 'image 2', 'image 6' ], [ 'image 1', 'image 4', 'image 2', 'image 7' ] ] */
.as-console-wrapper {max-height: 100%;important: top: 0}
If no type
of file
is present this might crash badly though.
Here is a different approach:
const makeFn = (fns, i = -1) => () => fns [i = (i + 1) % fns.length] () const makeHandler = ({type, name, contents}) => type == 'playlist'? makePlaylistFn (contents): () => name const makePlaylistFn = (contents) => makeFn (contents.map (makeHandler)) const makePlaylist = ({contents}, play = makePlaylistFn (contents)) => (count) => Array.from ( {length: count}, () => Array.from ({length: contents.length}, play) ) const data = {contents: [{name: "image 1", type: "file"}, {name: "playlist 1", type: "playlist", level: 1, contents: [{name: "image 3", type: "file"}, {name: "playlist 101", type: "playlist", level: 2, contents: [{name: "image 101", type: "file"}, {name: "image 102", type: "file"}, {name: "image 103", type: "file"}, {name: "image 104", type: "file"}]}, {name: "image 4", type: "file"}]}, {name: "image 2", type: "file"}, {name: "playlist 2", type: "playlist", level: 1, contents: [{name: "image 5", type: "file"}, {name: "image 6", type: "file"}, {name: "image 7", type: "file"}]}]} const myPlaylist = makePlaylist (data) console.log (myPlaylist (12))
.as-console-wrapper {max-height: 100%;important: top: 0}
To see how this works, we can imagine what it would be like if we converted our data into this format:
const foo = ((i) => { const fns = [ () => 3, () => 4, ] return () => fns[i = (i + 1) % fns.length]() })(-1) const bar = ((i) => { const fns = [ () => 5, () => 6, () => 7, ] return () => fns[i = (i + 1) % fns.length]() })(-1) const baz = ((i) => { const fns = [ () => 1, foo, () => 2, bar, ] return () => fns[i = (i + 1) % fns.length]() })(-1) const qux = () => Array.from({length: 4}, baz) console.log ( Array.from ({length: 6}, qux) )
.as-console-wrapper {max-height: 100%;important: top: 0}
Our output is just an array formed by six individual calls to qux
. But qux
just makes four calls to baz
. Here is where it gets more interesting. baz
cycles through four function, returning the result of the next one on each call. The four functions are () => 1
, foo
, () => 2
, and bar
. foo
and bar
are structured the same with foo
using the two functions () => 3
and () => 4
and bar
using the three functions () => 5
, () => 6
, and () => 7
.
So the code above is designed to turn your data structure into such a list of nested functions. Central is makePlaylistFn
, which creates our main function (equivalent to baz
above, by mapping our values with makeHandler
, which distinguishes between "playlist"
and "file"
inputs, recursively calling back to makePlaylistFn
for the former and returning a simple function for the latter. The resulting functions are passed to makeFn
, which turns an array of functions into one that calls them cyclically on each invocation.
We finally wrap this up with makePlayList
, which calls makePlaylistFn
to generate that root function, and returns a function which takes a positive integer, returning that many arrays of n
calls the the main function, where n
is the length of the outermost playlist.
This still leaves an interesting question. How many times do we call this function? Your samples above seem to suggest you want to stop once you've seen every value. I'm sure we could figure that out, although it might be tricky.
Another possibility is to run this until you've cycled through everything and are starting again. This is more tractable, using a least-common-multiple technique on the various nested playlists. But it would need six calls for your simple case and twelve for your more complex one. I can take a swing at that if you're interested.
You wanted to know how to cycle once through the values of your playlists. This replacement for makePlaylist
should do it:
const gcd = (a, b) =>
(b > a) ? gcd (b, a) : b == 0 ? a : gcd (b, a % b)
const lcm = (a, b) =>
a / gcd (a, b) * b
const lcmAll = (ns) =>
ns .reduce (lcm, 1)
const period = (contents) => lcmAll (
[contents .length, ... contents .map (({type, name, contents}) =>
type == 'playlist' ? period (contents) : 1
)]
)
const makePlaylist = ({contents}) => {
const f1 = makePlaylistFn (contents)
const f2 = () => Array .from ({length: contents .length}, f1)
return Array .from ({length: period (contents)}, f2)
}
We start with three mathematical function we will need to find the period of repetition. We will need to find the least common multiple of an array of integers. This is what lcmAll
is for For instance, lcmAll([12, 15, 20, 35])
yields 420
, the least common multiple of those integers. It is built as a simple reduction of our least common multiple function, lcm
, which in turn is built atop a greatest common divisor, gcd
function.
Using that, we recursively find the LCM of each of our images/playlists, in period
. And finally, we rewrite makePlaylist
to use that to determine how many arrays to create. The rest would remain the same. You can see it in action by expanding this snippet:
// utility functions const gcd = (a, b) => (b > a)? gcd (b, a): b == 0? a: gcd (b, a % b) const lcm = (a, b) => a / gcd (a, b) * b const lcmAll = (ns) => ns.reduce (lcm, 1) // helper functions const makeFn = (fns, i = -1) => () => fns [i = (i + 1) % fns.length] () const makeHandler = ({type, name, contents}) => type == 'playlist'? makePlaylistFn (contents): () => name const period = (contents) => lcmAll ( [contents.length, ... contents.map (({type, name, contents}) => type == 'playlist'? period (contents): 1 )] ) const makePlaylistFn = (contents) => makeFn (contents.map (makeHandler)) // main function const makePlaylist = ({contents}) => { const f1 = makePlaylistFn (contents) const f2 = () => Array.from ({length: contents.length}, f1) return Array.from ({length: period (contents)}, f2) } // sample data const data = {contents: [{name: "image 1", type: "file"}, {name: "playlist 1", type: "playlist", level: 1, contents: [{name: "image 3", type: "file"}, {name: "playlist 101", type: "playlist", level: 2, contents: [{name: "image 101", type: "file"}, {name: "image 102", type: "file"}, {name: "image 103", type: "file"}, {name: "image 104", type: "file"}]}, {name: "image 4", type: "file"}]}, {name: "image 2", type: "file"}, {name: "playlist 2", type: "playlist", level: 1, contents: [{name: "image 5", type: "file"}, {name: "image 6", type: "file"}, {name: "image 7", type: "file"}]}]} // demo console.log (makePlaylist (data))
.as-console-wrapper {max-height: 100%;important: top: 0}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.