【问题标题】:Iterating based on a variable number of inner loops基于可变数量的内部循环进行迭代
【发布时间】:2016-04-21 17:14:25
【问题描述】:

在下面的代码中,我试图通过所有可能的字母组合来确定运行时变量的字符数。

这段代码的目的是构建一种密码破解器,它基本上是暴力破解字符串。我想使用循环,因为一旦命中正确的组合,我就能够打破循环,从而节省时间和资源,否则如果我尝试在第一步中构建所有可能组合的数组,则将需要这些时间和资源。

我有一个静态代码,它适用于 5 个字符长的字符串,但实际上我的字符串可以是任意长度。如何使我的代码适用于任意长度的字符串?

let len = textField.text?.characters.count //Length of string
let charRange = "abcdefghijklmnopqrstuvwxyz" //Allowed characterset

for char1 in charRange.characters {
    for char2 in charRange.characters {
        for char3 in charRange.characters {
            for char4 in charRange.characters {
                for char5 in charRange.characters {
                     // Do whatever with all possible combinations
                }
            }
        }
    }
}

我想我必须以某种方式使用for totalChars in 1...len {,但不知道如何动态创建 for 循环?

【问题讨论】:

  • 这是比 Swift 问题更多的算法问题。一种常见的方法是使用 recursion.
  • 我不完全确定我是否理解这个问题,您是否只想找到一种方法来查找字符串中所有可能的字符组合?
  • @Oliver:我想针对问题字符串(一种密码破解器)尝试在一定字符串长度内允许的字母的所有可能组合。
  • 对于 "a" ... "z" 中的 8 个字符,这将是 26^8 = 208827064576 个组合 ... :) 如果您每秒处理 10,000 个组合,那么您将需要大约 240 天.
  • @Martin:是的,我明白这一点,也明白完成它所需的资源。

标签: swift swift2.2


【解决方案1】:

想法:使用字母表中的索引数组形成字符串;每次增加索引。

[0, 0, 0] -> [1, 0, 0] -> [2, 0, 0] ->
[0, 1, 0] -> [1, 1, 0] -> [2, 1, 0] ->
[0, 2, 0] -> [1, 2, 0] -> [2, 2, 0] ->
[0, 0, 1] ... [2, 2, 2]

这是一个使用长度为 3 和字母为 abcd 的示例

let len = 3
let alphabet = "abcd".characters.map({ String($0) })
var allStrings = [String]()
let maxIndex = alphabet.endIndex
var indicies = Array(count: len, repeatedValue: 0)

outerLoop: while (true) {
    // Generate string from indicies
    var string = ""
    for i in indicies {
        let letter = alphabet[i]
        string += letter
    }
    allStrings.append(string)
    print("Adding \(string)")

    // Increment the index
    indicies[0] += 1

    var idx = 0
    // If idx overflows then (idx) = 0 and (idx + 1) += 1 and try next
    while (indicies[idx] == maxIndex) {
        // Reset current
        indicies[idx] = 0
        // Increment next (as long as we haven't hit the end done)
        idx += 1
        if (idx >= alphabet.endIndex - 1) {
            print("Breaking outer loop")
            break outerLoop
        }
        indicies[idx] += 1
    }
}

print("All Strings: \(allStrings)")

【讨论】:

  • 这确实会给我一个包含所有可能组合的数组,但我要求一种通过循环遍历所有组合的方法的原因是,我能够在找到组合后立即终止循环我正在寻找并因此节省所需的时间/资源(随着字符串长度和字符范围的增加,这可以成倍增加。
  • 在这种情况下,您可以只比较字符串而不是 allStrings.append(string)
【解决方案2】:

正如 Martin R 所建议的,您可以使用递归

这是函数

func visit(alphabet:[Character], combination:[Character], inout combinations:[String], length: Int) {
    guard length > 0 else {
        combinations.append(String(combination))
        return
    }

    alphabet.forEach {
        visit(alphabet, combination: combination + [$0], combinations: &combinations, length: length - 1)
    }
}

辅助函数

func combinations(alphabet: String, length: Int) -> [String] {
    var combinations = [String]()
    visit([Character](alphabet.characters), combination: [Character](), combinations: &combinations, length: length)
    return combinations
}

测试

现在,如果您想要 3 个字符的每个组合,并且您想要 "ab" 作为字母,那么

combinations("ab", length: 3) // ["aaa", "aab", "aba", "abb", "baa", "bab", "bba", "bbb"]

重复

请注意,如果您在字母表中插入重复元素,则会在结果中出现重复元素。

时间复杂度

visit 函数的调用次数与节点的调用次数一样多,从而形成高度为 h 的完美 k-ary 树,其中:

  • k:将元素个数放入alphabet参数
  • hlength 参数

这样的树有

节点。这是函数将被调用的确切次数。

空间复杂度

理论上同时分配执行访问的最大栈帧数为length

但是,由于 Swift 编译器确实实现了 Tail Call Optimization,因此分配的堆栈帧数仅为 1。

最后我们必须考虑到combinations会和结果的数量一样大:alphabet^length

所以时间复杂度是lengthelements into the result的最大值。

它是O(length + alphabet^length)


更新

原来你想要一个暴力破解密码器。

func find(alphabet:[Character], combination:[Character] = [Character](), length: Int, check: (keyword:String) -> Bool) -> String? {
    guard length > 0 else {
        let keyword = String(combination)
        return check(keyword: keyword) ? keyword : nil
    }

    for char in alphabet {
        if let keyword = find(alphabet, combination: combination + [char], length: length - 1, check: check) {
            return keyword
        }
    }

    return nil
}

最后一个参数check 是一个闭包,用于验证当前单词是否是正确的密码。你将把你的逻辑放在这里,一旦找到密码,find 就会停止。

例子

find([Character]("tabcdefghil".characters), length: 3) { (keyword) -> Bool in
    return keyword == "cat" // write your code to verify the password here
}

【讨论】:

  • 我可能弄错了,但我认为visit() 方法中没有尾调用,因为最后一条语句在循环中执行递归调用。
  • 嗨@MartinR,我相信将唯一的递归调用放在函数的末尾就足够了。我错了吗?
  • 但是递归调用不是函数中的最后一个语句,它在一个循环中。如果函数调用自身 n 次,它应该如何工作?
  • @MartinR:我明白你的意思,我将更新我的答案。谢谢你。每次您回答或评论帖子时,都是一个学习新知识的机会。
  • 这确实会给我一个包含所有可能组合的数组,但我要求一种通过循环遍历所有组合的方法的原因是,我能够在找到组合后立即终止循环我正在寻找并因此节省所需的时间/资源(随着字符串长度和字符范围的增加,这可以成倍增加。
【解决方案3】:

递归的替代方案;增量(重复)遍历字母表的循环基数表示

递归的替代方法是循环遍历字母表的数字表示,使用基数表示不同数量的字母。此方法的一个限制是 String(_:,radix:) 初始化程序最多允许 base36 数字(基数 36),即您最多可以使用一组唯一计数


帮助功能

// help function to use to pad incremental alphabeth cycling to e.g. "aa..."
let padToTemplate: (str: String, withTemplate: String) -> String = {
    return $0.characters.count < $1.characters.count
        ? String($1.characters.suffixFrom($0.characters.endIndex)) + $0
        : $0
}

主要的基数暴力密码检查方法

// attempt brute-force attempts to crack isCorrectPassword closure
// for a given alphabet, suspected word length and for a maximum number of 
// attempts, optionally with a set starting point
func bruteForce(isCorrectPassword: (String) -> Bool, forAlphabet alphabet: [Character], forWordLength wordLength: Int, forNumberOfAttempts numAttempts: Int, startingFrom start: Int = 0) -> (Int, String?) {
    
    // remove duplicate characters (but preserve order)
    var exists: [Character:Bool] = [:]
    let uniqueAlphabet = Array(alphabet.filter { return exists.updateValue(true, forKey: $0) == nil })
    
    // limitation: allows at most base36 radix
    guard case let radix = uniqueAlphabet.count
        where radix < 37 else {
        return (-1, nil)
    }
    
    // begin brute-force attempts
    for i in start..<start+numAttempts {
        let baseStr = String(i, radix: radix).characters
            .flatMap { Int(String($0), radix: radix) }
            .map { String(uniqueAlphabet[$0]) }
            .joinWithSeparator("")
        
        // construct attempt of correct length
        let attempt = padToTemplate(str: baseStr,
            withTemplate: String(count: wordLength, repeatedValue: alphabet.first!))
        
        // log
        //print(i, attempt)
        
        // test attempt
        if isCorrectPassword(attempt) { return (i, attempt) }
    }
    return (start+numAttempts, nil) // next to test
}

示例用法

示例用法#1

// unknown content closure
let someHashBashing : (String) -> Bool = {
    return $0 == "ask"
}

// setup alphabet
let alphabet = [Character]("abcdefghijklmnopqrstuvwxyz".characters)

// any success for 500 attempts?
if case (let i, .Some(let password)) =
    bruteForce(someHashBashing, forAlphabet: alphabet,
               forWordLength: 3, forNumberOfAttempts: 500) {
    print("Password cracked: \(password) (attempt \(i))")
} /* Password cracked: ask (attempt 478) */

示例用法 #2(将一个失败的“批次”与另一个一起拾取)

// unknown content closure
let someHashBashing : (String) -> Bool = {
    return $0 == "axk"
}

// setup alphabet
let alphabet = [Character]("abcdefghijklmnopqrstuvwxyz".characters)

// any success for 500 attempts?
let firstAttempt = bruteForce(someHashBashing, forAlphabet: alphabet,
                              forWordLength: 3, forNumberOfAttempts: 500)
if let password = firstAttempt.1 {
    print("Password cracked: \(password) (attempt \(firstAttempt.0))")
}
// if not, try another 500?
else {
    if case (let i, .Some(let password)) =
        bruteForce(someHashBashing, forAlphabet: alphabet,
                   forWordLength: 3, forNumberOfAttempts: 500,
                   startingFrom: firstAttempt.0) {
        print("Password cracked: \(password) (attempt \(i))")
    } /* Password cracked: axk (attempt 608) */
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-04-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-06-20
    • 1970-01-01
    • 2022-01-23
    相关资源
    最近更新 更多