Check duplicates properties on Swift array - ios

I have a custom class called Place with 3 properties:
Name (String)
Category (String)
GeoPoint (CLLocationCoordinate2D)
I have an array of type [Place] of 100 objects and I want to check if there are duplicates on the GeoPoint property (just on this one).
How can I check duplicates of a specific property in an array of custom objects?
Thanks!

Although the accepted answer is good, I'd like to chip in.
There are two more ways to achieve what you want, and they both benefit from functionalities provided by SDK.
1 - Use Sets as Tj3n mentioned in a comment.
To achieve this you would need to make your Place conform to Hashable protocol.
class Place : Hashable {
var name = ""
var category = ""
var geoPoint: CLLocationCoordinate2D = CLLocationCoordinate2D()
var hashValue: Int {
get {
return geoPoint.longitude.hashValue &+ geoPoint.latitude.hashValue
}
}
}
func ==(lhs: Place, rhs: Place) -> Bool {
return lhs.geoPoint.latitude == rhs.geoPoint.latitude && lhs.geoPoint.longitude == rhs.geoPoint.longitude
}
The &+ operator in hashValue means "add, and don't crash at overflow". Using it is as straightforward as it can - let set = Set(yourArrayOfPlaces) - the set will contain only unique, in regard to geoPoint, places.
2 - Use KVC. While this is more of an Objective-C world, I find it a useful tool. To achieve this, you'd need to make Place inherit from NSObject. Then getting an array of unique places can be reduced to this one line :
let uniquePlaces = (yourPlacesArray as NSArray).value(forKeyPath: "#distinctUnionOfObjects.geoPoint")

You can do something like this:
var dict : [String : Int] = [:]
for place in arr {
if dict[place.GeoPoint] != nil { // Not in dictionary
if dict[place.GeoPoint] >= 1 { // If already there
return true // Duplicate
} else {
dict[place.GeoPoint]! += 1 // Increment instance
}
} else {
dict[place.GeoPoint] = 0 // Put in dictionary
}
}
return false // No duplicates
Where you loop through a [Place] array and check to see how many have the same GeoPoint. Then check to see if there's one there more than once.

Related

Dictionary not appending values

I think I'm introducing some logic error and I might be missing something here.
Please consider the following code:
// Model
class MyModel: NSObject {
let month: Int
let destiny: String
init(month: Int, destiny: String) {
self.month = month
self.destiny = destiny
}
}
var datasource: [MyModel] = []
var dict: [Int : [MyModel]] = [:]
func fillDatasource() {
for _ in 0...20 {
let month = Int.random(in: 1...12)
let destiny = "Any"
let model = MyModel(month: month, destiny: destiny)
datasource.append(model)
}
}
func fillDict() {
datasource.forEach {
let month = $0.month
dict[month]?.append($0)
}
print(dict) // always empty
}
fillDatasource()
fillDict()
Inside my fillDict function the array is always nil.
I think this is because the key doesn't exist , so the value cannot be appended to that specific key.
My question is: if the key doesn't exist, calling the append function would insert the key as well?
Am I missing something here?
Thanks.
Your assumption is incorrect and there is no reason to think that this would insert a new array.
It might seem intuitive for this case but it may be very wrong for some cases. How about something like this:
garages[myName]?.parkCar(myCar)
Should this construct a new garage for my car? I think not. But even if so; what if default constructor is unavailable and this is actually defined as a protocol:
protocol Garage {
func parkCar(_ car: Car)
}
var garages[String: Garage]
there is no way for Swift to fill in this object automatically.
Technically there would be a possible solution for this work that Swift would automatically construct an object for you in dictionary if this object had a default constructor and possibly the object type is a struct or a final class... But this would most likely only introduce more confusion than it would solve.
The most straight forward solution to your example is what #Sh_Khan wrote (but later deleted) which is:
if dict[month] == nil {
dict[month] = [$0]
}
else {
dict[month]?.append($0)
}
Probably some more feasible approach would be
dict[month] = (dict[month] ?? []) + [$0]
but as described in a comment there is already a method that does exactly that for you:
dict[month, default: []].append($0)
I hope we can agree that this is a more general approach and it fixes all cases. For instance
garages[myName, default: PublicGarage(parkingSpot: myName)].parkCar(myCar)
You can update your fillDict method to the following:
func fillDict() {
datasource.forEach {
let month = $0.month
if dict.keys.contains(month) {
dict[month]?.append($0)
} else {
dict[month] = [$0]
}
}
print(dict)
}
Explanation:
We need to check if the month key already exits in the dictionary, than append in it's array else we are assigning a new array against a month in the dictionary
Dic is empty because dic[month] is nil, the value has never been altered.
To group array by a property of the array elem, I'd use the following:
dic = Dictionary(grouping: datasource) { (model) -> Int in
return model.month
}

How to remove common items from two struct arrays in Swift

In my app I have two struct arrays and I want to remove common items from one of them. My struct:
struct PeopleSelectItem {
var name = ""
var id = ""
var added = false
}
My arrays:
var people : [PeopleSelectItem] = []
var selectedPeople : [PeopleSelectItem] = []
I want to remove items from people array if they exist (compare by id) on selectedPeople array.
I tried several array filtering and converting to set but none of them worked. What can I do here?
Thanks!
Get an array of all ids in selectedPeople
let selectedPeopleIDs = selectedPeople.map(\.id)
Filter the items whose id is not in the array
let filteredPeople = people.filter { !selectedPeopleIDs.contains($0.id) }
If you know that people equal each other if the id is the same then you can conform your struct to the Equatable protocol and you can use the array filter method.
struct PeopleSelectItem : Equatable {
var name = ""
var id = ""
var added = false
}
func ==(lhs: PeopleSelectItem, rhs: PeopleSelectItem) -> Bool {
return lhs.id == rhs.id
}
func filterPeople() {
//swift 2, 3:
people = people.filter{!selectedPeople.contains($0)}
//swift older versions:
people = people.filter{!contains(selectedPeople, $0)}
}
If people might have a significant amount of entries, performance should be considered. So, instead of searching with an n^2 algorithm, you should make use of Swifts dictionary and the corresponding hash-search to find items.
If Id is unique for people then I would store them in a dictionary like:
var peopleDict: [String:PeopleSelectItem] = [:]()
You can easily convert from the array you have to this dictionary:
people.foreach {peopleDict[$0.id] = $0}
With this dictionary it's very easy to delete single entries:
selectedPeople.foreach {peopleDict.remove($0.id)}
Optionally to switch back to an array for people you just say:
let filteredPeople = peopleDict.values as [PeopleSelectItem]
Remarks
I assumed, that selectedPeople is smaller then the base of all people. If this is not the case, you should pu selectedPeople in a dictionary.
did I say I like this Spark like api? I think I do so.
I just wrote that code from top of my mind. If it is not completely syntactically correct let me know and I correct it.

Remove duplicate structs in array based on struct property in Swift

I have made a simple struct and implemented the Equatable protocol :
extension MyModelStruct: Equatable {}
func ==(lhs: NModelMatch, rhs: NModelMatch) -> Bool {
let areEqual = lhs.id == rhs.id
return areEqual
}
public struct MyModelStruct {
var id : String?
var staticId : String?
init(fromDictionary dictionary: NSDictionary){
id = dictionary["id"] as? String
...
}
Then in my project i get an array of [MyModelStruct], what i what to do is to remove all the MyModelStruct that have the same id
let val1 = MyModelStruct(id:9, subId:1)
let val2 = MyModelStruct(id:10, subId:1)
let val3 = MyModelStruct(id:9, subId:10)
var arrayOfModel = [val1,val2,val3]; // or set but i do not know how to use a set
var arrayCleaned = cleanFunction[M2,M3]
How can i make the cleanFunction ?
Can someone help please.
Thanks for all.
Xcode : Version 7.3.1
I really don't want people to just take an answer because it's the only one, that's why I'm showing you how you can use the power of sets. Sets are used wherever it doesn't make sense to have more than one, either it's there or not. Sets provide fast methods for checking whether an element is in the set (contains), removing an element (remove), combining two sets (union) and many more. Often people just want an array because they're familiar with it, but often a set is really what they need. With that said, here is how you can use a set:
struct Model : Hashable {
var id : String?
var hashValue: Int {
return id?.hashValue ?? 0
}
}
func ==(l: Model, r: Model) -> Bool {
return l.id == r.id
}
let modelSet : Set = [
Model(id: "a"),
Model(id: "hello"),
Model(id: "a"),
Model(id: "test")
]
// modelSet contains only the three unique Models
The only requirement for a type in order to be in a set is the Hashable protocol (which extends Equatable). In your case, you can just return the underlying hashValue of the String. If your id is always a number (which it probably is), you should change the type of id to be an Int, because Strings are much less efficient than Ints and it doesn't make sense to use a String.
Also consider storing this property somewhere, so that every time you receive new models, you can just do
modelSet.unionInPlace(newModels)
Use a Set instead of an Array.
I agree you are better off using a Set. You should be able to initialize the Set using an Array, e.g., var arrayOfModel: Set = [val1, val2, val3]. But because you are using a custom type, you will need to make sure that MyModelStruct conforms to hashable. This link has a good explanation.
But if you want to use an array then you need to change
let areEqual = rhs.id == lhs.id
to
let areEqual = rhs.id == lhs.id && rhs.subId == lhs.subId)
You need to modify your struct to have a subId property (and make the variables Int instead of String.
In answer to your question, yes you do need to iterative over the array.
If you extend the Array type with this function :
extension Array
{
func uniqueValues<V:Equatable>( value:(Element)->V) -> [Element]
{
var result:[Element] = []
for element in self
{
if !result.contains({ value($0) == value(element) })
{ result.append(element) }
}
return result
}
}
You'll be able to clean up duplicates using :
var arrayCleaned = arrayOfModel.uniqueValues({$0.id!})
without needing to make the structure Equatable.
Please note that this is not going to be efficient if your array is very large and you might want to consider filtering insertions into your array at the source if possible.

How to achieve object merging through generics in swift?

Imagine I have a class Number:
class Number {
var val: Double?
}
and have two instances of that class, A and B.
Now imagine I want to merge Binto Athrough a statement like
merge(B, into: A)
Now of course I could write the function like this:
func merge(from: Number, into: Number){
into.val = from.val
}
But that isn't reusable at all. Is there a way I could write a generic merge class?
UPDATE: Although some of the answers offer good and viable solutions, none of them are "generic" enough (generic here is meant in a non-technical way).So looking at the answers, I got some inspiration, and here is the solution I am now considering: make Number a NSObject subclass and declare all the properties that can be merged as dynamic. For example:
class Number: NSObject {
//Put the required init and initWithCoder: here
dynamic var val: Double?
}
Then declaring a protocol that mergeable classes must respect
protocol Mergeable: class {
var mergeablePropertyKeys:[String] {get}
}
And then declaring a global function that performs a merge:
func merge<U: Mergeable, Mergeable where U.Type == V.Type>(from: U, into:V){
for property in U.mergeablePropertyKeys {
V.setValue(U.valueForKey(property), property)
}
}
And I know that this will not work because the arguments to merge are not necessarily NSObjects.
How can I make sure that the arguments to merge are both NSObjects?
Can avoid having to specify the names of all my mergeable values by simply obtaining a list of my object's dynamic values?
It sounds like what you want is a generic function that uses reflection to merge properties. Reflection is limited in Swift, but it is doable using the MirrorType. I have used this method before to build a generic json parser in swift - you could do something similar but instead of parsing a json dictionary to properties map your two object's properties.
An example of using reflection to do this in swift:
func merge<T>(itemToMerge:T) {
let mirrorSelf = Mirror(reflecting: self)
let mirrorItemToMerge = Mirror(reflecting: itemToMerge)
for mirrorSelfItem in mirrorSelf.children {
// Loop through items in mirrorItemToMerge.
for mirrorImageItem in mirrorItemToMerge.children {
// If you have a parameter who's name is a match, map the value
// OR You could add any custom mapping logic you need for your specific use case
if mirrorSelfItem.label == mirrorImageItem.label {
// To set values, use self.setValue(valueToSet, forKey: propertyName)
self.setValue(mirrorImageItem.value as? AnyObject, forKey: mirrorImageItem.label!)
}
}
}
}
This assumes the object defining the merge method is a subclass of NSObject (so it can take advantage of NSKeyValueCoding). You could also make this a static method that could merge any 2 objects of any NSObject type:
static func merge<T1: NSObject, T2: NSObject>(itemChanging:T1, itemToMerge:T2) {
let mirrorSelf = Mirror(reflecting: itemChanging)
let mirrorItemToMerge = Mirror(reflecting: itemToMerge)
for mirrorSelfItem in mirrorSelf.children {
// Loop through items in mirrorItemToMerge.
for mirrorImageItem in mirrorItemToMerge.children {
// If you have a parameter who's name is a match, map the value
// OR You could add any custom mapping logic you need for your specific use case
if mirrorSelfItem.label == mirrorImageItem.label {
// To set values, use self.setValue(valueToSet, forKey: propertyName)
self.setValue(mirrorImageItem.value as? AnyObject, forKey: mirrorImageItem.label!)
}
}
}
}
Im not sure what you are expecting but there is generic solution:
class Number<T> {
var val: T?
}
protocol Merge {
func merge(from: Self, into: Self)
}
extension Number: Merge {
func merge(from: Number, into: Number) {
into.val = from.val
}
}
Protocol
Lets define a HasValue protocol (available only for classes) like this
protocol HasValue: class {
typealias T
var val: T? { get set }
}
Merge
Now we can define a generic function
func merge<U: HasValue, V:HasValue where U.T == V.T>(from: U, into:V) {
into.val = from.val
}
The constraints in the function signature do guarantee that
Both params do conform to HasValue (therefore are classes)
val types for both params are equals
Scenario 1: params have the same type
class Number: HasValue {
var val: Double?
}
let one = Number()
one.val = 1
let two = Number()
two.val = 2
merge(one, into: two)
print(two.val) // Optional(1.0)
Scenario 2: params have different types but their values have the same type
I did not constrain the 2 params of Merge to having the same type, I am only checking that the val properties of the 2 params must have the same type.
So we could also merge different instances of different classes having val of the same type like
class Phone: HasValue {
var val: Int?
}
class Computer: HasValue {
var val: Int?
}
let iPhone = Phone()
iPhone.val = 10
let iMac = Computer()
iMac.val = 9
merge(iPhone, into: iMac)
print(iMac.val) // Optional(10)
Scenario 3: params have generic types
class Box<S>: HasValue {
var val: S?
}
let boxOfString = Box<String>()
boxOfString.val = "hello world"
let boxOfInt = Box<Int>()
boxOfInt.val = 12
merge(boxOfString, into: boxOfInt) // << compile error
let boxOfWords = Box<String>()
boxOfWords.val = "What a wonderful world"
merge(boxOfString, into: boxOfWords)
print(boxOfWords.val) // Optional("hello world")

How do I do indexOfObject or a proper containsObject

With an array: How do I do indexOfObject or a proper containsObject?
I mean I know I could just bridge the Array to NSArray and do it there ^^
But there must be a 'native' way of doing this
P.S. for the containsObject I guess I could filter the array too but for indexOf?
You can use the built-in find, and thus avoid bridging to Objective-C — but only if your element type is Equatable. (If it isn't Equatable, you can make it so with a comparison function and an extension.)
Example:
func == (lhs:Piece,rhs:Piece) -> Bool {
return lhs.val == rhs.val
}
class Piece:Equatable,Printable {
var val : Int
var description : String { return String(val) }
init (_ v:Int) {
val = v
}
}
Now you can call find(arr,p) where arr is an Array<Piece> and p is a Piece.
Once you have this, you can develop utilities based on it. For example, here's a global function to remove an object from an array without bridging to Objective-C:
func removeObject<T:Equatable>(inout arr:Array<T>, object:T) -> T? {
if let found = find(arr,object) {
return arr.removeAtIndex(found)
}
return nil
}
And test it like this:
var arr = [Piece(1), Piece(2), Piece(3)]
removeObject(&arr,Piece(2))
println(arr)
You can do this for NSObject subclasses too. Example:
func == (v1:UIView, v2:UIView) -> Bool {
return v1.isEqual(v2)
}
extension UIView : Equatable {}
Now you can call find on an Array of UIView. It's sort of a pain in the butt, though, having to do this for every single class where you want to be able to use find on an Array of that class. I have filed an enhancement request with Apple requesting that all NSObject subclasses be considered Equatable and that == should fall back on isEqual: automatically.
EDIT Starting in Seed 3, this is automatic for UIView and other NSObject classes. So find now just works for them.
EDIT 2 Starting in Swift 2.0, indexOf will exist as a method:
let s = ["Manny", "Moe", "Jack"]
let ix = s.indexOf("Moe") // 1
Alternatively, it takes a function that returns Bool:
let ix2 = s.indexOf {$0.hasPrefix("J")} // 2
Again, this works only on collections of Equatable, since obviously you cannot locate a needle in a haystack unless you have a way of identifying a needle when you come to it.
EDIT 3 Swift 2.0 also introduces protocol extensions. This means I can rewrite my global function removeObject as a method!
For example:
extension RangeReplaceableCollectionType where Generator.Element : Equatable {
mutating func removeObject(object:Self.Generator.Element) {
if let found = self.indexOf(object) {
self.removeAtIndex(found)
}
}
}
Since Array adopts RangeReplaceableCollectionType, now I can write code like this:
var arr = [Piece(1), Piece(2), Piece(3)]
arr.removeObject(Piece(2))
Oh, happy day!
Its actually able to be done in Swift. To get the index use find(YourArray, ObjectToFind)
As I was told, this isn't available yet / I have to bridge it to NSArray
I don't like this and it feels dirty so I went and did this in an extension. that way it hides the usage of NSArray and allows apple to provide it later
import Foundation
extension Array {
func contains(object:AnyObject!) -> Bool {
if(self.isEmpty) {
return false
}
let array: NSArray = self.bridgeToObjectiveC();
return array.containsObject(object)
}
func indexOf(object:AnyObject!) -> Int? {
var index = NSNotFound
if(!self.isEmpty) {
let array: NSArray = self.bridgeToObjectiveC();
index = array.indexOfObject(object)
}
if(index == NSNotFound) {
return Optional.None;
}
return index
}
//#pragma mark KVC
func getKeyPath(keyPath: String!) -> AnyObject! {
return self.bridgeToObjectiveC().valueForKeyPath(keyPath);
}
}
https://gist.github.com/Daij-Djan/9d1c4b1233b4017f3b67
Apple provide an example of exactly this in the The Swift Programming Language book. Specifically, see the section on Type Constraints in Action (p621 in the iBook).
func findIndex<T: Equatable>(array: [T], valueToFind: T) -> Int? {
for (index, value) in enumerate(array) {
if value == valueToFind {
return index
}
}
return nil
}
Everything depends upon your type implementing Equatable.
The Swift Programming Language covers that and explains how to implement that protocol:
“The Swift standard library defines a protocol called Equatable, which
requires any conforming type to implement the equal to operator (==)
and the not equal to operator (!=) to compare any two values of that
type. ”
NSHipster has a couple of relevant posts on this subject:
Swift Default Protocol Implementations
Swift Comparison Protocols
I also found this answer very useful in implementing Equatable:
How do I implement Swift's Comparable protocol?
Alhough it mentions Comparable, Equatable is a subset and the explanation is good.
Excerpts above from: Apple Inc. “The Swift Programming Language.” iBooks. https://itun.es/gb/jEUH0.l
One other option is to use filter:
haystack.filter({$0 == needle}).count > 0
This checks to see if array haystack contains object needle.
You can't. That's why NSArray is still there. However, the Apple documentation reads as follows about String and NSString:
Swift’s String type is bridged seamlessly to Foundation’s NSString
class. If you are working with the Foundation framework in Cocoa or
Cocoa Touch, the entire NSString API is available to call on any
String value you create, in addition to the String features described
in this chapter. You can also use a String value with any API that
requires an NSString instance.
Following that approach, the NSArray API should be available on Array, but it isn't because the native Swift Array is a primitive (most likely a struct or similar), so you have to "convert" it to an NSArray.
It appears that not all of the toll-free bridging from NS/CF space is in place. However, if you declare your array as an NSArray, it works fine:
let fruits: NSArray = [ "apple", "orange", "tomato (really?)" ]
let index = fruits.indexOfObject("orange")
println("Index of Orange: \(index)")
If your array elements are objects and you want to find an identical object in that array, you can use this function:
func findObject<C: CollectionType where C.Generator.Element: AnyObject>(domain: C, value: C.Generator.Element) -> Int? {
for (index, element) in enumerate(domain) {
if element === value {
return index
}
}
return nil
}

Resources