为什么我认为数据结构与算法对前端开发很重要? #2

Open
LeuisKen opened this Issue May 13, 2016 · 15 comments

Comments

Projects
None yet
@LeuisKen
Owner

LeuisKen commented May 13, 2016

从一个需求谈起

在我之前的项目中,曾经遇到过这样一个需求,编写一个级联选择器,大概是这样:

1

图中的示例使用的是Ant-Design的Cascader组件。

要实现这一功能,我需要类似这样的数据结构:

var data = [{
  "value": "浙江",
  "children": [{
    "value": "杭州",
    "children": [{
      "value": "西湖"
    }]
  }]
}, {
  "value": "四川",
  "children": [{
    "value": "成都",
    "children": [{
      "value": "锦里"
    }, {
      "value": "方所"
    }]
  }, {
    "value": "阿坝",
    "children": [{
      "value": "九寨沟"
    }]
  }]
}]

一个具有层级结构的数据,实现这个功能非常容易,因为这个结构和组件的结构是一致的,递归遍历就可以了。

但是,由于后端通常采用的是关系型数据库,所以返回的数据通常会是这个样子:

var data = [{
  "province": "浙江",
  "city": "杭州",
  "name": "西湖"
}, {
  "province": "四川",
  "city": "成都",
  "name": "锦里"
}, {
  "province": "四川",
  "city": "成都",
  "name": "方所"
}, {
  "province": "四川",
  "city": "阿坝",
  "name": "九寨沟"
}]

前端这边想要将数据转换一下其实也不难,因为要合并重复项,可以参考数据去重的方法来做,于是我写了这样一个版本。

'use strict'

/**
 * 将一个没有层级的扁平对象,转换为树形结构({value, children})结构的对象
 * @param {array} tableData - 一个由对象构成的数组,里面的对象都是扁平的
 * @param {array} route - 一个由字符串构成的数组,字符串为前一数组中对象的key,最终
 * 输出的对象层级顺序为keys中字符串key的顺序
 * @return {array} 保存具有树形结构的对象
 */

var transObject = function(tableData, keys) {
  let hashTable = {}, res = []
  for( let i = 0; i < tableData.length; i++ ) {
    if(!hashTable[tableData[i][keys[0]]]) {
      let len = res.push({
        value: tableData[i][keys[0]],
        children: []
      })
      // 在这里要保存key对应的数组序号,不然还要涉及到查找
      hashTable[tableData[i][keys[0]]] = { $$pos: len - 1 }
    }
    if(!hashTable[tableData[i][keys[0]]][tableData[i][keys[1]]]) {
      let len = res[hashTable[tableData[i][keys[0]]].$$pos].children.push({
        value: tableData[i][keys[1]],
        children: []
      })
      hashTable[tableData[i][keys[0]]][tableData[i][keys[1]]] = { $$pos: len - 1 }
    }
    res[hashTable[tableData[i][keys[0]]].$$pos].children[hashTable[tableData[i][keys[0]]][tableData[i][keys[1]]].$$pos].children.push({
      value: tableData[i][keys[2]]
    })
  }
  return res
}

var data = [{
  "province": "浙江",
  "city": "杭州",
  "name": "西湖"
}, {
  "province": "四川",
  "city": "成都",
  "name": "锦里"
}, {
  "province": "四川",
  "city": "成都",
  "name": "方所"
}, {
  "province": "四川",
  "city": "阿坝",
  "name": "九寨沟"
}]

var keys = ['province', 'city', 'name']

console.log(transObject(data, keys))

还好keys的长度只有3,这种东西长了根本没办法写,很明显可以看出来这里面有重复的部分,可以通过循环搞定,但是想了很久都没有思路,就搁置了。

后来,有一天晚饭后不是很忙,就跟旁边做数据的同事聊了一下这个需求,请教一下该怎么用循环来处理。他看了一下,就问我:“你知道trie树吗?”。我头一次听到这个概念,他简单的给我讲了一下,然后说感觉处理的问题有些类似,让我可以研究一下trie树的原理并试着优化一下。

讲道理,trie树这个数据结构网上确实有很多资料,但很少有使用JavaScript实现的,不过原理倒是不难。尝试之后,我就将transObject的代码优化成了这样。(关于trie树,还请读者自己阅读相关材料)

var transObject = function(tableData, keys) {
  let hashTable = {}, res = []
  for (let i = 0; i < tableData.length; i++) {
    let arr = res, cur = hashTable
    for (let j = 0; j < keys.length; j++) {
      let key = keys[j], filed = tableData[i][key]
      if (!cur[filed]) {
        let pusher = {
          value: filed
        }, tmp
        if (j !== (keys.length - 1)) {
          tmp = []
          pusher.children = tmp
        }
        cur[filed] = { $$pos: arr.push(pusher) - 1 }
        cur = cur[filed]
        arr = tmp
      } else {
        cur = cur[filed]
        arr = arr[cur.$$pos].children
      }
    }
  }
  return res
}

这样,解决方案就和keys的长短无关了。

这大概是我第一次,真正将数据结构的知识和前端项目需求结合在一起。

再谈谈我在面试遇到的问题

目前为止我参加过几次前端开发方面的面试,确实有不少面试官会问道一些算法。通常会涉及的,是链表、树、字符串、数组相关的知识。前端面试对算法要求不高,似乎已经是业内的一种共识了。虽说算法好的前端面试肯定会加分,但是仅凭常见的面试题,而不去联系需求,很难让人觉得,算法对于前端真的很重要。

直到有一天,有一位面试官问我这样一个问题,下面我按照自己的回忆把对话模拟出来,A指面试官,B指我:

A:你有写过瀑布流吗?

B:我写过等宽瀑布流。实现是当用户拉到底部的一定高度的时候,向后端请求一定数量的图片,然后再插入到页面中。

A:那我问一下,如何让几列图片之间的高度差最小?

B:这个需要后端发来的数据里面有图片的高度,然后我就可以看当前高度最小的是哪里列,将新图片插入那一列,然后再看看新的高度最小的是哪一列。

A:我觉得你没有理解我的问题,我的意思是如何给后端发来的图片排序,让几列图片之间的高度差最小?

B:(想了一段时间)对不起,这个问题我没有思路。

A:你是软件工程专业的对吧?你们数据结构课有没有学动态规划?

B:可能有讲吧,但是我没什么印象了。

对话大概就是这样,虽然面试最终还是pass了,但这个问题确实让我很在意,因为我觉得,高度差“最”小,真的能用很简单的算法就解决吗?

这个问题的实质,其实就是有一个数组,将数组元素分成n份,每份所有元素求和,如何使每份的和的差最小。

搜索上面这个问题,很快就能找到相关的解答,很基本的一类动态规划问题——背包问题。

之前我确实看过背包问题的相关概念(也仅仅是相关概念)。当时我看到这样一段话:

许多使用递归去解决的编程问题,可以重写为使用动态规划的技巧去解决。动态规划方案通常会使用一个数组来建立一张表,用于存放被分解成众多子问题的解。当算法执行完毕,最终的解将会在这个表中很明显的地方被找到。

后面是一个用动态规划重写斐波那契数列的例子。我看到它只是将递归的结果,保存在了一个数组中,就天真的以为动态规划是优化递归的一种方法,并没有深入去理解。

不求甚解,确实早晚会出问题的。当时我虽然以为自己知道了算法的重要性,但其实还是太年轻。

动态规划可以求解一类“最优解”问题,这在某种程度上让我耳目一新。由于本文主要还是为了说明数据结构与算法对于前端的意义,关于动态规划的细节,本文也不会涉及,而且水平确实也不够。网上有许多非常好的博文,尤其推荐《背包九讲》。

多说两句——一道思考题

将如下扁平对象,转为树形对象。parent字段为空字符串的节点为根节点:

var input = {
  h3: {
    parent: 'h2',
    name: '副总经理(市场)'
  },
  h1: {
    parent: 'h0',
    name: '公司机构'
  },
  h7: {
    parent: 'h6',
    name: '副总经理(总务)'
  },
  h4: {
    parent: 'h3',
    name: '销售经理'
  },
  h2: {
    parent: 'h1',
    name: '总经理'
  },
  h8: {
    parent: 'h0',
    name: '财务总监'
  },
  h6: {
    parent: 'h4',
    name: '仓管总监'
  },
  h5: {
    parent: 'h4',
    name: '销售代表'
  },
  h0: {
    parent: '',
    name: 'root'
  }
};

这个需求在前端其实也很实际,示例中的对象是一个公司组织结构图。如果需求是让你在前端用svg之类的技术画出这样一张图,就需要这个功能。(另外我想到的一种应用场景,就是在前端展示类似windows资源管理器的文件树)

我当时想了很久,没有想到一个循环解决的方法,后来在stackoverflow上找到了答案:

var plain2Tree = function (obj) {
  var key, res
  for(key in obj) {
    var parent = obj[key].parent
    if(parent === '') {
      res = obj[key]
    } else {
      obj[parent][key] = obj[key]
    }
  }
  return res
}

这段代码,就是利用了JavaScript里面的引用类型,之后的思路,和操作指针没什么区别,就是构造一棵树。

但对于我来说,从来都没有往树和指针的那方面思考,就很被动了。

结语

以上列举了三道题,希望可以引起大家对于在前端应用数据结构与算法相关知识的共鸣。

@fengmk2

This comment has been minimized.

Show comment
Hide comment
@fengmk2

fengmk2 Jun 17, 2016

还能更加极致地让服务端通过 SQL 来实现了,这样传输给前端的数据量也大大减少了。

SQL 无法实现的话,建议这个trie树由服务端来实现数据的转换。

fengmk2 commented Jun 17, 2016

还能更加极致地让服务端通过 SQL 来实现了,这样传输给前端的数据量也大大减少了。

SQL 无法实现的话,建议这个trie树由服务端来实现数据的转换。

@LeuisKen

This comment has been minimized.

Show comment
Hide comment
@LeuisKen

LeuisKen Jun 18, 2016

Owner

@fengmk2 如果只是这一个地方使用这一接口的话,传来的数据确实过多了。其实除了这个选择外,还有一个完整列表的展示需求,也就是说,完整的数据肯定是要发送过来的。这种情况下,转换的逻辑放在前端应该还是更合理一点,毕竟后端可以少一个接口。

Owner

LeuisKen commented Jun 18, 2016

@fengmk2 如果只是这一个地方使用这一接口的话,传来的数据确实过多了。其实除了这个选择外,还有一个完整列表的展示需求,也就是说,完整的数据肯定是要发送过来的。这种情况下,转换的逻辑放在前端应该还是更合理一点,毕竟后端可以少一个接口。

@fengmk2

This comment has been minimized.

Show comment
Hide comment
@fengmk2

fengmk2 Jun 19, 2016

对于整个页面来说,最好只请求服务端一次就将这个页面需要的数据都一次性返回了。

Sent from my iPhone

On Jun 18, 2016, at 6:10 PM, 魏嘉汛 notifications@github.com wrote:

@fengmk2 如果只是这一个地方使用这一接口的话,传来的数据确实过多了。其实除了这个选择外,还有一个完整列表的展示需求,也就是说,完整的数据肯定是要发送过来的。这种情况下,转换的逻辑放在前端应该还是更合理一点,毕竟后端可以少一个接口。


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or mute the thread.

fengmk2 commented Jun 19, 2016

对于整个页面来说,最好只请求服务端一次就将这个页面需要的数据都一次性返回了。

Sent from my iPhone

On Jun 18, 2016, at 6:10 PM, 魏嘉汛 notifications@github.com wrote:

@fengmk2 如果只是这一个地方使用这一接口的话,传来的数据确实过多了。其实除了这个选择外,还有一个完整列表的展示需求,也就是说,完整的数据肯定是要发送过来的。这种情况下,转换的逻辑放在前端应该还是更合理一点,毕竟后端可以少一个接口。


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or mute the thread.

@majunbao

This comment has been minimized.

Show comment
Hide comment
@majunbao

majunbao Mar 19, 2017

非常赞同你的看法,目前做的项目处处都是数据结构和算法,而且还有设计模式。

非常赞同你的看法,目前做的项目处处都是数据结构和算法,而且还有设计模式。

@yhhwpp

This comment has been minimized.

Show comment
Hide comment
@yhhwpp

yhhwpp Jun 22, 2017

前端确实得学习一点儿算法的知识。

yhhwpp commented Jun 22, 2017

前端确实得学习一点儿算法的知识。

@jinyang1994

This comment has been minimized.

Show comment
Hide comment
@jinyang1994

jinyang1994 Jun 22, 2017

非常赞同!不把数据结构和算法当基础的,都是耍流氓

非常赞同!不把数据结构和算法当基础的,都是耍流氓

@youngwind youngwind referenced this issue in tiodot/tiodot.github.io Aug 11, 2017

Open

瀑布流布局之multi-columns实现 #24

@Lucifier129

This comment has been minimized.

Show comment
Hide comment
@Lucifier129

Lucifier129 Sep 7, 2017

listToTree 的,可以用递归组合的方式来处理。

var data = [
  {
    province: "浙江",
    city: "杭州",
    name: "西湖",
    area: 'A区'
  },
  {
    province: "四川",
    city: "成都",
    name: "锦里",
    area: 'D区'
  },
  {
    province: "四川",
    city: "成都",
    name: "方所",
    area: 'B区'
  },
  {
    province: "四川",
    city: "阿坝",
    name: "九寨沟",
    area: 'C区'
  }
];

// 将一个扁平对象,根据 keys 树形化
function toTree(obj, [key, ...rest], result = {}) {
    if (result.value == null) {
        result.value = obj[key]
        if (rest.length) {
            result.children = toTreeList(obj, rest)
        }
    } else if (result.value === obj[key] && rest.length) {
        toTreeList(obj, rest, result.children)
    }
    return result
}

// 将一个扁平对象的树形化产物,不重复地放到 list 里
function toTreeList(obj, keys, list = []) {
    let value = obj[keys[0]]
    let target = list.find(item => item.value === value)
    if (target) {
        toTree(obj, keys, target)
    } else {
        list.push(toTree(obj, keys))
    }
    return list
}

// 将一个扁平化对象组成的列表,变成树形化的列表
function listToTree(list=[], keys=[]) {
    return list.reduce(
        (result, obj) => toTreeList(obj, keys, result),
        []
    )
}

console.log('result', listToTree(data, ['province', 'city', 'name', 'area']))

这个做法,更加语义化,也可以得到多个可复用的工具函数。

解决这类问题,也有一个统一的思路:先解决能解决的子问题和简单问题,在此基础上再审视,根据现有工具,还可以进一步的解决哪些问题,直到所有问题被解决。这个做法,一来可以不必等某个数据结构和算法被发明,二来甚至可以在这个过程中发明新的数据结构和算法。

---- 2017/09/08 早上更新

为了体现上述方法论的普适性。用吃早饭的功夫,用相同的递归组合思路写了一个 treeToList 的逆运算。把一个树形结构,变成扁平化列表。当树形结构不仅仅是省市区这类选择题,而是可编辑的文件目录时,互相转换就很有用。

var data = [
  {
    value: "浙江",
    children: [
      { value: "杭州", children: [{ value: "西湖", children: [{ value: "A区" }] }] }
    ]
  },
  {
    value: "四川",
    children: [
      {
        value: "成都",
        children: [
          { value: "锦里", children: [{ value: "D区" }] },
          { value: "方所", children: [{ value: "B区" }] }
        ]
      },
      { value: "阿坝", children: [{ value: "九寨沟", children: [{ value: "C区" }] }] }
    ]
  }
];

// 把一个树形结构,变成扁平化列表
function toFlat(tree, [key, ...rest], result = {}) {
  if (result[key] == null) {
    result[key] = tree.value;
  } else if (result[key] !== tree.value) {
    result = {
      ...result,
      [key]: tree.value
    };
  }
  return rest.length ? toFlatList(tree.children, rest, result) : [result];
}

// 把一个树形结构的列表,变成扁平化列表
function toFlatList(treeList, keys, result = {}) {
  return treeList.reduce(
    (list, tree) => list.concat(toFlat(tree, keys, result)),
    []
  );
}

// 转换树形结构为列表结构
function treeToList(treeList = [], keys) {
  return toFlatList(treeList, keys);
}

var result = treeToList(data, ["province", "city", "name", "area"]);
console.log("result", result);

Lucifier129 commented Sep 7, 2017

listToTree 的,可以用递归组合的方式来处理。

var data = [
  {
    province: "浙江",
    city: "杭州",
    name: "西湖",
    area: 'A区'
  },
  {
    province: "四川",
    city: "成都",
    name: "锦里",
    area: 'D区'
  },
  {
    province: "四川",
    city: "成都",
    name: "方所",
    area: 'B区'
  },
  {
    province: "四川",
    city: "阿坝",
    name: "九寨沟",
    area: 'C区'
  }
];

// 将一个扁平对象,根据 keys 树形化
function toTree(obj, [key, ...rest], result = {}) {
    if (result.value == null) {
        result.value = obj[key]
        if (rest.length) {
            result.children = toTreeList(obj, rest)
        }
    } else if (result.value === obj[key] && rest.length) {
        toTreeList(obj, rest, result.children)
    }
    return result
}

// 将一个扁平对象的树形化产物,不重复地放到 list 里
function toTreeList(obj, keys, list = []) {
    let value = obj[keys[0]]
    let target = list.find(item => item.value === value)
    if (target) {
        toTree(obj, keys, target)
    } else {
        list.push(toTree(obj, keys))
    }
    return list
}

// 将一个扁平化对象组成的列表,变成树形化的列表
function listToTree(list=[], keys=[]) {
    return list.reduce(
        (result, obj) => toTreeList(obj, keys, result),
        []
    )
}

console.log('result', listToTree(data, ['province', 'city', 'name', 'area']))

这个做法,更加语义化,也可以得到多个可复用的工具函数。

解决这类问题,也有一个统一的思路:先解决能解决的子问题和简单问题,在此基础上再审视,根据现有工具,还可以进一步的解决哪些问题,直到所有问题被解决。这个做法,一来可以不必等某个数据结构和算法被发明,二来甚至可以在这个过程中发明新的数据结构和算法。

---- 2017/09/08 早上更新

为了体现上述方法论的普适性。用吃早饭的功夫,用相同的递归组合思路写了一个 treeToList 的逆运算。把一个树形结构,变成扁平化列表。当树形结构不仅仅是省市区这类选择题,而是可编辑的文件目录时,互相转换就很有用。

var data = [
  {
    value: "浙江",
    children: [
      { value: "杭州", children: [{ value: "西湖", children: [{ value: "A区" }] }] }
    ]
  },
  {
    value: "四川",
    children: [
      {
        value: "成都",
        children: [
          { value: "锦里", children: [{ value: "D区" }] },
          { value: "方所", children: [{ value: "B区" }] }
        ]
      },
      { value: "阿坝", children: [{ value: "九寨沟", children: [{ value: "C区" }] }] }
    ]
  }
];

// 把一个树形结构,变成扁平化列表
function toFlat(tree, [key, ...rest], result = {}) {
  if (result[key] == null) {
    result[key] = tree.value;
  } else if (result[key] !== tree.value) {
    result = {
      ...result,
      [key]: tree.value
    };
  }
  return rest.length ? toFlatList(tree.children, rest, result) : [result];
}

// 把一个树形结构的列表,变成扁平化列表
function toFlatList(treeList, keys, result = {}) {
  return treeList.reduce(
    (list, tree) => list.concat(toFlat(tree, keys, result)),
    []
  );
}

// 转换树形结构为列表结构
function treeToList(treeList = [], keys) {
  return toFlatList(treeList, keys);
}

var result = treeToList(data, ["province", "city", "name", "area"]);
console.log("result", result);
@huigeek

This comment has been minimized.

Show comment
Hide comment
@huigeek

huigeek Sep 7, 2017

瀑布流的思路跟楼主的思路一样,的确没有想更多,看来还是要加强算法的学习

huigeek commented Sep 7, 2017

瀑布流的思路跟楼主的思路一样,的确没有想更多,看来还是要加强算法的学习

@yalishizhude

This comment has been minimized.

Show comment
Hide comment
@yalishizhude

yalishizhude Sep 7, 2017

算法对于前端开发重要程度应该是很低的,因为通常情况下用不到~

前端通过ajax向后端获取数据,通常这个数据量非常小,为什么?因为考虑到网络开销和浏览器性能。数据量太大会导致前端页面加载速度慢,影响用户体验。

如果数据量大怎么办?分块加载,比如表单的分页。

算法更多地是用在对大量数据进行操作的时候,几十条数据你用排序算法和sort根本无差别。

在楼主这个例子中,如果后端把全国的地市数据都丢给你,你还能开开心心地在前端用算法去整理成树形解构吗?

如果喜欢算法,建议从事后端开发~

算法对于前端开发重要程度应该是很低的,因为通常情况下用不到~

前端通过ajax向后端获取数据,通常这个数据量非常小,为什么?因为考虑到网络开销和浏览器性能。数据量太大会导致前端页面加载速度慢,影响用户体验。

如果数据量大怎么办?分块加载,比如表单的分页。

算法更多地是用在对大量数据进行操作的时候,几十条数据你用排序算法和sort根本无差别。

在楼主这个例子中,如果后端把全国的地市数据都丢给你,你还能开开心心地在前端用算法去整理成树形解构吗?

如果喜欢算法,建议从事后端开发~

@mingyun mingyun referenced this issue in mingyun/mingyun.github.io Sep 9, 2017

Open

网站收集 #92

@RayJune

This comment has been minimized.

Show comment
Hide comment
@RayJune

RayJune Oct 29, 2017

很有收获,谢谢博主

RayJune commented Oct 29, 2017

很有收获,谢谢博主

@RexSkz

This comment has been minimized.

Show comment
Hide comment
@RexSkz

RexSkz Oct 29, 2017

很好奇那个瀑布流的动规式子是什么?想了半天想不出来,感觉把什么作为状态都不满足最优子结构……(如果只分两列的话倒是有动规解法,多列就不知道了。)觉得如果是日常应用的话,一个简单的贪心就足够了。


Update 1:

刚搜了一下,如果是大于两列的话,似乎是个 NP 问题……如果只有两列,那就只是一个装箱问题。

RexSkz commented Oct 29, 2017

很好奇那个瀑布流的动规式子是什么?想了半天想不出来,感觉把什么作为状态都不满足最优子结构……(如果只分两列的话倒是有动规解法,多列就不知道了。)觉得如果是日常应用的话,一个简单的贪心就足够了。


Update 1:

刚搜了一下,如果是大于两列的话,似乎是个 NP 问题……如果只有两列,那就只是一个装箱问题。

@zhanfang

This comment has been minimized.

Show comment
Hide comment
@zhanfang

zhanfang Mar 8, 2018

这么解决?大佬看一下

const R = require('ramda');

const data = [{
    province: '浙江',
    city: '杭州',
    name: '西湖'
}, {
    province: '四川',
    city: '成都',
    name: '锦里'
}, {
    province: '四川',
    city: '成都',
    name: '方所'
}, {
    province: '四川',
    city: '阿坝',
    name: '九寨沟'
}];
const keys = ['province', 'city', 'name'];

/**
 * 将对象数组转换为字典数组
 * @param {Array} arr
 * @return {Array}
 */
const transObjToDict = function (arr, keys) {
    return arr.map(item => R.compose(R.join('|'), R.props(keys))(item));
};

function Trie() {
    this.trie = {};
}
Trie.prototype.add = function(str) {
    let trie = this.trie;
    R.compose(
        R.map(function (item, i) {
            if (trie[item] == null) {
                trie[item] = {};
                trie = trie[item];
            }
            else {
                trie = trie[item];
            }
        })
        , R.split('|')
    )(str);
}

function formatTree(trie) {
    const keys = Object.keys(trie);
    let res = [];
    for (let i = 0, len = keys.length; i < len; i++) {
        let temp = {
            value: keys[i],
            children: formatTree(trie[keys[i]])
        }
        if (temp.children.length === 0) {
            delete temp.children;
        }
        res.push(temp);
        
    }
    return res;
}

const newTrans = function (data, keys) {
    const dicts = transObjToDict(data, keys);
    let tree = new Trie();
    for (let i = 0, len = dicts.length; i < len; i++) {
        tree.add(dicts[i]);
    }
    return formatTree(tree.trie);
};

const result = newTrans(data, keys);
console.log(JSON.stringify(res));

zhanfang commented Mar 8, 2018

这么解决?大佬看一下

const R = require('ramda');

const data = [{
    province: '浙江',
    city: '杭州',
    name: '西湖'
}, {
    province: '四川',
    city: '成都',
    name: '锦里'
}, {
    province: '四川',
    city: '成都',
    name: '方所'
}, {
    province: '四川',
    city: '阿坝',
    name: '九寨沟'
}];
const keys = ['province', 'city', 'name'];

/**
 * 将对象数组转换为字典数组
 * @param {Array} arr
 * @return {Array}
 */
const transObjToDict = function (arr, keys) {
    return arr.map(item => R.compose(R.join('|'), R.props(keys))(item));
};

function Trie() {
    this.trie = {};
}
Trie.prototype.add = function(str) {
    let trie = this.trie;
    R.compose(
        R.map(function (item, i) {
            if (trie[item] == null) {
                trie[item] = {};
                trie = trie[item];
            }
            else {
                trie = trie[item];
            }
        })
        , R.split('|')
    )(str);
}

function formatTree(trie) {
    const keys = Object.keys(trie);
    let res = [];
    for (let i = 0, len = keys.length; i < len; i++) {
        let temp = {
            value: keys[i],
            children: formatTree(trie[keys[i]])
        }
        if (temp.children.length === 0) {
            delete temp.children;
        }
        res.push(temp);
        
    }
    return res;
}

const newTrans = function (data, keys) {
    const dicts = transObjToDict(data, keys);
    let tree = new Trie();
    for (let i = 0, len = dicts.length; i < len; i++) {
        tree.add(dicts[i]);
    }
    return formatTree(tree.trie);
};

const result = newTrans(data, keys);
console.log(JSON.stringify(res));
@jessiezhudev

This comment has been minimized.

Show comment
Hide comment
@jessiezhudev

jessiezhudev Apr 21, 2018

了解了应用场景之后,更能有方向和动力学习算法了,给楼主点个赞

了解了应用场景之后,更能有方向和动力学习算法了,给楼主点个赞

@gaoshijun1993

This comment has been minimized.

Show comment
Hide comment
@gaoshijun1993

gaoshijun1993 May 9, 2018

想请假一下,这个转换放在后端转换好呢还是在前端转换?

想请假一下,这个转换放在后端转换好呢还是在前端转换?

@LeuisKen

This comment has been minimized.

Show comment
Hide comment
@LeuisKen

LeuisKen May 9, 2018

Owner

@gaoshijun1993 个人感觉其实放在后端比较好

Owner

LeuisKen commented May 9, 2018

@gaoshijun1993 个人感觉其实放在后端比较好

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment