文字列"ABC"
の場合、以下のコードスニペットは6つの合計順列のうち5つを計算します。私の戦略は、各インデックスの可能なインデックスに各文字を挿入することでした。しかし、関数が可能な順列として"CBA"
を取得することはありません。何が足りないのですか?
var permutationArray:[String] = [];
let string: String = "ABC"
func permute(input: String) -> Array<String>
{
var permutations: Array<String> = []
/* Convert the input string into characters */
var inputArray: Array<String>
inputArray = input.characters.map { String($0) }
print(inputArray)
/* For each character in the input string... */
for var i = 0; i < inputArray.count; i++
{
/* Insert it at every index */
let characterInArray: String = inputArray[i]
var inputArrayCopy: Array<String> = []
for var y = 0; y < inputArray.count; y++
{
inputArrayCopy = inputArray
inputArrayCopy.removeAtIndex(i)
inputArrayCopy.insert(characterInArray, atIndex:y)
let joiner = ""
let permutation = inputArrayCopy.joinWithSeparator(joiner)
if !permutations.contains(permutation) {
permutations.insert(permutation, atIndex: 0)
}
}
}
return permutations
}
var permutations = permute(string)
print(permutations)
StefanとMattは、Heapのアルゴリズムの使用について良い点を述べていますが、なぜyourコードが機能しないのか、そしてそれをどのようにデバッグするのかについて、重要な質問があると思います。
この場合、アルゴリズムは単に正しくありません。それを発見する最良の方法は、鉛筆と紙のIMOを使用することです。あなたがしていることは、各要素を選び、それを配列から削除し、そしてそれをそれぞれの可能な場所に注入することです。あなたのコードはあなたがそれをするように頼んだことをします。しかし、その方法で「CBA」に到達することはできません。一度に移動する要素は1つだけですが、「CBA」にはtwo要素の順序が正しくありません。 ABCDに展開すると、さらに多くの欠落した順列が見つかります(24のうち10のみが生成されます)。
Heapのアルゴリズムは非常に効率的ですが、より深いポイントは、配列内で1つの要素を移動するだけでなく、配列全体をウォークスルーし、可能なすべてのペアを交換することです。選択するアルゴリズムには、そのプロパティが必要です。
そして、帽子をリングに投げ込むために、マットの実装を次のように拡張します。
// Takes any collection of T and returns an array of permutations
func permute<C: Collection>(items: C) -> [[C.Iterator.Element]] {
var scratch = Array(items) // This is a scratch space for Heap's algorithm
var result: [[C.Iterator.Element]] = [] // This will accumulate our result
// Heap's algorithm
func heap(_ n: Int) {
if n == 1 {
result.append(scratch)
return
}
for i in 0..<n-1 {
heap(n-1)
let j = (n%2 == 1) ? 0 : i
scratch.swapAt(j, n-1)
}
heap(n-1)
}
// Let's get started
heap(scratch.count)
// And return the result we built up
return result
}
// We could make an overload for permute() that handles strings if we wanted
// But it's often good to be very explicit with strings, and make it clear
// that we're permuting Characters rather than something else.
let string = "ABCD"
let perms = permute(string.characters) // Get the character permutations
let permStrings = perms.map() { String($0) } // Turn them back into strings
print(permStrings) // output if you like
これは、Swiftでのヒープ(Sedgewick?)アルゴリズムの表現です。配列は値ではなく参照で渡されるため、効率的です(ただし、もちろん、これは、配列が改ざんされる準備が必要であることを意味します)。スワッピングは、組み込みのswapAt(_:_:)
関数を使用して効率的に表現されます。
_func permutations(_ n:Int, _ a: inout Array<Character>) {
if n == 1 {print(a); return}
for i in 0..<n-1 {
permutations(n-1,&a)
a.swapAt(n-1, (n%2 == 1) ? 0 : i)
}
permutations(n-1,&a)
}
_
試してみよう:
_var arr = Array("ABC".characters)
permutations(arr.count,&arr)
_
出力:
_["A", "B", "C"]
["B", "A", "C"]
["C", "A", "B"]
["A", "C", "B"]
["B", "C", "A"]
["C", "B", "A"]
_
各順列でやりたいことが単にそれを印刷することではなかった場合は、print(a)
を別のものに置き換えてください。たとえば、各順列を配列に追加したり、文字の配列を組み合わせて文字列にしたりすることができます。
これが私の解決策です。
import Foundation
class Permutator {
class func permutation(_ str: String) -> Set<String> {
var set = Set<String>()
permutation(str, prefix: "", set: &set)
return set
}
private class func permutation(_ str: String, prefix: String, set: inout Set<String>) {
if str.characters.count == 0 {
set.insert(prefix)
}
for i in str.characters.indices {
let left = str.substring(to: i)
let right = str.substring(from: str.index(after: i))
let rem = left + right
permutation(rem, prefix: prefix + String(str[i]), set: &set)
}
}
}
let startTime = Date()
let permutation = Permutator.permutation("abcdefgh")
print("\(permutation) \n")
print("COMBINAISON: \(permutation.count)")
print("TIME: \(String(format: "%.3f", Date().timeIntervalSince(startTime)))s")
それをコピーしてファイルに貼り付け、コマンドラインSwift binaryで実行できます。
7つのすべての一意の文字の順列の場合、このアルゴリズムの実行には約0.06秒かかります。
func generate(n: Int, var a: [String]){
if n == 1 {
print(a.joinWithSeparator(""))
} else {
for var i = 0; i < n - 1; i++ {
generate(n - 1, a: a)
if n % 2 == 0 {
let temp = a[i]
a[i] = a[n-1]
a[n-1] = temp
}
else {
let temp = a[0]
a[0] = a[n-1]
a[n-1] = temp
}
}
generate(n - 1, a: a)
}
}
func testExample() {
var str = "123456"
var strArray = str.characters.map { String($0) }
generate(str.characters.count, a: strArray)
}
車輪の再発明をしないでください。これは ヒープのアルゴリズム の単純なポートです。
Swiftコーディングの課題でも示唆されているように、非常に単純なアプローチ。
func permutation(string: String, current: String = "") {
let length = string.characters.count
let strArray = Array(string.characters)
if (length == 0) {
// there's nothing left to re-arrange; print the result
print(current)
print("******")
} else {
print(current)
// loop through every character
for i in 0 ..< length {
// get the letters before me
let left = String(strArray[0 ..< i])
// get the letters after me
let right = String(strArray[i+1 ..< length])
// put those two together and carry on
permutation(string: left + right, current: current +
String(strArray[i]))
}
}
}