Mahmut K.
Mahmut K.

Reputation: 850

Search Multiple Words (or Characters) in String Array (swift)

I am developing an application for words. My problem resembles the problem: this

But, I want to do the search in the string array.

let myArr: [String] = ["BERLIN","ISTANBUL","TOKYO","NEWYORK","PRAGUE","WIEN"]
    let findChrs = ["I","N"]

   // myArr.contains(findChrs)  //so error

I want to: return 3 or "BERLIN","ISTANBUL" and "WIEN" (Contains "I" and "N")

I tried it, but it's too long...(I look individually. Too many words.):

for i in 0...myArr.count - 1 {

        let success = !findChrs.contains(where: { !myArr[i].contains($0) })
        if success {
            print(myArr[i])
        }

    }

Is there an easier way? Thank you so much.

Upvotes: 1

Views: 2297

Answers (2)

Nitin Alabur
Nitin Alabur

Reputation: 5812

There's a swifty way to solve your problem

I started with using just one filter on the myArr array using hardcoded search terms

let filteredStrings : [String] = myArr.filter({
        return $0.contains("I") && $0.contains("N")
    })

but thats going to help only if your findChars are always going to be I and N only.

Later I realized how to do it without hardcoding the find char characters:

ANSWER 1

let myArr: [String] = ["BERLIN","ISTANBUL","TOKYO","NEWYORK","PRAGUE","WIEN"]
let findChrs = ["I","N"]

let filteredStrings : [String] = myArr.filter({ (aString) in

    let hasChars = findChrs.filter({(bString) in
        return aString.contains(bString)
    })

    print(hasChars)

    return hasChars.count == findChrs.count
})

print(filteredStrings)

Instead of using $0, I think in the second chunk of code, its easier to understand whats happening with aString and bString.

Do check the code in a playground and see how this code works. If you haven't used the higher order functions, it can be a little daunting to understand filters without a playground and the print statements.

Update:

Was just thinking about this problem and I gave this alternate approach a try, using sets, map and filter. It is super swifty, and can be difficult to read/understand:

ANSWER 2, concise

let myArr: [String] = ["BERLIN","ISTANBUL","TOKYO","NEWYORK","PRAGUE","WIEN"]
let findChrs = ["I","N"]
let finderSet:Set<String> = Set(findChrs)
let filteredArray = myArr.filter {
    return Set($0.characters.map({String($0)})).intersection(finderSet).count == findChrs.count
}

For the sake of readability and ease of understanding, here's what is happening:

Answer 2, verbose

let filteredArray = myArr.filter { (aString) -> Bool in

    //for each string in myArr, convert it into an array of string chars
    let stringArray = aString.characters.map({aCharacter in
        String(aCharacter)
    })

    //convert string array into a set
    let aSet = Set(stringArray)

    // find the intersection (common elemnts from aSet and finderSet)
    let intersect = aSet.intersection(finderSet)

    //return true if aString has all of findChrs elements
    return intersect.count == findChrs.count
}

Both Answer 2 'concise' and 'verbose' will give you the same results.

Based on some simple code execution time check, it looks like Answer 1 is ~3x faster than Answer 2. So, Answer 1 is still a better choice and probably easier to understand.

Hope this helps anyone reading the answer understand filter and map!

Upvotes: 2

Alain T.
Alain T.

Reputation: 42139

You could also use a set for the filter provided you're not looking for patterns that contain repeated letters:

let myArr: [String] = ["BERLIN","ISTANBUL","TOKYO","NEWYORK","PRAGUE","WIEN"]
let findChrs = Set<Character>(["I","N"])   // let findChrs = Set("IN")

let matchingCities = myArr.filter{ findChrs.isSubset(of:$0) }

Upvotes: 2

Related Questions