【问题标题】:Javascript pushing element in array like chasing tailJavascript在数组中推送元素,如追尾
【发布时间】:2021-11-06 17:19:47
【问题描述】:

我想从输入中获取输出。
它应该像追尾一样被推动元素,并且不能被复制。
规则很简单。 首先,如果input[i][0]中有与input[0][1]相同的元素, 您可以将元素的第一个值放入结果中。
元素为 [1,5],因此结果变为 [[0,1]]。
现在你可以重复了。 [5,29]的第一个元素和[1,5][1]是同一个元素,结果变成[[0,1,5]]

几周来我一直很沮丧,无法解决这个问题。请帮忙。任何 cmets 将不胜感激。

input = 
[
  [ 0, 1 ],   [ 0, 2 ],   [ 0, 3 ],
  [ 0, 4 ],   [ 1, 5 ],   [ 2, 6 ],
  [ 3, 7 ],   [ 4, 10 ],  [ 4, 11 ],
  [ 4, 12 ],  [ 4, 13 ],  [ 5, 29 ],
  [ 6, 29 ],  [ 7, 8 ],   [ 8, 29 ],
  [ 9, 29 ],  [ 12, 18 ], [ 13, 19 ],
  [ 17, 29 ], [ 18, 29 ], [ 19, 29 ],
  [ 21, 29 ], [ 24, 29 ], [ 26, 29 ],
  [ 28, 29 ]
]

output = [
 [0,1,5,29],[0,2,6,29],[0,3,7,8,29],[0,4,10],[0,4,11],
 [0,4,12,18,29],[0,4,13,19,29]
]
       

【问题讨论】:

标签: javascript arrays recursion iteration


【解决方案1】:

根据我对您的输出的理解,您想要的有点像多米诺链。您使用数组开始在第一个元素中包含 0 的输出播种,例如[0,1][0,2]...,然后您只需根据数组中的最后一项与下一项中的第一项找到下一个要加入链的数组。

此过程本质上是递归,因为您从种子开始,不知道您需要加入多远/多深。为了打破这一点,我们可以这样做:

  1. input 分为种子和非种子(节点)。
  2. 从种子开始
  3. 遍历种子并调用一个函数,该函数将递归地将节点添加到您的种子中。诀窍是当可以找到 多个 路径时,您要复制种子数组

函数应该递归调用自己,这里有一个快速的逻辑:

/**
 * @method
 * @params arr    The seed array (which will grow in length)
 * @params nodes  The collection of non-seeds
 */
function chain(arr, nodes) {
  // We first find whatever nodes left that we can domino-chain to the current seed
  const nextNodes = nodes.filter(node => node[0] === arr[arr.length - 1]);

  // If nothing is to be found, return the array
  if (!nextNodes.length) return [arr];

  // Otherwise we go through all the next nodes and create a copy of the current seed
  // And then append the next node to it
  return nextNodes.map(nextNode => {
    return chain([...arr, nextNode[1]], nodes);
  }).flat();
}

请参阅下面的概念验证:

const input = [
  [0, 1],
  [0, 2],
  [0, 3],
  [0, 4],
  [1, 5],
  [2, 6],
  [3, 7],
  [4, 10],
  [4, 11],
  [4, 12],
  [4, 13],
  [5, 29],
  [6, 29],
  [7, 8],
  [8, 29],
  [9, 29],
  [12, 18],
  [13, 19],
  [17, 29],
  [18, 29],
  [19, 29],
  [21, 29],
  [24, 29],
  [26, 29],
  [28, 29]
];

const seeds = input.filter(entry => entry[0] === 0);
const nodes = input.filter(entry => entry[0] !== 0);

function chain(arr, nodes) {
  const nextNodes = nodes.filter(node => node[0] === arr[arr.length - 1]);

  if (!nextNodes.length) return [arr];

  return nextNodes.map(nextNode => {
    return chain([...arr, nextNode[1]], nodes);
  }).flat();
}

const output = seeds.map(seed => {
  return chain(seed, nodes);
}).flat();

console.log(output);

【讨论】:

  • 非常感谢您的帮助。我一直很难使用递归。您清晰的代码和详细的描述确实对我有很大帮助。太感谢了!周末愉快!!
【解决方案2】:

我会将问题拆分为 2:

  1. 从您的列表中创建一个树状结构。树中的每个节点都包含对所有可能的下一个节点的引用。
  2. 编写一个遍历树以查找所有可能路径的递归函数

这是该方法的一个实现:

const input = 
  [
    [ 0, 1 ],   [ 0, 2 ],   [ 0, 3 ],
    [ 0, 4 ],   [ 1, 5 ],   [ 2, 6 ],
    [ 3, 7 ],   [ 4, 10 ],  [ 4, 11 ],
    [ 4, 12 ],  [ 4, 13 ],  [ 5, 29 ],
    [ 6, 29 ],  [ 7, 8 ],   [ 8, 29 ],
    [ 9, 29 ],  [ 12, 18 ], [ 13, 19 ],
    [ 17, 29 ], [ 18, 29 ], [ 19, 29 ],
    [ 21, 29 ], [ 24, 29 ], [ 26, 29 ],
    [ 28, 29 ]
  ];
  
  
  
const nodes = {};

// Store all paths between nodes
for (const [ start, end ] of input) {
  nodes[end] = nodes[end] || { id: end, children: [] };
  nodes[start] = nodes[start] || { id: start, children: [] };
  
  nodes[start].children.push(nodes[end]);
}

// Find all paths from 0
const getPaths = ({ children, id }) => children.length === 0
  ? [[ id ]]
  : children.flatMap(
      n => getPaths(n).map(p => [ id, ...p ])
    )

console.log(getPaths(nodes[0]));

【讨论】:

  • 非常感谢您的帮助。是的,它需要像树一样的递归和结构。尽管您说可能需要重构,但代码看起来非常清晰。太感谢了!周末愉快!!
【解决方案3】:

你也可以用一个循环来做到这一点,并遵循你的规则:

  1. 如果edge0 开头(start),只需将其推入result
  2. 否则尝试在包含startresult 中查找已经存在的path
  3. 如果现有的pathstart 结尾,只需附加edge 的结束位置
  4. 如果现有的path 在内部某处包含start,则将path 的副本复制到positionresult,并附加edge 的结束位置。

let input = [[0, 1], [0, 2], [0, 3], [0, 4], [1, 5], [2, 6], [3, 7], [4, 10], [4, 11],
            [4, 12], [4, 13], [5, 29], [6, 29], [7, 8], [8, 29], [9, 29], [12, 18],
            [13, 19], [17, 29], [18, 29], [19, 29], [21, 29], [24, 29], [26, 29],
            [28, 29]];

let result = [];
for (let edge of input) {
  let start = edge[0];
  if (start === 0) {                            // <--- 1. (0 => push for sure)
    result.push(edge);
  } else {
    for (let path of result) {
      let position = path.indexOf(start);
      if (position > 0) {                       // <--- 2. (can't be 0, but >= would work too)
        if (position !== path.length - 1) {     // <--- 4. (need a copy)
          path = path.slice(0, position + 1);
          result.push(path);
        }
        path.push(edge[1]);                     // <--- 3. 4. (whatever path is)
        break;
      }
    }
  }
}
console.log(JSON.stringify(result));

JSON.stringify() 仅用于格式化)

【讨论】:

  • 非常感谢您的帮助!使用迭代对我来说比使用递归更舒服。所以你的代码对我很有帮助。非常感谢!
【解决方案4】:

您可以将solver 编写为生成器,输入为t,起始查询为q。其他答案建议重塑您的输入或其他多次迭代输入的功能技术。这种简单的命令式技术只使用一次(每次递归调用)。使用生成器可以找到所有解决方案,但可以随时暂停/停止,出于任何其他原因 -

function* solver (t, q) {
  let atLeastOnce = false
  for (const [parent, child] of t) {
    if (parent == q) {
      atLeastOnce = true
      for (const sln of solver(t, child))
        yield [parent, ...sln]
    }
  }
  if (!atLeastOnce) {
    yield [q]
  }
}

const input =
  [[0,1],[0,2],[0,3],[0,4],[1,5],[2,6],[3,7],[4,10],[4,11],[4,12],[4,13],[5,29],[6,29],[7,8],[8,29],[9,29],[12,18],[13,19],[17,29],[18,29],[19,29],[21,29],[24,29],[26,29],[28,29]]

for (const sln of solver(input, 0))
  console.log(JSON.stringify(sln))
[0,1,5,29]
[0,2,6,29]
[0,3,7,8,29]
[0,4,10]
[0,4,11]
[0,4,12,18,29]
[0,4,13,19,29]

生成器是 iterable,因此您可以使用 Array.from 将所有结果收集到一个数组中 -

const all = Array.from(solver(input, 0))
console.log(all)
[
  [0,1,5,29],
  [0,2,6,29],
  [0,3,7,8,29],
  [0,4,10],
  [0,4,11],
  [0,4,12,18,29],
  [0,4,13,19,29],
]

将生成器与优化的输入类型相结合以获得更好的结果。

【讨论】:

  • 非常感谢您的帮助!我从来没有想过使用生成器来解决这个问题。看起来很神奇。感谢您介绍一种新方法!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-12-30
  • 2017-12-11
  • 1970-01-01
  • 2022-06-15
  • 2017-06-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多