【问题标题】:How Traverse nested JSON in order with root node items如何使用根节点项按顺序遍历嵌套 JSON
【发布时间】:2021-01-04 18:48:05
【问题描述】:

我有一个嵌套的 JSON 结构,就像一个播放列表可以有图像、视频,这个播放列表也可以有另一个嵌套的播放列表。

所以我希望得到输出,比如当我从头到尾遍历最顶部的播放列表时,将其嵌套/子播放列表的项目只是落在最顶层循环索引中的项目。

示例一:输入结构及其预期输出:

示例二:输入结构及其预期输出:

到目前为止我已经尝试过了

<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>

注意:调用 API 即 get playlist 返回与示例二匹配的响应

【问题讨论】:

  • 我不太明白你想解决的问题。您可以添加更多详细信息吗?就像在第一个例子中一样 - 你如何决定,你计算了多少轮?最长的播放列表是多少?如果您能更详细地描述它,这将有助于我理解您的问题。
  • maxRound 代表什么?
  • 如果有三个顶级图像而不是两个会发生什么?每轮返回超过 4 个文件吗?
  • @SimonvanEndern 谢谢,是的,根据最长的播放列表,我将编辑我的问题以使其更清晰
  • @D.Seah 在访问所有项目之前,我需要迭代最顶部的播放列表的最大轮数,例如示例一个 maxRound 为 3,示例二为 9,截至目前它是硬编码的而且我不知道我的方法是否正确,或者我将如何计算这个 maxRound 只是我用我的参考示例图像硬编码

标签: javascript algorithm data-structures logic


【解决方案1】:

这是一个简单的递归解决方案

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}

这是动态终止的更新版本

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}

如果不存在 typefile,这可能会严重崩溃。

【讨论】:

  • 很好的答案,只是缺少一件事:来自播放列表 101 的图像 104 不在输出中
  • 我相信你已经明白了;我只是在输出中循环不够远。更新
  • @vincet 再次感谢,还有一个问题,我怎样才能动态获得最大循环计数,而不是硬编码,即目前为 13
  • 更新了,这是你要找的吗?另外我相信这可以优化很多
【解决方案2】:

这是一种不同的方法:

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}

要了解它是如何工作的,我们可以想象如果我们将数据转换成这种格式会是什么样子:

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}

我们的输出只是一个由对qux 的六个单独调用形成的数组。但是qux 只给baz 打了四次电话。这是它变得更有趣的地方。 baz 循环执行四个函数,每次调用都返回下一个函数的结果。这四个函数是() =&gt; 1foo() =&gt; 2barfoobar 的结构与 foo 使用两个函数 () =&gt; 3() =&gt; 4bar 使用三个函数 () =&gt; 5() =&gt; 6() =&gt; 7 的结构相同。

因此,上面的代码旨在将您的数据结构转换为这样的嵌套函数列表。 Central是makePlaylistFn,它创建了我们的主函数(相当于上面的baz,通过将我们的值映射到makeHandler,它区分"playlist""file"输入,递归回调到makePlaylistFn为前者并为后者返回一个简单的函数。生成的函数被传递给makeFn,它将一组函数转换为一个函数数组,在每次调用时循环调用它们。

我们最后用makePlayList 来结束它,它调用makePlaylistFn 来生成根函数,并返回一个接受正整数的函数,返回许多n 的数组调用主函数,其中@ 987654352@ 是最外层播放列表的长度。


这仍然留下了一个有趣的问题。我们调用这个函数多少次?您上面的示例似乎表明您希望在看到每个值后停止。我相信我们可以解决这个问题,尽管这可能很棘手。

另一种可能性是运行此程序,直到您循环完成所有内容并重新开始。这更容易处理,在各种嵌套播放列表上使用最不常见的多重技术。但是对于您的简单案例,需要 6 次调用,而对于更复杂的案例,则需要 12 次。如果你有兴趣,我可以试试。

更新

您想知道如何在播放列表的值之间循环一次。 makePlaylist 的替换应该可以做到:

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)
}

我们从三个数学函数开始,我们需要找到重复周期。我们需要找到整数数组的最小公倍数。这就是lcmAll 例如,lcmAll([12, 15, 20, 35]) 产生420,这些整数的最小公倍数。它是作为最小公倍数 lcm 的简单约简而构建的,而后者又构建在最大公约数 gcd 函数之上。

使用它,我们递归地在period 中找到每个图像/播放列表的 LCM。最后,我们重写makePlaylist 以使用它来确定要创建多少个数组。其余的将保持不变。你可以通过展开这个 sn-p 来查看它的实际效果:

// 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}

【讨论】:

  • 谢谢,Scott,是的,如果我们可以停下来重新开始,这对我会有很大帮助,我仍在努力理解这些事情:D
  • @CMSSignage:添加了一个更新来处理这个问题。
  • 非常感谢先生,老实说,作为一个平庸的开发人员,我需要大约一天的时间来阅读这段代码和解释才能正确理解它,现在,我将用几个测试用例和使用
  • @CMSSignage:很高兴你喜欢它。我认为最棘手的一点是处理makeFn 中的索引i。 (提示:查找闭包,如果您还不了解它们。)如果您能理解该功能,那么我认为其余的应该更容易。
猜你喜欢
  • 2017-06-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多