Swift中的重构解决方案

时间:2017-01-18 21:34:19

标签: ios arrays swift set

我一直在通过HackerRank测试案例进行编码考试,大部分时间我都做得很好,但是我对一些简单的案例感到困惑,当我看不到的时候你都会帮助我解决方案。我正在解决这个问题:

https://www.hackerrank.com/challenges/ctci-ransom-note

一名绑架者写了一封赎金票据,但担心会被追溯到他身边。他找到了一本杂志,想要知道他是否可以从中删除整个单词,并用它们来创建一个无法追踪的赎金笔记复制品。他的笔记中的单词区分大小写,他必须使用杂志中的全部单词,这意味着他不能使用子串或连接来创建他需要的单词。

鉴于杂志中的文字和赎金中的文字,如果他能用杂志上的全文完全复制他的赎金笔记,请打印是;否则,打印编号

输入格式

第一行包含两个以空格分隔的整数,用于描述(杂志中的单词数)和(勒索单中的单词数)的相应值。 第二行包含空格分隔的字符串,表示杂志中存在的单词。 第三行包含空格分隔的字符串,表示赎金票据中存在的单词。

每个单词由英文字母组成(即to和to)。 笔记和杂志中的单词区分大小写。 输出格式

打印是的,如果他可以使用该杂志创建一个无法追踪的赎金票据复制品;否则,打印编号

示例输入

6 4
give me one grand today night
give one grand today

示例输出

Yes
Explanation

杂志中有一个写一个无法追踪的赎金笔记复制品所需的四个字,所以我们打印是答案。

这是我的解决方案:

import Foundation

func main() -> String {
    let v = readLine()!.components(separatedBy: " ").map{Int($0)!}
    var a = [String](); var b = [String]()
    if v[0] < v[1] { return "No"}
    for i in 0 ..< 2 {
        if i == 0 {
            a = (readLine()!).components(separatedBy: " ")
        } else { b = (readLine()!).components(separatedBy: " ") }
    }

    // Get list of elements that intersect in each array
    let filtered = Set(a).intersection(Set(b))
    // Map set to set of Boolean where true means set a has enough words to satisfy set b's needs
    let checkB = filtered.map{ word in reduceSet(b, word: word) <= reduceSet(a, word: word) }

    // If mapped set does not contain false, answer is Yes, else No
    return !checkB.contains(false) ? "Yes" : "No"
}
func reduceSet(_ a: [String], word: String) -> Int {
    return (a.reduce(0){ $0 + ($1 == word ? 1 : 0)})
}

print(main())

我总是在这个解决方案的20个测试案例中有3个超时。因此,解决方案似乎解决了所有测试用例,但不在其所需的时间限制范围内。这些都很棒,但是当你遇到这种情况时,它会非常令人沮丧。

我应该注意,我使用SetsSet(a).intersection(Set(b)),因为当我尝试映射Strings数组时,有一半的测试用例超时。

任何更清洁,更高效的解决方案将不胜感激!谢谢!

2 个答案:

答案 0 :(得分:4)

感谢@Alexander - 我能够使用NSCountedSet而不是我的自定义reduce方法解决此问题。它更清洁,更高效。这是解决方案:

import Foundation

func main() -> String {
    let v = readLine()!.components(separatedBy: " ").map{Int($0)!}
    var a = [String](); var b = [String]()
    if v[0] < v[1] { return "No"}
    for i in 0 ..< 2 {
        if i == 0 {
            a = (readLine()!).components(separatedBy: " ")
        } else { b = (readLine()!).components(separatedBy: " ") }
    }
    let countA = NSCountedSet(array: a)
    let countB = NSCountedSet(array: b)
    let intersect = Set(a).intersection(Set(b))
    let check = intersect.map{ countB.count(for: $0) <= countA.count(for: $0) }
    return !check.contains(false) ? "Yes" : "No"
}
print(main())

非常感谢!

答案 1 :(得分:2)

我悠闲地对您的代码进行了一些改进。我发表评论来解释这些变化:

import Foundation

func main() -> String {
    // Give more meaningful variable names
    let firstLine = readLine()!.components(separatedBy: " ").map{Int($0)!}
    let (magazineWordCount, ransomNoteWordCount) = (firstLine[0], firstLine[1])

    // a guard reads more like an assertion, stating the affirmative, as opposed to denying the negation.
    // it also 
    guard magazineWordCount > ransomNoteWordCount else { return "No" }

    // Don't use a for loop if it only does 2 iterations, which are themselves hardcoded in.
    // Just write the statements in order.
    let magazineWords = readLine()!.components(separatedBy: " ")
    let ransomNoteWords = readLine()!.components(separatedBy: " ") //You don't need ( ) around readLine()!

    let magazineWordCounts = NSCountedSet(array: magazineWords)
    let ransomNoteWordCounts = NSCountedSet(array: ransomNoteWords)

    // intersect is a verb. you're looking for the noun, "intersection"
    // let intersection = Set(a).intersection(Set(b))
    // let check = intersect.map{ countB.count(for: $0) <= countA.count(for: $0) }

    // You don't actually care for the intersection of the two sets.
    // You only need to worry about exactly the set of words that
    // exists in the ransom note. Just check them directly.
    let hasWordWithShortage = ransomNoteWordCounts.contains(where: { word in
       magazineWordCounts.count(for: word) < ransomNoteWordCounts.count(for: word)
    })

    // Don't negate the condition of a conditional expression. Just flip the order of the last 2 operands.
    return hasWordWithShortage ? "No" : "Yes"
}
print(main())

删除了评论:

import Foundation

func main() -> String {
    let firstLine = readLine()!.components(separatedBy: " ").map{Int($0)!}
    let (magazineWordCount, ransomNoteWordCount) = (firstLine[0], firstLine[1])

    guard magazineWordCount > ransomNoteWordCount else { return "No" }

    let magazineWords = readLine()!.components(separatedBy: " ")
    let ransomNoteWords = readLine()!.components(separatedBy: " ")

    let magazineWordCounts = NSCountedSet(array: magazineWords)
    let ransomNoteWordCounts = NSCountedSet(array: ransomNoteWords)

    let hasWordWithShortage = ransomNoteWordCounts.contains{ word in
       magazineWordCounts.count(for: word) < ransomNoteWordCounts.count(for: word)
    }

    return hasWordWithShortage ? "No" : "Yes"
}
print(main())

它更简单,更容易理解。 :)