在子线性时间内找到圣经中包含单词或短语的所有经文?

时间:2016-06-21 19:37:47

标签: swift

我打算使用字典作为我的数据结构,其中键等于圣经中找到的所有单词,值存储整数数组,其中每个整数指向一个经文数组的索引。我的实现看起来像这样:

let verses = [String]() //All verses in the bible
var dict = [String:[Int]]() //Data Structure

func fillDict(){
    for verseIndex in 0..<verses.count{
        let words = verses[verseIndex].componentsSeparatedByString(" ")
        for word in words{
            if let indexArray = dict[word]{
                var newIndexArray = indexArray
                newIndexArray.append(verseIndex)
                dict[word] = newIndexArray
            }else{
                let arr = [verseIndex]
                dict[word] = arr
            }
        }
    }
}

填写dict显然非常慢。我正在寻找更快的实现或不同的数据结构,以保证亚线性搜索时间。任何帮助将不胜感激。

1 个答案:

答案 0 :(得分:0)

<强> TL; DR: Swift的数组是写时复制的。使用NSMutableArray避免复制。

Swift的数组是值类型。要将新元素附加到长度为9的数组,需要分配容量为10的数组,复制前9个数组,并将新元素分配给最后一个插槽。这当然需要很多周期。为了演示,让我们稍微修改您的代码并通过Instruments运行它,以了解花了这么长时间的代码:

let bible = try String(contentsOfFile: "King James Bible.txt")
let verses = bible.componentsSeparatedByCharactersInSet(.newlineCharacterSet())

func fillDict1() -> [String: [Int]] {
    var dict = [String: [Int]]()

    for verseIndex in 0..<verses.count{
        let words = verses[verseIndex].componentsSeparatedByString(" ")
        for word in words{
            if let indexArray = dict[word]{
                var newIndexArray = indexArray
                newIndexArray.append(verseIndex)
                dict[word] = newIndexArray
            } else {
                let arr = [verseIndex]
                dict[word] = arr
            }
        }
    }

    return dict
}

fillDict1()

(我在Project Gutenberg使用了King James圣经。我知道verses阵列没有正确的诗节分解,但这与问题无关手。)

选择产品&gt;个人资料Cmd + I)然后选择时间分析器。这是最昂贵的三大电话:

Running Time        Self (ms)   Symbol Name
6464.0ms   61.8%    5.0         specialized Array._copyToNewBuffer(Int) -> ()
1093.0ms   10.4%    6.0         String.componentsSeparatedByString(String) -> [String]
896.0ms    8.5%     0.0         specialized _VariantDictionaryStorage.updateValue(B, forKey : A) -> B?
...
(Total: 9736ms)

正如所料,为新阵列分配新内存需要花费大量时间。幸运的是,Apple已经在NSMutableArray中为您解决了这个问题:

func fillDict2() -> [String: [Int]] {
    var tmp = [String: NSMutableArray]()

    for (verseIndex, verse) in verses.enumerate() {
        let words = verse.componentsSeparatedByString(" ")
        for word in words {
            let indexArray = tmp[word] ?? NSMutableArray()
            indexArray.addObject(verseIndex)

            tmp[word] = indexArray
        }
    }

    var dict = [String: [Int]]()
    for (word, verses) in tmp {
        dict[word] = ((verses as NSArray) as! [Int])
    }

    return dict
}

再次通过Instruments运行fillDict2(),这是我得到的:

Running Time        Self (ms)   Symbol Name
916.0ms   21.5%     8.0         String.componentsSeparatedByString(String) -> [String]
783.0ms   18.4%     27.0        specialized _VariantDictionaryStorage.nativeUpdateValue(B, forKey : A) -> B?
754.0ms   17.7%     0.0         specialized _VariantDictionaryStorage.updateValue(B, forKey : A) -> B?
...
(Total: 3911ms)

快2.5倍!显然,你也可以寻找其他优化。这是一场永无止境的比赛。你必须确定它何时足够快。