Find an object in array? - ios

Does Swift have something like _.findWhere in Underscore.js?
I have an array of structs of type T and would like to check if array contains a struct object whose name property is equal to Foo.
Tried to use find() and filter() but they only work with primitive types, e.g. String or Int. Throws an error about not conforming to Equitable protocol or something like that.

SWIFT 5
Check if the element exists
if array.contains(where: {$0.name == "foo"}) {
// it exists, do something
} else {
//item could not be found
}
Get the element
if let foo = array.first(where: {$0.name == "foo"}) {
// do something with foo
} else {
// item could not be found
}
Get the element and its offset
if let foo = array.enumerated().first(where: {$0.element.name == "foo"}) {
// do something with foo.offset and foo.element
} else {
// item could not be found
}
Get the offset
if let fooOffset = array.firstIndex(where: {$0.name == "foo"}) {
// do something with fooOffset
} else {
// item could not be found
}

You can use the index method available on Array with a predicate (see Apple's documentation here).
func index(where predicate: (Element) throws -> Bool) rethrows -> Int?
For your specific example this would be:
Swift 5.0
if let i = array.firstIndex(where: { $0.name == "Foo" }) {
return array[i]
}
Swift 3.0
if let i = array.index(where: { $0.name == Foo }) {
return array[i]
}
Swift 2.0
if let i = array.indexOf({ $0.name == Foo }) {
return array[i]
}

FWIW, if you don't want to use custom function or extension, you can:
let array = [ .... ]
if let found = find(array.map({ $0.name }), "Foo") {
let obj = array[found]
}
This generates name array first, then find from it.
If you have huge array, you might want to do:
if let found = find(lazy(array).map({ $0.name }), "Foo") {
let obj = array[found]
}
or maybe:
if let found = find(lazy(array).map({ $0.name == "Foo" }), true) {
let obj = array[found]
}

Swift 3
If you need the object use:
array.first{$0.name == "Foo"}
(If you have more than one object named "Foo" then first will return the first object from an unspecified ordering)

You can filter the array and then just pick the first element, as shown
in Find Object with Property in Array.
Or you define a custom extension
extension Array {
// Returns the first element satisfying the predicate, or `nil`
// if there is no matching element.
func findFirstMatching<L : BooleanType>(predicate: T -> L) -> T? {
for item in self {
if predicate(item) {
return item // found
}
}
return nil // not found
}
}
Usage example:
struct T {
var name : String
}
let array = [T(name: "bar"), T(name: "baz"), T(name: "foo")]
if let item = array.findFirstMatching( { $0.name == "foo" } ) {
// item is the first matching array element
} else {
// not found
}
In Swift 3 you can use the existing first(where:) method
(as mentioned in a comment):
if let item = array.first(where: { $0.name == "foo" }) {
// item is the first matching array element
} else {
// not found
}

Swift 3.0
if let index = array.index(where: { $0.name == "Foo" }) {
return array[index]
}
Swift 2.1
Filtering in object properties is now supported in swift 2.1. You can filter your array based on any value of the struct or class here is an example
for myObj in myObjList where myObj.name == "foo" {
//object with name is foo
}
OR
for myObj in myObjList where myObj.Id > 10 {
//objects with Id is greater than 10
}

Swift 4,
Another way to achieve this
using filter function,
if let object = elements.filter({ $0.title == "title" }).first {
print("found")
} else {
print("not found")
}

Swift 3
you can use index(where:) in Swift 3
func index(where predicate: #noescape Element throws -> Bool) rethrows -> Int?
example
if let i = theArray.index(where: {$0.name == "Foo"}) {
return theArray[i]
}

Swift 2 or later
You can combine indexOf and map to write a "find element" function in a single line.
let array = [T(name: "foo"), T(name: "Foo"), T(name: "FOO")]
let foundValue = array.indexOf { $0.name == "Foo" }.map { array[$0] }
print(foundValue) // Prints "T(name: "Foo")"
Using filter + first looks cleaner, but filter evaluates all the elements in the array. indexOf + map looks complicated, but the evaluation stops when the first match in the array is found. Both the approaches have pros and cons.

Another way to get access to array.index(of: Any) is by declaring your object
import Foundation
class Model: NSObject { }

Swift 3
if yourArray.contains(item) {
//item found, do what you want
}
else{
//item not found
yourArray.append(item)
}

Use contains:
var yourItem:YourType!
if contains(yourArray, item){
yourItem = item
}
Or you could try what Martin pointed you at, in the comments and give filter another try: Find Object with Property in Array.

Swift 3:
You can use Swifts built in functionality to find custom objects in an Array.
First you must make sure your custom object conforms to the: Equatable protocol.
class Person : Equatable { //<--- Add Equatable protocol
let name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
//Add Equatable functionality:
static func == (lhs: Person, rhs: Person) -> Bool {
return (lhs.name == rhs.name)
}
}
With Equatable functionality added to your object , Swift will now show you additional properties you can use on an array:
//create new array and populate with objects:
let p1 = Person(name: "Paul", age: 20)
let p2 = Person(name: "Mike", age: 22)
let p3 = Person(name: "Jane", age: 33)
var people = [Person]([p1,p2,p3])
//find index by object:
let index = people.index(of: p2)! //finds Index of Mike
//remove item by index:
people.remove(at: index) //removes Mike from array

For Swift 3,
let index = array.index(where: {$0.name == "foo"})

Use Dollar which is Lo-Dash or Underscore.js for Swift:
import Dollar
let found = $.find(array) { $0.name == "Foo" }

For example, if we had an array of numbers:
let numbers = [2, 4, 6, 8, 9, 10]
We could find the first odd number like this:
let firstOdd = numbers.index { $0 % 2 == 1 }
That will send back 4 as an optional integer, because the first odd number (9) is at index four.

Related

how can I remove a struct element from an array of structs based on the id of the element in Swift?

I have a structure in my Swift app:
open class Cluster : NSObject {
open var username: String? = ""
open var id: String? = ""
open var deleted: Bool? = false
}
and now I'm iterating over this array and I'm adding new elements to it, but only in case those elements are not there yet:
if(!self.array.contains(where: {$0.id==temp.id}))
{
self.array.append(temp);
}
I want to tweak this code so that it not only adds new elements if they're not there, but also removes the ones that - in the meantime - had their flag deleted changed to true.
I started writing this code:
if(!self.array.contains(where: {$0.id==temp.id}))
{
self.array.append(temp);
} else {
if(temp.deleted == true){
self.array.remove //how can I remove here this specific element?
}
}
To remove a particular element from an array, you are supposed to get index of that element first and then delete as shown below:
if let index:Int = self.array.index(where: {$0.id == temp.id && $0.deleted == true}) {
self.array.remove(at: index)
}
First, I suggest you fix your class:
An optional Bool makes no sense - the object is either deleted or not
An optional id doesn't make much sense either; All objects need an id
If you implement the hash and equality parts of NSObject then you get access to array's index(of:) method and you can use sets.
Cluster.swift
open class Cluster : NSObject {
open var username: String? = ""
open let id: String
open var isDeleted: Bool = false
init(id: String) {
self.id = id
}
open override var hashValue: Int {
get {
return self.id.hashValue
}
}
open override func isEqual(_ object: Any?) -> Bool {
guard let rhs = object as? Cluster else {
return false
}
let lhs = self
return lhs.id == rhs.id
}
}
Now, given an array of Cluster objects, you can remove the deleted ones using:
let cleanArray = dirtyArrayOfCluster.filter {
!$0.isDeleted
}
And you can remove duplicates by passing the array through a set:
let deDupedArray = Array(Set(cleanArray))
if temp.deleted == true, let index = array.index(where: { $0.id == temp.id }) {
array.remove(at: index)
}
What about this?
if array.contains(where: { $0.id == temp.id } ) {
array.append(temp)
}
array = array.filter { $0.deleted == true }
The first part add temp only if it is not into the array.
The last line removes all the elements marked as deleted.

Find match in array for given string?

Ok so lets say I have an custom object for vocabulary words, alternate way of being written, and their meaning.
class VocabEntry {
var kanji:String?
var kana:String?
var meaning:String?
}
Then I have an array comprised of these objects. Here's one for example.
let newVocabWord = VocabEntry()
newVocabWord.kanji = "下さい”
newVocabWord.kana = "ください”
newVocabWord.meaning = "please"
Now I have a string of text:
let testString = "すみません、十階のボタンを押して下さい"
How can I compare that string to my array of custom objects (that contain strings) and reference the matches?
I tried.
if vocabArray.contains( { $0.kanji == testString }) {
print("yes")
}
But that trying to match the entire string. If I change testString to "下さい" it works, but that's not what I'm looking for. What I want is for it to say "Here I found 下さい in xx object. Here's the index number."
You can use indexOf() with a predicate to find the index of a
matching entry, and containsString() to search for substrings.
Since the kanji property is optional, you have to check that via
optional binding:
if let index = vocabArray.indexOf({ entry in
if let kanji = entry.kanji {
// check if `testString` contains `kanji`:
return testString.containsString(kanji)
} else {
// `entry.kanji` is `nil`: no match
return false
}
}) {
print("Found at index:", index)
} else {
print("Not found")
}
This can be written more concise as
if let index = vocabArray.indexOf({
$0.kanji.flatMap { testString.containsString($0) } ?? false
}) {
print("Found at index:", index)
} else {
print("Not found")
}
To get the indices of all matching entries, the following would work:
let matchingIndices = vocabArray.enumerate().filter { (idx, entry) in
// filter matching entries
entry.kanji.flatMap { testString.containsString($0) } ?? false
}.map {
// reduce to index
(idx, entry) in idx
}
print("Found at indices:", matchingIndices)

Keep ordered array of dictionary values updated by index

I have a dictionary that's updated from another class. I have a property observer on the dictionary so I know when a value has been added or removed.
I create a sorted array based on the values of the dictionary. I need to keep this array updated and retain the index associated with the update for use with a UITableView. My UI is as such that a wholesale reloading of data isn't possible - I need to directly insert or remove rows based on what the update was.
I have simplified this into a playground:
func dictionaryUpdated() {
print("dictionary updated")
// Add or remove string at index depending on order.
}
var myDictionary : [Int:String] = ["Bob".hashValue:"Bob","Dave".hashValue:"Dave","Yoda".hashValue:"Yoda","Windu".hashValue:"Windu","Obi Wan".hashValue:"Obi Wan","Qui-gon".hashValue:"Qui-gon","Anakin".hashValue:"Anakin"] { didSet { dictionaryUpdated() } }
func addEntry(entry: String) {
myDictionary[entry.hashValue] = entry
}
func removeEntry(entry: String) {
myDictionary.removeValueForKey(entry.hashValue)
}
// sort the keys alphabetically while creating the array
var valuesArray = myDictionary.values.sort { (lhs, rhs) -> Bool in
return lhs < rhs
}
I have tried using an NSMutableOrderedSet but the keys can only be Strings.
Just playing around in playground. Can be much more elegant though...
var valuesArray: [String] = [] { didSet { valuesArray.sortInPlace { $0 < $1 } } }
func dictionaryUpdated(old: [Int: String]) {
let added = myDictionary.count > old.count
let item: [String] = added ? myDictionary.values.filter { !old.values.contains($0) } : old.values.filter { !myDictionary.values.contains($0) }
valuesArray += item
let index = valuesArray.indexOf(item[0])!
print("item " + (added ? "added" : "removed") + ": \(item) at index \(index)")
}
var myDictionary: [Int: String] = ["Yoda".hashValue: "Yoda", "Windu".hashValue: "Windu", "Obi Wan".hashValue: "Obi Wan"] {
didSet {
dictionaryUpdated(oldValue)
}
}
addEntry("Darth Vader")
print(valuesArray)
Output:
item added: ["Darth Vader"] at index 0
["Darth Vader", "Obi Wan", "Windu", "Yoda"]
Assuming you have the sorted array before and after the property change (which can be achieved via another instance variable), what you need to do is to compare the old and the new array, and detect which which indexes changed.
An elegant solution to this problem would be to add a diff method to the Array class which computes the difference. The method might look something like this:
extension Array where Element: Equatable {
func diff(other: [Element]) -> (added: [Int], deleted: [Int], moved: [(from: Int, to: Int)]) {
var added: [Int] = []
var deleted: [Int] = []
var moved: [(from: Int, to: Int)] = []
for (i, item) in enumerate() {
if let j = other.indexOf({$0 == item}) {
if i != j {
moved.append((from: i, to: j))
}
} else {
deleted.append(i)
}
}
for (i, item) in other.enumerate() {
if indexOf({$0 == item}) == nil {
added.append(i)
}
}
return (added: added, deleted: deleted, moved: moved)
}
}
You would then use like this: valuesArray.diff(oldValuesArray).

Find first element matching condition in Swift array (e.g. EKSource)

I would like to find the first EKSource of type EKSourceType.Local with a "single"-line expression in Swift. Here is what I currently have:
let eventSourceForLocal =
eventStore.sources[eventStore.sources.map({ $0.sourceType })
.indexOf(EKSourceType.Local)!]
Is there a better way of doing this (such as without mapping and/or with a generic version of find)?
Alternatively in Swift3 you could use:
let local = eventStore.sources.first(where: {$0.sourceType == .Local})
There's a version of indexOf that takes a predicate closure - use it to find the index of the first local source (if it exists), and then use that index on eventStore.sources:
if let index = eventStore.sources.indexOf({ $0.sourceType == .Local }) {
let eventSourceForLocal = eventStore.sources[index]
}
Alternately, you could add a generic find method via an extension on SequenceType:
extension SequenceType {
func find(#noescape predicate: (Self.Generator.Element) throws -> Bool) rethrows -> Self.Generator.Element? {
for element in self {
if try predicate(element) {
return element
}
}
return nil
}
}
let eventSourceForLocal = eventStore.sources.find({ $0.sourceType == .Local })
(Why isn't this there already?)
I don't understand why you're using map at all. Why not use filter? You will then end up with all the local sources, but in actual fact there will probably be only one, or none, and you can readily find out by asking for the first one (it will be nil if there isn't one):
let local = eventStore.sources.filter{$0.sourceType == .Local}.first
Swift 4 solution that also handles the situation when there are no elements in your array that match your condition:
if let firstMatch = yourArray.first{$0.id == lookupId} {
print("found it: \(firstMatch)")
} else {
print("nothing found :(")
}
Swift 5 If you want to find out from Array of Model then speciyfy $0.keyTofound otherwise use $0
if let index = listArray.firstIndex(where: { $0.id == lookupId }) {
print("Found at \(index)")
} else {
print("Not found")
}
Let's try something more functional:
let arr = [0,1,2,3]
let result = arr.lazy.map { print("💥"); return $0 }.first(where: { $0 == 2 })
print(result) // 3x 💥 then 2
Whats cool about this?
You get access to element or i while you search. And it's functional.
For Swift 3 you'll need to make a few small changes to Nate's answer above. Here's the Swift 3 version:
public extension Sequence {
func find(predicate: (Iterator.Element) throws -> Bool) rethrows -> Iterator.Element? {
for element in self {
if try predicate(element) {
return element
}
}
return nil
}
}
Changes: SequenceType > Sequence, Self.Generator.Element > Iterator.Element

How to determine if one array contains all elements of another array in Swift?

I have 2 arrays:
var list:Array<Int> = [1,2,3,4,5]
var findList:Array<Int> = [1,3,5]
I want to determine if list Array contains all findList elements.
By the way, elements might be String as well or other type.
How to do that?
I know that Swift provides contains method that works with one item.
Instead of iterating through arrays and doing filtering yourself, you can use NSSet to do all the work for you.
var list:Array<Int> = [1,2,3,4,5]
var findList:Array<Int> = [1,3,5]
let listSet = NSSet(array: list)
let findListSet = NSSet(array: findList)
let allElemtsEqual = findListSet.isSubsetOfSet(otherSet: listSet)
NSSet is a lot faster than arrays at checking if it contains any object. In fact it's what it's designed for.
Edit: Using Swift's built-in Set.
let list = [1,2,3,4,5]
let findList = [1,3,5]
let listSet = Set(list)
let findListSet = Set(findList)
//**Swift 4.2 and Above**
let allElemsContained = findListSet.isSubset(of: listSet)
//below versions
//let allElemsContained = findListSet.isSubsetOf(listSet)
allSatisfy seems to be what you want, assuming you can't conform your elements to Hashable and use the set intersection approach others have mentioned:
let containsAll = subArray.allSatisfy(largerArray.contains)
Since Swift 4.2 you can write:
extension Array where Element: Equatable {
func satisfy(array: [Element]) -> Bool {
return self.allSatisfy(array.contains)
}
}
Otherwise for Swift 3, Swift 4 you can write this:
extension Array where Element: Equatable {
func contains(array: [Element]) -> Bool {
for item in array {
if !self.contains(item) { return false }
}
return true
}
}
You can see the:
contains method here
allSatisfy method here
This is just a simple extension that check if the array that you give is in the current array (self)
You can use the filter method to return all elements of findList which are not in list:
let notFoundList = findList.filter( { contains(list, $0) == false } )
then check if the length of the returned array is zero:
let contained = notFoundList.count == 0
Note that his solution traverses the entire findList array, so it doesn't stop as soon as a non contained element is found. It should be used if you also want to know which elements are not contained.
If you just need a boolean stating whether all elements are contained or not, then the solution provided by Maxim Shoustin is more efficient.
Consider following generic method:
func arrayContainsArray<S : SequenceType where S.Generator.Element : Equatable>
(src:S, lookFor:S) -> Bool{
for v:S.Generator.Element in lookFor{
if contains(src, v) == false{
return false
}
}
return true
}
The advantage - method stops after 1st fail and do not continue over findList
Tests
var listAsInt:Array<Int> = [1,2,3,4,5]
var findListAsInt:Array<Int> = [1,3,5]
var result = arrayContainsArray(listAsInt, findListAsInt) // true
listAsInt:Array<Int> = [1,2,3,4,5]
findListAsInt:Array<Int> = [1,3,5,7,8,9]
result = arrayContainsArray(listAsInt, findListAsInt) // false
var listOfStr:Array<String> = ["aaa","bbb","ccc","ddd","eee"]
var findListOfStr:Array<String> = ["bbb","ccc","eee"]
result = arrayContainsArray(listOfStr, findListOfStr) // true
listOfStr:Array<String> = ["aaa","bbb","ccc","ddd","eee"]
findListOfStr:Array<String> = ["bbb","ccc","eee","sss","fff","ggg"]
result = arrayContainsArray(listOfStr, findListOfStr) // false
(tested on Beta7)
As a complement to Sequence.contains(element) handling multiple elements, add this extension:
public extension Sequence where Element : Hashable {
func contains(_ elements: [Element]) -> Bool {
return Set(elements).isSubset(of:Set(self))
}
}
Used:
list.contains(findList)
Since this uses Set/Hashable it performs much better than Equatable alternatives.
Right now, I'd probably use something like:
let result = list.reduce(true, { $0 ? contains(findList, $1) : $0 })
...but then I did just read this article, which might be biasing me towards this kind of solution. You could probably make this more efficient without making it completely unreadable, but it's early and I've not had my coffee.
Extend the Array with the following methods:
extension Array {
func contains<T where T : Equatable>(obj: T) -> Bool {
return self.filter({$0 as? T == obj}).count > 0
}
func isEqualTo< T : Equatable> (comparingArray : [T]) -> Bool {
if self.count != comparingArray.count {
return false
}
for e in comparingArray {
if !self.contains(e){
return false
}
}
return true
}
}
An example of how you can use it like this:
if selectedDates.isEqualTo(originalDates) {
//Arrays the same hide save button
} else {
//Arrays not the same, show Save & Discard Changes Button (if not shown)
}
Shout out to #David Berry for the contain method.
None of the previous answers seem to be right.
consider:
let a = [2,2]
let b = [1,2,3]
we wouldn't say that b actually "contains" a, but if your algorithm is based on for-loop & swift's built-in contains(element:) or a set, the above case would pass.
I use this set of extended methods myself. I hope this code snippet helps:
// Array + CommonElements.swift
import Foundation
public extension Array where Element: Hashable {
func set() -> Set<Array.Element> {
return Set(self)
}
func isSubset(of array: Array) -> Bool {
self.set().isSubset(of: array.set())
}
func isSuperset(of array: Array) -> Bool {
self.set().isSuperset(of: array.set())
}
func commonElements(between array: Array) -> Array {
let intersection = self.set().intersection(array.set())
return intersection.map({ $0 })
}
func hasCommonElements(with array: Array) -> Bool {
return self.commonElements(between: array).count >= 1 ? true : false
}
}
This is Maxim Shoustin's answer updated for Swift 3:
func arrayContainsArray<S : Sequence>
(src:S, lookFor:S) -> Bool where S.Iterator.Element : Equatable{
for v:S.Iterator.Element in lookFor{
if src.contains(v) == false{
return false
}
}
return true
}
If you need to determine, that one array is subArray of another.
public extension Array where Element: Equatable {
func isSuperArray(of array: Array<Element>) -> Bool {
guard
count >= array.count,
let indexes = array.first.flatMap(indexes(of:)),
!indexes.isEmpty else {
return false
}
let arraysForComparison = indexes
.compactMap { index -> [Element]? in
guard index + (array.count - 1) <= count else { return nil }
return Array(self[index..<(index + array.count)])
}
return arraysForComparison.contains(array)
}
func isSubArray(of array: Array<Element>) -> Bool {
array.isSuperArray(of: self)
}
private func indexes(of element: Element) -> [Index] {
enumerated()
.filter { element == $0.1 }
.map { index, _ in index }
}
}
Example of usage:
let array1 = [1, 2, 3, 4]
let array2 = [2, 3]
print(array1.isSuperArray(of: array2)) // true
print(array2.isSubArray(of: array1)) // true
print(array2.isSuperArray(of: array1)) // false
print(array1.isSubArray(of: array2)) // false

Resources