【问题标题】:Convert a list object into another structure将列表对象转换为另一个结构
【发布时间】:2022-06-10 19:54:53
【问题描述】:

我有一个列表,转换成js数组。几行有一个制表符前缀:

var data = [
    "2",
    "    2.1",
    "        2.1.1",
    "    2.2",
    "3",
    "4"
]

我想要做的是得到以下结构:

var data = [
        "2",
        "2->2.1",
        "2->2.1->2.1.1",
        "2->2.2",
        "3",
        "4"
    ]

试过(产生错误的结果):

for (var i = 0; i < data.length; i++) {
            
            var current = data; 
            var length  = data[i].length - data[i].replaceAll("    ", "").length;
            
            if (!length) {
                console.log(current); 
            } else {
                console.log(data[i-1] + '->' + data[i].trim()); 
            }
}

更新 (@MustSeeMelons) - 您的解决方案在下面附上的测试数据上产生错误结果:

【问题讨论】:

  • 正确,已更新。
  • 你真的需要使用标签吗? 2.1.1 对应于 2-&gt;2.1-&gt;2.1.1 因为它有两个点,不是吗?
  • 是的。我使用制表符作为前缀。

标签: javascript arrays list recursion


【解决方案1】:

平到树

我在this Q&A 中解决了这个问题。我们可以在您的数据上重复使用相同的功能 -

const data = `
2
    2.1
        2.1.1
    2.2
3
4
`
// using makeChildren and sanitize from the linked Q&A
console.log(makeChildren(sanitize(data)))
[
  {
    "value": "2",
    "children": [
      {
        "value": "2.1",
        "children": [
          {
            "value": "2.1.1",
            "children": []
          }
        ]
      },
      {
        "value": "2.2",
        "children": []
      }
    ]
  },
  {
    "value": "3",
    "children": []
  },
  {
    "value": "4",
    "children": []
  }
]

树变平

现在剩下的就是将树转换为 paths 的平面列表 -

function* paths(t) {
  switch (t?.constructor) {
    case Array:
      for (const child of t)
        yield* paths(child)
      break
    case Object:
      yield [t.value]
      for (const path of paths(t.children))
        yield [t.value, ...path]
      break
  }
}
const result =
  Array.from(paths(makeChildren(sanitize(data))), path => path.join("->"))
[
  "2",
  "2->2.1",
  "2->2.1->2.1.1",
  "2->2.2",
  "3",
  "4"
]

优势

将问题分解成更小的部分可以更容易解决并产生可重用的功能,但这些并不是唯一的优势。中间树表示使您能够在平面表示不允许的树的上下文中进行其他修改。此外,paths 函数产生路径段数组,允许调用者决定需要哪种最终效果,即path.join("-&gt;"),或其他。

演示

在你自己的浏览器中运行下面的演示来验证结果 -

const sanitize = (str = "") =>
  str.trim().replace(/\n\s*\n/g, "\n")
  
const makeChildren = (str = "") =>
  str === ""
    ? []
    : str.split(/\n(?!\s)/).map(make1)

const make1 = (str = "") => {
  const [ value, children ] = cut(str, "\n")
  return { value, children: makeChildren(outdent(children)) }
}

const cut = (str = "", char = "") => {
  const pos = str.search(char)
  return pos === -1
    ? [ str, "" ]
    : [ str.substr(0, pos), str.substr(pos + 1) ]
}

const outdent = (str = "") => {
  const spaces = Math.max(0, str.search(/\S/))
  const re = new RegExp(`(^|\n)\\s{${spaces}}`, "g")
  return str.replace(re, "$1")
}

function* paths(t) {
  switch (t?.constructor) {
    case Array: for (const child of t) yield* paths(child); break
    case Object: yield [t.value];  for (const path of paths(t.children)) yield [t.value, ...path]; break
  }
}

const data = `\n2\n\t2.1\n\t\n\t2.1.1\n\t2.2\n3\n4`

console.log(
  Array.from(paths(makeChildren(sanitize(data))), path => path.join("->"))
)
.as-console-wrapper { min-height: 100%; top: 0; }

备注

outdent 是通用的,无论您使用文字制表符、\t \t\t \t\t\t... 还是一些空格都可以使用。重要的是空格是一致的。查看original Q&A 以更深入地了解每个部分的工作原理。

【讨论】:

    【解决方案2】:

    考虑到tab由四个空格组成,并且数据有零个或多个完整的制表符,则可以通过reduceRight()map()的组合来解决。

    方法#1:

    这个想法是通过选项卡分割每个数据字符串,即,

    \t\t2.1.1
    

    放入长度为 nº tabs+1 的数组

    ['', '', 2.1.1] 
      0   1    2
    

    这样它就可以构建一个结构,将数组中的每个空条目“替换”为从右到左最后一个数字上方的级别,考虑到分隔符.(点)。

    [ '',    '', '2.1.1']
    [ '', '2.1', '2.1.1']  
    ['2', '2.1', '2.1.1'] 
       "2->2.1->2.1.1"
    

    从例子中可以看出:

    let data = ["2","\t2.1","\t\t2.1.1","\t2.2","3","4"], tab = "\t"
    
    let output = data.map(x => x.includes(tab) ?
        x.split(tab)
            .reduceRight((acc, curr, idx) =>
                (
                    curr === "" ?
                        acc.split('.', idx + 1).join('.') :
                        curr
                ) + '->' + acc
            )
        : x)
        
    console.log(output)

    方法 #2:

    如果允许您使用辅助空间,请创建一个数组来跟踪行的最新级别。它不是基于分隔符的。

    let data = ["2","\t2.1","\t\t2.1.1","\t2.2","3","4"], tab="\t", latest = [];
    
    let output = data.map(x => { 
       let noTabs = x.split(tab).length - 1;
       if(latest.length > noTabs) latest.splice(noTabs)
       latest.push(x.replaceAll(tab, ''));
       return latest.join('->')
    })
        
    console.log(output)

    【讨论】:

    • 一个实际的标签是\t,而不是“由4个空格组成”
    • 不错的解决方案,但您将数字拆分为 (.)。但数据可能不仅仅是数字。目前它仅适用于特定数据。
    • 检查第二个解决方案,看看是不是你问的
    【解决方案3】:

    这几乎可以满足您的要求,但不确定您要如何对它们进行分组:

    const mapped = data.reduce((acc, curr, idx) => {
      if (idx !== 0) {
        // Get leading digit of current & previous element
        const current = curr.trim()[0];
        const previous = acc[idx - 1].trim()[0];
    
        // If leading match, aggregate them
        if (current === previous) {
          acc.push(`${acc[idx - 1].trim()}->${curr.trim()}`);
        } else {
          acc.push(curr.trim());
        }
      } else {
        acc.push(curr.trim());
      }
    
      return acc;
    }, []);
    

    不要使用for 循环,除非您需要在某个时候跳出循环。转换数组通常应该使用map 函数来完成。 我使用了reduce,因为这个问题需要我访问新的、已经映射的元素。

    【讨论】:

    • 请看@更新的问题
    猜你喜欢
    • 2013-08-14
    • 2015-09-03
    • 1970-01-01
    • 1970-01-01
    • 2014-07-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多