[英]understanding better a solution for finding permutations of a string - javascript
我試圖更好地理解遞歸和函數式編程,我認為這是一個很好的實踐示例,即使用遞歸和現代方法(如reduce,filter和map)創建字符串的排列。
我找到了這段漂亮的代碼
const flatten = xs => xs.reduce((cum, next) => [...cum, ...next], []); const without = (xs, x) => xs.filter(y => y !== x); const permutations = xs => flatten(xs.map(x => xs.length < 2 ? [xs] : permutations(without(xs, x)).map(perm => [x, ...perm]) )); permutations([1,2,3]) // [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]
來自JavaScript的Permutations? 作者:MártonSári
為了添加一些控制台日志來調試它並理解它在幕后做了什么,我已經分隔了一點
const flatten = xs => { console.log(`input for flatten(${xs})`); return xs.reduce((cum, next) => { let res = [...cum, ...next]; console.log(`output from flatten(): ${res}`); return res; }, []); } const without = (xs, x) => { console.log(`input for without(${xs},${x})`) let res = xs.filter(y => y !== x); console.log(`output from without: ${res}`); return res; } const permutations = xs => { console.log(`input for permutations(${xs})`); let res = flatten(xs.map(x => { if (xs.length < 2) { return [xs] } else { return permutations(without(xs, x)).map(perm => [x, ...perm]) } })); console.log(`output for permutations: ${res}`) return res; } permutations([1,2,3])
我想我對每種方法的作用都有足夠的了解,但我似乎無法概念化如何將它們組合起來創建[[1,2,3],[1,3,2],[2] ,1,3],[2,3,1],[3,1,2],[3,2,1]]
有人可以一步一步地告訴我發動機罩下發生了什么嗎?
要獲得所有權限,我們會執行以下操作:
我們從左到右取一個數組元素。
xs.map(x => // 1
對於所有其他元素,我們遞歸地生成排列:
permutations(without(xs, x)) // [[2, 3], [3, 2]]
對於每個排列,我們添加我們在開始時取回的值:
.map(perm => [xs, ...perm]) // [[1, 2, 3], [1, 3, 2]]
現在,對所有數組元素重復,結果是:
[
// 1
[[1, 2, 3], [1, 3, 2]],
// 2
[[2, 1, 3], [2, 3, 1]],
// 3
[[3, 1, 2], [3, 2, 1]]
]
現在我們只需要flatten(...)
該數組以獲得所需的結果。
整個事情可以表示為遞歸調用樹:
[1, 2, 3]
- [2, 3] ->
- [3] -> [1, 2, 3]
- [2] -> [1, 3, 2]
- [1, 3] ->
- [1] -> [2, 3, 1]
- [3] -> [2, 1, 3]
- [1, 2] ->
- [1] -> [3, 2, 1]
- [2] -> [3, 1, 2]
為了添加一些控制台日志來調試它,我稍微划了一下
這當然有幫助。 但請記住,簡單的遞歸定義通常會導致復雜的執行跟蹤。
這實際上是遞歸可能如此有用的原因之一。 因為某些算法具有復雜的迭代,所以允許簡單的遞歸描述。 因此,理解遞歸算法的目標應該是在其定義中找出歸納(而不是迭代)推理。
讓我們忘記javascript並專注於算法。 讓我們看看我們可以得到集合A
的元素的排列,我們將表示P(A)
。
注意:在原始算法中輸入是一個列表並不重要,因為原始順序根本不重要。 同樣,我們將返回一組列表而不是列表列表,這是無關緊要的,因為我們不關心計算解決方案的順序。
基本情況:
最簡單的情況是空集。 對於0個元素的排列,只有一個解決方案,該解決方案是空序列[]
。 所以,
P(A) = {[]}
遞歸案例:
為了使用遞歸,您想要描述如何從P(A')
獲得P(A)
大小小於A
某些A'
。
注意:如果你這樣做,它就完成了。 在操作上,程序將通過對具有越來越小的參數的P
連續調用來計算,直到它到達基本情況,然后它將從較短的結果中恢復較長的結果。
所以這里有一種方法來編寫一個具有n + 1個元素的A
的特定排列。 您需要為每個位置連續選擇A
一個元素:
_ _ ... _
n+1 n 1
所以你為第一個選擇一個x ∈ A
x _ ... _
n 1
然后你需要在P(A\\{x})
選擇一個排列。
這告訴你一種構建大小為n
所有排列的方法。 考慮的所有可能的選擇x
在A
(作為第一元件使用),並且對於每個選擇放x
中的每一溶液的前面P(A\\{x})
最后,為每個x
選擇找到所有解決方案的聯合。
讓我們使用點運算符來表示將x
放在序列s
前面,並使用菱形運算符來表示將x
放在每個s ∈ S
前面。 那是,
x⋅s = [x, s1, s2, ..., sn]
x⟡S = {x⋅s : s ∈ S}
然后換一個非空A
P(A) = ⋃ {x⟡P(A\{x}) : x ∈ A}
此表達式與案例庫一起為您提供集合A
中元素的所有排列。
javascript代碼
要了解您顯示的代碼如何實現此算法,您需要考慮以下內容
當你有0或1個元素時,該代碼通過編寫xs.length < 2
考慮兩個基本情況。 我們也可以這樣做,這是無關緊要的。 您可以將2更改為1,它仍然可以工作。
映射對應於我們的運算x⟡S = {x⋅s : s ∈ S}
不對應P(A\\{x})
flatten對應於連接所有解決方案的⋃
。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.