Refactored Solution In Swift

I’ve been studying for a coding exam by doing the HackerRank test cases, for the most part I’ve been doing well, but I get hung up on some easy cases and you all help me when I can’t see the solution. I’m working on this problem:

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

  • Why NSDateFormatter is returning null for a 19/10/2014 in a Brazilian time zone?
  • Error: UIView's window is not equal to another view's window
  • How to create an alert box in iphone?
  • How can I get the index of sub UIView in the parent view?
  • No Title or Cancel Button for MFMessageComposeViewController
  • iOS6 Preferred Interface Orientation Not Working
  • A kidnapper wrote a ransom note but is worried it will be traced back to him. He found a magazine and wants to know if he can cut out whole words from it and use them to create an untraceable replica of his ransom note. The words in his note are case-sensitive and he must use whole words available in the magazine, meaning he cannot use substrings or concatenation to create the words he needs.

    Given the words in the magazine and the words in the ransom note, print Yes if he can replicate his ransom note exactly using whole words from the magazine; otherwise, print No.

    Input Format

    The first line contains two space-separated integers describing the respective values of (the number of words in the magazine) and (the number of words in the ransom note).
    The second line contains space-separated strings denoting the words present in the magazine.
    The third line contains space-separated strings denoting the words present in the ransom note.

    Each word consists of English alphabetic letters (i.e., to and to ).
    The words in the note and magazine are case-sensitive.
    Output Format

    Print Yes if he can use the magazine to create an untraceable replica of his ransom note; otherwise, print No.

    Sample Input

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

    Sample Output

    Yes
    Explanation
    

    All four words needed to write an untraceable replica of the ransom note are present in the magazine, so we print Yes as our answer.

    And here is my solution:

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

    I always time out on three of the 20 test-cases with this solution. So the solution seems to solve all the test cases, but not within their required time constraints. These are great practice, but it’s so extremely frustrating when you get stuck like this.

    I should note that I use Sets and the Set(a).intersection(Set(b)) because when I tried mapping an array of Strings, half the test-cases timed out.

    Any cleaner, or more efficient solutions will be greatly appreciated! Thank you!

    2 Solutions Collect From Internet About “Refactored Solution In Swift”

    I took the leisure of making some improvements on your code. I put comments to explain the changes:

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

    with the comments removed:

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

    It’s simpler, and much easier to follow. 🙂

    Thanks to @Alexander – I was able to solve this issue using NSCountedSet instead of my custom reduce method. It’s much cleaner and more efficient. Here is the solution:

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

    Many thanks!