рекурсивный подход
У вас супер веселая проблема! Я собираюсь добавить еще несколько элементов к вашему входу, чтобы мы могли видеть, что братья и сестры правильно вложены. Я также разбросал несколько пустых строк, чтобы сделать нашу программу более устойчивой -
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
Имея чистую отправную точку, мы можем начать разбирать проблему ...
design
Давайте подставим пробелы для •
и окончания строки для ¬
, чтобы мы могли видеть, что происходит. Мы начинаем с вызова makeChildren
для чистой строки -
makeChildren(
<b>todo•list¬
••learn•js¬
••••hello•world¬
••••functions¬
shopping•list¬
••costco¬
••berries¬
••mushrooms¬
procrastination•list</b>
)
makeChildren
создает массив и вызывает make1
для каждого элемента -
[ make1(
<b>todo•list¬
••learn•js¬
••••hello•world¬
••••functions¬</b>
)
, make1(
<b>shopping•list¬
••costco¬
••berries¬
••mushrooms¬</b>
)
, make1(
<b>procrastination•list</b>
)
]
make1
создает узел и впоследствии вызывает makeChildren
своих потомков -
[ { value: <b>todo•list</b>
, children: makeChildren(outdent(
<b>••learn•js¬
••••hello•world¬
••••functions¬</b>
))
}
, { value: <b>shopping•list</b>
, children: makeChildren(outdent(
<b>••costco¬
••berries¬
••mushrooms¬</b>
))
}
, { value: <b>procrastination•list</b>
, children: makeChildren(outdent(
))
}
]
И, как мы уже видели, makeChildren
создает массив и вызывает make1
для каждого дочернего элемента -
[ { value: <b>todo•list</b>
, children:
[ make1(
<b>learn•js¬
••hello•world¬
••functions¬</b>
)
]
}
, { value: <b>shopping•list</b>
, children:
[ make1(<b>costco¬</b>)
, make1(<b>berries¬</b>)
, make1(<b>mushrooms¬</b>)
]
}
, { value: <b>procrastination•list</b>
, children:
[]
}
]
И время от времени взаимно рекурсивный процесс продолжается ... makeChildren
вызывает make1
, который вызывает makeChildren
, который вызывает make1
и c, пока базовый случай не будет достигнут в каждом ответвление.
орудие
В соответствии с нашим проектом мы начнем с 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
, но разбивает только str
на первое вхождение char
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: [] }
]
Запустите приведенный ниже фрагмент, чтобы проверить результаты в своем браузере -
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: [] }
// ]
имеет для меня смысл!
Является ли программа "простой и понятной", потому что она имеет смысл для меня? Что если бы мы могли использовать объективных качеств для таких утверждений? @ tokafew420 уверен в своей программе, и поэтому я предлагаю этот объективный анализ.
Я изменил имена переменных на _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-строчного поведения c его нельзя использовать в других частях программы.
Если сравнить это с низкой сложностью функциональная программа, у нас есть меньшие функции с четко определенной целью, которые легко писать, тестировать и обслуживать, а также повторно использовать в других частях нашей программы. Как вы можете видеть, переименование переменных в _1
, _2
и _3
едва ли ухудшает читабельность, так как наш мозг легко отслеживает 3 вещи одновременно, и даже легче, когда мы знаем, что эти 3 вещи не являются мутированный или переназначенный.
Какое значение x
в строке Y
императивной программы? Из-за всех мутаций и переназначений в for
- while
, вложенных в l oop, можно только догадываться. Если ваш мозг не заменен компьютером, на этот вопрос трудно ответить почти для всех переменных и всех строк в этой программе, и поэтому я представляю, что это совсем не просто или просто.
С другой стороны, легко ответить на эти вопросы о функциональной программе. Мы можем мгновенно узнать значение любой переменной в любой строке, не ссылаясь на бесчисленное множество других переменных или не подвергаясь неуправляемым концептуальным издержкам. Если это не просто или просто, я не знаю, что ...
/ 2cents