Remove duplicated objects from array in Swift 5 - ios

I have an array of questions and each question have a unique lineID.
The problem is when I sync the questions from the server sometimes (1 time at 1000 syncs ) the questions are duplicated. Instead of 28 questions I have 56.
I want somehow to filter the array of questions and if there are 2 questions with the same lineID (duplicated) then to append only 1 into my array of objects.
Can anyone help me with this issue ?
// Get the questions from CoreData.
func loadQuestionsUsing(templateId: Int) {
do {
guard let questions = try Question.getQuestions(templateId: templateId, context: context) else { return }
checklistSections = []
questions.forEach { question in
// If the question lineID is duplicated, then select only one of that 2 duplications.
// code here
print(“Question Line ID: \(question.lineId)“)
// If the question have the same templateID as the Checklist Template which user selected from the menu then create an object with that question properties.
if question.templateId == templateId {
let showVehicle = question.vehicle == 1
let showTrailer = question.trailer == 1
let highlight = question.highlight == 1
let pg9 = question.pg9 == 1
let question = ChecklistQuestion( rowID: Int(question.id),
templateID: Int(question.templateId),
lineID: Int(question.lineId),
poolID: Int(question.poolId),
descript: question.descript ?? String(),
showVehicle: showVehicle,
showTrailer: showTrailer,
header: question.header ?? String(),
highlight: highlight,
pg9: pg9,
imagesPath: [])
let header = (question.header.isEmpty) ? Constants.questionsNoHeader : question.header
// check if question.header (Section) exist in the ChecklistItemSection
if let existingSection = checklistSections.firstIndex(where: { $0.name == header }) {
// If section exist then append the ChecklistItem (question) to that existing section.
checklistSections[existingSection].questions.append(question)
} else {
// If section don’t exist then create the section (question.header) and also append the question to that section.
checklistSections.append(ChecklistSection(name: header, questions: [question]))
}
}
}
} catch {
print(“Error while fetching the Questions from CoreData: \(error.localizedDescription)“)
}
print(“CHECKLIST CONTAINS: \(checklistSections.count) Sections.“)
}
Struct for ChecklistSection:
class ChecklistSection {
var name: String // Name of the section
var questions: [ChecklistQuestion] // List with all questions from a Section
init(name: String, questions: [ChecklistQuestion]) {
self.name = name
self.questions = questions
}
}
My stupid solution:
func findDuplicatedQuestions(checklistSections: [ChecklistSection]) -> [ChecklistSection] {
var duplicates: [ChecklistQuestion] = []
var prevQuestionLineID: Int = 0
var addedQuestionLineID: Int = 0
checklistSections.forEach { section in
section.questions.forEach { question in
if (prevQuestionLineID == question.lineId && addedQuestionLineID != question.lineId) {
duplicates.append(question)
addedQuestionLineID = question.lineId
}
prevQuestionLineID = question.lineId
}
}
return checklistSections
}
Thanks for reading this !

Conform your ChecklistQuestion to Equatable, implement == so it knows how to compare two instances.
class ChecklistQuestion: Equatable {
static func ==(lhs: ChecklistQuestion, rhs: ChecklistQuestion) -> Bool {
return lhs.lineID == rhs.lineID
}
}
Then using this extension to remove duplicate items:
extension Array where Element: Equatable {
/// Remove Duplicates
var unique: [Element] {
// Thanks to https://github.com/sairamkotha for improving the method
return self.reduce([]){ $0.contains($1) ? $0 : $0 + [$1] }
}
}
Usage:
var questions: [ChecklistQuestion] = ...
questions.unique
//or
var uniqueQuestions = questions.unique
Extension credits to https://github.com/dillidon/alerts-and-pickers

In case you need preserve order and still use arrays (and run in O(n)). Use this:
func getUnique(questions: [ChecklistQuestion]) -> [ChecklistQuestion] {
var set = Set<ChecklistQuestion>()
var res = [ChecklistQuestion]()
for question in questions {
if !set.contains(question) {
res.append(question)
set.insert(question)
}
}
return res
}
And you need to implement Hashable protocol for your model

Related

iterate through two custom arrays and set values if variables are equal Swift

I have an easy question that is also hard at the same time. I have two separate structs (this can also work for classes):
struct FBTweet {
var tweetId: Int? //set
var tweetText: String? //set
}
and
struct Status {
var statusId: Int? //set
var statusText: String? //no value
}
I have an array of both structs var fbTweetArray: [FBTweet] = [] and var statusArray: [Status] = []
I have set every variable in to a certain value in each index in fbTweetArray but I only set the .statusId variable in each index for statusArray. For every statusArray.statusId value in statusArray, there is only one fbTweetArray.tweetId that has the same exact Int value. I am trying to make is so that if these two variables are the same then I should set set
statusArray.statusText to whatever fbTweetarray.tweetText is. So for example only fbTweetArray[1].tweetid = 2346 and statusArray[4].statusId = 2346 have 2346 as their value. There for if fbTweetArray[1].tweetText = "hello friend" then statusArray[4].statusText needs to be set to "hello friend".
So far I have
func testWhat () {
var fbTweetArray: [FBTweet] = []
var statusArray: [Status] = []
for fbTweet in fbTweetArray {
for var status in statusArray {
if (status.statusId == fbTweet.tweetId ) {
status.statusText = fbTweet.tweetText
}
}
}
}
how do I set the for var status in the for loop back into the statusArray since it is now a var and is different than one of the indexes in var statusArray: [Status] = []
Basically, you need only one for/forEach loop to achieve what you want:
var fbTweetArray: [FBTweet] = [
FBTweet(tweetId: 1, tweetText: "1"),
FBTweet(tweetId: 2, tweetText: "2"),
FBTweet(tweetId: 3, tweetText: "3")
]
var statusArray: [Status] = [
Status(statusId: 2, statusText: nil),
Status(statusId: 1, statusText: nil),
Status(statusId: 3, statusText: nil)
]
fbTweetArray.forEach { tweet in
if let index = statusArray.index(where: { $0.statusId == tweet.tweetId }) {
statusArray[index].statusText = tweet.tweetText
}
}
print(statusArray.map { $0.statusText }) // [Optional("2"), Optional("1"), Optional("3")]
Note, that your ids in both structures can be nil. To handle this situation (if both id is nil - objects are not equal) you can write custom == func:
struct Status {
var statusId: Int? //set
var statusText: String? //no value
static func == (lhs: Status, rhs: FBTweet) -> Bool {
guard let lhsId = lhs.statusId, let rhsId = rhs.tweetId else { return false }
return lhsId == rhsId
}
}
...
// rewrite .index(where: ) in if condition
if let index = statusArray.index(where: { $0 == tweet }) { ... }
Also, there is some pro-tip. If you adopt your structs to Hashable protocol, you will be able to place FBTweets and Statuses into Set structure. The benefits of that:
If you instead store those objects in a set, you can theoretically
find any one of them in constant time (O(1)) — that is, a lookup on a
set with 10 elements takes the same amount of time as a lookup on a
set with 10,000.
You can find more in-depth info about it in a new great article by NSHipster.
Your question is interesting only if both the arrays are not ordered.
To find the element from fbTweet array, you can sort it and employ binary search.
Then enumerate status array and find the fbTweet object with the same identifier and modify the status object. It needs to be saved again in the array as structs get copied on write.
extension Array where Element == FBTweet {
func binarySearchFBTweetWith(_ id:Int) -> FBTweet? {
var range = 0..<self.count
while range.startIndex < range.endIndex {
let midIndex = range.startIndex + (range.endIndex - range.startIndex) / 2
guard let tweetId = self[midIndex].tweetId else {
continue
}
if tweetId == id {
return self[midIndex]
} else if tweetId < id {
range = midIndex+1..<range.endIndex
} else {
range = range.startIndex..<midIndex
}
}
return nil
}
}
fbTweetArray.sort{($0.tweetId ?? 0) < ($1.tweetId ?? 0)}
for (index, status) in statusArray.enumerated() {
guard let statusId = status.statusId else {continue}
guard let fbTweet = fbTweetArray.binarySearchFBTweetWith(statusId) else {continue}
var status = status
status.statusText = fbTweet.tweetText
statusArray[index] = status
}
An alternative would be use dictionaries instead of arrays, for better performance and easier implementation (in this case). You can easily get the array of keys and values later If you need.
In this case, the Id would be the Key of the dictionary, and the text the value.

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.

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).

iOS Swift: Find index for CKRecord in array

I'm trying to use the find function to get the index of a CKRecord in an array. However I'm running into a problem that I don't understand.
I have two arrays: notes and allNotes. notes is a subset of allNotes. I have an item from notes and I'm trying to find its index within allNotes. As you can see from the output it exists in both arrays, but its only being found in the notes array.
func removeNoteAtIndexPath(indexPath: NSIndexPath) {
let note = tableViewController.notes[indexPath.row]
if let index = find(allNotes, note) {
println("Found in allNotes")
} else {
println("Not found in allNotes")
}
if let index = find(tableViewController.notes, note) {
println("Found in notes")
} else {
println("Not found in notes")
}
println(tableViewController.notes[0])
println(allNotes[0])
}
Console output
Not found in allNotes
Found in notes
<CKRecord: 0x7fb49ad37120; recordType=Note, recordID=E6BD60A1-AB15-4952-84A3-81BDB4DFC961:(_defaultZone:__defaultOwner__), recordChangeTag=i8nh54t3, values={
Text = "My note";
}>
<CKRecord: 0x7fb49ac31f60; recordType=Note, recordID=E6BD60A1-AB15-4952-84A3-81BDB4DFC961:(_defaultZone:__defaultOwner__), recordChangeTag=i8nh54t3, values={
Text = "My note";
}>
You need to adapt Equatable protocol to CKRecord like below.
extension CKRecord: Equatable {}
public func
==( lhs: CKRecord, rhs: CKRecord ) -> Bool {
return lhs.recordID.recordName == rhs.recordID.recordName
}
I like Satachito's answer better, but this was my initial solution:
func findCKRecord(records: [CKRecord], record: CKRecord) -> Int? {
var index: Int?
let recordID = record.recordID
for (i, record) in enumerate(records) {
let currentRecordID = record.recordID
if recordID == currentRecordID {
index = i
}
}
return index
}

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