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

Open
opened this Issue May 13, 2016 · 15 comments

Projects
None yet
Owner

## 从一个需求谈起

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

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

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

A：你有写过瀑布流吗？

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

A：那我问一下，如何让几列图片之间的高度差最小？

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

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

B：（想了一段时间）对不起，这个问题我没有思路。

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

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

## 多说两句——一道思考题

```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'
}
};```

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

## 结语

### fengmk2 commented Jun 17, 2016 • edited Edited 1 time fengmk2 edited Jun 17, 2016 (most recent)

 还能更加极致地让服务端通过 SQL 来实现了，这样传输给前端的数据量也大大减少了。 SQL 无法实现的话，建议这个trie树由服务端来实现数据的转换。
Owner

### LeuisKen commented Jun 18, 2016

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

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

Open

### majunbao commented Mar 19, 2017

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

### yhhwpp commented Jun 22, 2017

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

### jinyang1994 commented Jun 22, 2017

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

Open

### Lucifier129 commented Sep 7, 2017 • edited Edited 1 time Lucifier129 edited Sep 7, 2017 (most recent)

 `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 commented Sep 7, 2017

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

### yalishizhude commented Sep 7, 2017

 算法对于前端开发重要程度应该是很低的，因为通常情况下用不到~ 前端通过ajax向后端获取数据，通常这个数据量非常小，为什么？因为考虑到网络开销和浏览器性能。数据量太大会导致前端页面加载速度慢，影响用户体验。 如果数据量大怎么办？分块加载，比如表单的分页。 算法更多地是用在对大量数据进行操作的时候，几十条数据你用排序算法和sort根本无差别。 在楼主这个例子中，如果后端把全国的地市数据都丢给你，你还能开开心心地在前端用算法去整理成树形解构吗？ 如果喜欢算法，建议从事后端开发~

Open

### RayJune commented Oct 29, 2017

 很有收获，谢谢博主

### RexSkz commented Oct 29, 2017 • edited Edited 1 time RexSkz edited Oct 29, 2017 (most recent)

 很好奇那个瀑布流的动规式子是什么？想了半天想不出来，感觉把什么作为状态都不满足最优子结构……（如果只分两列的话倒是有动规解法，多列就不知道了。）觉得如果是日常应用的话，一个简单的贪心就足够了。 Update 1： 刚搜了一下，如果是大于两列的话，似乎是个 NP 问题……如果只有两列，那就只是一个装箱问题。

### zhanfang commented Mar 8, 2018 • edited Edited 1 time zhanfang edited Apr 21, 2018 (most recent)

 这么解决？大佬看一下 ```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 commented Apr 21, 2018

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

### gaoshijun1993 commented May 9, 2018

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

### LeuisKen commented May 9, 2018

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