0

I'm trying to filter an array of strings and return the strings that match based on two use cases.

Case 1: Only match if the searchString is at the beginning of the word.

For eg, if we have an array -> ["Ralph Breaks The Internet", "Bohemian Rhapsody", "Spider-Man: Into the Spider-Verse"] and we are trying to match it with a search string "r"

In this case, we should return ["Ralph Breaks The Internet", "Bohemian Rhapsody"] as "r" is at the beginning as in r in ralph and r in rhapsody. But "Spider-Man: Into the Spider-Verse" is not matched as the r is in the middle.

Case 2: Also match if the order of searchText is not exact.

For eg, if we have an array -> ["Ralph Breaks The Internet", "Bohemian Rhapsody", "Spider-Man: Into the Spider-Verse"] and we are trying to match it with a search string "Rhapsody Bohemian", it should still match even though the order is not the same.

Here's what I have tried so far:

func searchMovies(_ movieNames: [String], with searchText: String) -> [String] {
    
    var matchedMovies = [String]()
    
    for movie in movieNames {
        
        let movieWords = movie.split(separator: " ")
        let searchTextWords = searchText.split(separator: " ")
        var count = searchTextWords.count
        
        loop:
        for word in movieWords {
            for text in searchTextWords {
                let pattern = "\\b\(text)"
                if let _ = word.range(of: pattern, options: [.regularExpression, .caseInsensitive]) {
                    count -= 1
                }
                if count == 0 {
                    matchedMovies.append(movie)
                    break loop
                }
            }
        }
    }
    
    return matchedMovies
}

I'm aware this is not an efficient way to do this. It would be great if someone can direct me in some direction so that I can solve the same thing more efficiently.

1 Answer 1

1

For your specific case, you can format your regex pattern like this:

"^(?=.*\\bRhapsody)(?=.*\\bBohemian).*$"

To make it flexible, you could write your func like this:

func searchMovies(_ movieNames: [String], with searchText: String) -> [String] {
    // split search text into "words"
    let words: [String] = searchText.components(separatedBy: " ")
    
    // start of pattern string
    var pattern: String = "^"
    
    // for each word in search text
    words.forEach { w in
        // append regex to search for words beginning with word
        pattern += "(?=.*\\b\(w))"
    }
    
    // end of pattern string
    pattern += ".*$"
    
    return movieNames.filter { (movie) -> Bool in
        if let _ = movie.range(of: pattern, options: [.regularExpression, .caseInsensitive]) {
            return true
        }
        return false
    }
}

And you can call it with:

    let a: [String] = [
        "Ralph Breaks The Internet",
        "Bohemian Rhapsody",
        "Spider-Man: Into the Spider-Verse"
    ]
    
    let matchingArray = searchMovies(a, with: "Rhapsody Bohemian")

Note that this will match the beginning of the word (as you showed using "r"), so this will return the same result:

let matchingArray = searchMovies(a, with: "Rhap Boh")
Sign up to request clarification or add additional context in comments.

4 Comments

Thank you, it works very well. Could you explain what "^(?=.*\\bRhapsody)(?=.*\\bBohemian).*$" exactly does?
Go to this link: regex101.com/r/J38aas/1 ... it will show you a detailed explanation of that regex pattern.
@AmeyaVichare Copy/paste it in a Regex Online (remove the double backslash that's Swift specific), and you should get an idea.
Thanks @DonMag and Larme

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.