Reputation: 850
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
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
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