2017-01-18 77 views
1

我一直在做HackerRank測試用例,大部分時間我都做得很好,但是我掛斷了一些簡單的例子,當我一直在幫助我時看不到解決方案。我工作的這個問題:Swift中的重構解決方案

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的測試用例與此解決方案的時間了。所以這個解決方案似乎解決了所有的測試案例,但不是在他們所需的時間限制內。這些都是很好的練習,但是當你這樣陷入困境時,這是非常令人沮喪的。

我要指出,我用SetsSet(a).intersection(Set(b))因爲當我試圖映射的Strings陣列,一半的測試用例超時。

任何更清潔,或更有效的解決方案將不勝感激!謝謝!

+0

你可以使用'基金會'? – Alexander

+0

是的。我總是使用它,實際上 – Pierce

+2

你會想看看'NSCountedSet'。所有你需要做的就是創建一個包含所有需要的單詞(所需消息)的'NSCountedSet',以及所有可用單詞(來自雜誌)的另一個「NSCountedSet」。然後,檢查每一個唯一的單詞,第一組中的計數是否大於第二組中的計數 – Alexander

回答

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()) 

所以,很簡單,並且更容易跟蹤。 :)

+1

這可以進一步使用'ransomNoteWordCounts.contains(其中:{...})短接' –

+0

@MartinR不錯,保存數組分配:) – Alexander

+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()) 

非常感謝!

+0

看看你的'a'。你首先給它分配一個新的數組,但是在for循環中,你分配了'(readLine()!)components(separatedBy:「」)''的結果。第一次分配是不必要的 – Alexander