递归方法
你有一个超级有趣的问题!我将在您的输入中添加更多元素,以便我们可以看到兄弟姐妹和后代正确嵌套。我还分散了一些空行以使我们的程序更健壮-
const data = `
todo list
learn js
hello world
functions
shopping list
costco
berries
mushrooms
procrastination list
`
首先,我们将通过删除所有空行以及所有前导和尾随空格来sanitize 数据 -
const sanitize = (str = "") =>
str.trim().replace(/\n\s*\n/g, "\n")
console.log(sanitize(data))
todo list
learn js
hello world
functions
shopping list
costco
berries
mushrooms
procrastination list
有了一个清晰的起点,我们就可以开始分解问题了...
设计
让我们用空格代替•,用行尾代替¬,这样我们就可以看到发生了什么。我们首先在干净的字符串上调用makeChildren -
makeChildren(
todo•list¬
••learn•js¬
••••hello•world¬
••••functions¬
shopping•list¬
••costco¬
••berries¬
••mushrooms¬
procrastination•list
)
makeChildren 创建一个数组并在每个元素上调用make1 -
[ make1(
todo•list¬
••learn•js¬
••••hello•world¬
••••functions¬
)
, make1(
shopping•list¬
••costco¬
••berries¬
••mushrooms¬
)
, make1(
procrastination•list
)
]
make1 创建一个节点并随后在其后代上调用makeChildren -
[ { value: todo•list
, children: makeChildren(outdent(
••learn•js¬
••••hello•world¬
••••functions¬
))
}
, { value: shopping•list
, children: makeChildren(outdent(
••costco¬
••berries¬
••mushrooms¬
))
}
, { value: procrastination•list
, children: makeChildren(outdent(
))
}
]
正如我们已经看到的,makeChildren 创建一个数组并在每个孩子上调用 make1 -
[ { value: todo•list
, children:
[ make1(
learn•js¬
••hello•world¬
••functions¬
)
]
}
, { value: shopping•list
, children:
[ make1(costco¬)
, make1(berries¬)
, make1(mushrooms¬)
]
}
, { value: procrastination•list
, children:
[]
}
]
mutually recursive 进程不断地继续...makeChildren 调用 make1,后者调用 makeChildren,后者调用 make1 等等,直到每个分支都满足基本情况。
实施
根据我们的设计,我们将从makeChildren 开始-
const makeChildren = (str = "") =>
str === ""
? []
: str.split(/\n(?!\s)/).map(make1)
这要求我们实现make1 -
const make1 = (str = "") =>
{ const [ value, children ] = cut(str, "\n")
return { value, children: makeChildren(outdent(children)) }
}
这要求我们实现 cut 和 outdent -
-
cut 与String.prototype.split 类似,但仅在char 的第一次 出现时拆分str
-
outdent 删除一级缩进
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")
}
就是这样!最后的result 是-
const result =
makeChildren(sanitize(data))
console.log(result)
[ { value: "todo list"
, children:
[ { value: "learn js"
, children:
[ { value: "hello world", children: [] }
, { value: "functions", children: [] }
]
}
]
}
, { value: "shopping list"
, children:
[ { value: "costco", children: [] }
, { value: "berries", children: [] }
, { value: "mushrooms", children: [] }
]
}
, { value: "procrastination list", children: [] }
]
在你自己的浏览器中运行下面的sn-p来验证结果-
const sanitize = (str = "") =>
str.trim().replace(/\n\s*\n/g, "\n")
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")
}
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 data = `
todo list
learn js
hello world
functions
shopping list
costco
berries
mushrooms
procrastination list
`
const result =
makeChildren(sanitize(data))
console.log(JSON.stringify(result, null, 2))
// [ { value: "todo list"
// , children:
// [ { value: "learn js"
// , children:
// [ { value: "hello world", children: [] }
// , { value: "functions", children: [] }
// ]
// }
// ]
// }
// , { value: "shopping list"
// , children:
// [ { value: "costco", children: [] }
// , { value: "berries", children: [] }
// , { value: "mushrooms", children: [] }
// ]
// }
// , { value: "procrastination list", children: [] }
// ]
对我来说很有意义!
一个程序“简单直接”是因为它对我有意义吗?如果我们可以使用 objective 品质来做出这样的断言会怎样? @tokafew420 对他们的计划很有信心,所以我提供了这个客观的分析。
我在 each 程序中将变量名称更改为 _n,以便我们可以轻松识别和计算各个移动部件 -
const TxtParser = _1 => { // 10 total variables; 4 mutations; 5 variable reassignments
let _2 = []; // <-- mutates below but never reassigned; should be const
let _3 = []; // <-- mutates below but never reassigned; should be const
let _4 = { // <-- reassigned below
nbrSpaces: -1,
children: _2 // <-- mutates below
};
let _5; // <-- reassigned below
if (_1) {
let _6 = _1.split("\n"); // <-- reassignment #1
let _7 = _6.length; // <-- reassignment #2
if (_7) {
let i; // <-- mutates; leaks variable out of `for` scope
for (i = 0; i < _7; i++) { // <-- mutation #1
let _8 = _6[i].trim(); // <-- never reassigned, does not mutate; should be const
let _9 = _6[i].search(/\S/); // <-- never reassigned, does not mutate; should be const
if (_8) {
let _10 = { // <-- never reassigned, does not mutate; should be const
line: _8,
nbrSpaces: _9,
children: []
};
if (_5 && _9 > _5.nbrSpaces) {
_3.push(_4); // <-- mutation #2
_4 = _5; // <-- reassignment #3
} else {
while (_9 <= _4.nbrSpaces) {
_4 = _3.pop(); // <-- reassignment #4 AND mutation #3
}
}
_4.children.push(_10); // <-- mutation #4
_5 = _10; // <-- reassignment #5
}
}
}
}
return _2;
};
- 单个范围内的最高变量计数:10
- 突变:4
- 变量重新分配:5
- 实施行数:36
- 可重用函数:0
- 需要额外的转换才能产生预期的结果:是的
将其与声明式函数方法进行比较 -
const sanitize = (_1 = "") => // 1 total variable; never mutates; never reassigned
_1.trim().replace(/\n\s*\n/g, "\n")
const cut = (_1 = "", _2 = "") => // 3 total variables; never mutates; never reassigned
{ const _3 = _1.search(_2)
return _3 === -1
? [ _1, "" ]
: [ _1.substr(0, _3), _1.substr(_3 + 1) ]
}
const outdent = (_1 = "") => // 3 total variables; never mutates; never reassigned
{ const _2 = Math.max(0, _1.search(/\S/))
const _3 = new RegExp(`(^|\n)\\s{${_2}}`, "g")
return _1.replace(_3, "$1")
}
const makeChildren = (_1) => // 1 total variable; never mutates; never reassigned
_1 === ""
? []
: _1.split(/\n(?!\s)/).map(make1)
const make1 = (_1 = "") => // 3 total variables; never mutates; never reassigned
{ const [ _2, _3 ] = cut(_1, "\n")
return { value: _2, children: makeChildren(outdent(_3)) }
}
- 单个范围内的最高变量计数:3
- 突变:0
- 变量重新分配:0
- 实施行数:13
- 可重用函数:3 个(
sanitize、cut、outdent)
- 需要额外的转换才能产生所需的结果:否
为什么这些事情很重要?
当单个作用域中有 10 个变量,并且它们都可以随时更改和重新分配时,我们的大脑非常很难跟踪所有移动的部分。这个程序很大,很难写。即使我们对一个输入得到了正确的结果,我们怎么知道我们的程序对其他输入是正确的呢?需要编写更多测试以确保正确的行为,并且由于它是 36 行的特定行为,它不能在程序的其他部分中重用。
当您将其与函数式程序的低复杂性进行比较时,我们的函数更小,目的明确,易于编写、测试和维护,并可在程序的其他部分重用。如您所见,将变量重命名为 _1、_2 和 _3 几乎不会影响可读性,因为我们的大脑很容易同时跟踪 3 件事,当我们知道这 3 件事不是时更容易变异或重新分配。
命令式程序的Y线上x的值是多少?由于 for-while 嵌套循环中的所有突变和重新分配,这是任何人的猜测。除非您的大脑被计算机取代,否则对于该程序中几乎所有变量和所有行,这个问题很难回答,所以我认为它绝非简单或直截了当。
另一方面,很容易回答有关函数式程序的这些问题。我们可以立即知道任何行上任何变量的值,而无需引用无数其他变量或产生难以管理的概念开销。如果这不简单或直接,我不知道是什么......
/2美分