0

Let's say I have a text and I want to check if the text contains a couple of words.

let text: String = "The rain in Spain"
let wordsA: [String] = [ "rain", "Spain" ] // should return true when compared with text
let wordsB: [String] = [ "rain", "Italy" ] // should return false when compared with text

What is the shortest, quickest way to check if my text contains ALL of the words?

I know, I can do:

var result: Bool = true
for word in wordsA {
    result = result && text.contains(word)
}

But I was wondering if there is a shorter way that involves using predicates. For instance, to check if the string contains ANY of the words I could do:

let result: Bool = wordsA.contains(where: text.contains)

Is there something similar that results only in true if ALL words are found?

2
  • 4
    dont have time to answer, but look up allSatisfy Commented May 2, 2021 at 23:18
  • @Alexander Wow that was quick, I just found it in the suggestions of my IDE, but thanks a lot! I posted it as answer. Commented May 2, 2021 at 23:19

2 Answers 2

1

I found a very simple solution, which is perfect for me:

let result: Bool = wordsA.allSatisfy(text.contains)

This works, but it's not fast like Leo's solution.

Sign up to request clarification or add additional context in comments.

Comments

1

You should enumerateSubstrings in your string using byWords option. It will return only words not a sequence of characters that might be just part of a larger word. Once you have all the words of your string you should create a set and check if it is a superset of your words. Your code should be something like:


extension StringProtocol {
    var setOfWords: Set<String> {
        var setOfWords: Set<String> = []
        enumerateSubstrings(in: startIndex..., options: .byWords) { word, _, _, _ in
            setOfWords.insert(word!)
        }
        return setOfWords
    }
}

let text = "The rain in Spain"

let wordsA = [ "rain", "Spain" ] // should return true when compared with text
let wordsB = [ "rain", "Italy" ] // should return false when compared with text

let setOfWords = text.setOfWords
setOfWords.isSuperset(of: wordsA)  // true
setOfWords.isSuperset(of: wordsB)  // false

Without extending StringProtocol:

let text = "The rain in Spain"
var setOfWords: Set<String> = []
text.enumerateSubstrings(in: text.startIndex..., options: .byWords) { word, _, _, _ in
    setOfWords.insert(word!)
}
let wordsA = [ "rain", "Spain" ]
let wordsB = [ "rain", "Italy" ]

setOfWords.isSuperset(of: wordsA)  // true
setOfWords.isSuperset(of: wordsB)  // false

6 Comments

I learned something new here, although it's definitely too overkill and not what I am after for my current circumstances. I will come back in the future, definitely. I'm pretty sure this will be useful to me one day.
@MartinBraun using a set is much faster and you don't have to search the string multiple times.
Sounds right, but is there a way to get rid of the extension to get the thing shorter?
@MartinBraun sure it would be easy. Note also that your method would return true also for the string "A train in Spain"
@MartinBraun If you are doing a search you shouldn't use contains. You should use localizedStandardContains which is diacritic and case insensitive
|

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.