Swift Ordered Set

Swift Ordered Set

Swift does not have a native ordered set type. If you use Foundation, you can use NSOrderedSet in Swift. If not, you have the opportunity to write your own ordered set data structure.

Update: Swift Package Manager includes an OrderedSet implementation that may be useful. It wraps both an array and a set and manages access to get ordered set behavior.

Update #2: Apple's Swift Collections repository contains an ordered set implementation.

Reorder NSOrdered Set based on the order specified in a different NSOrderedSet

You can use filter with a predicate:

let osA: NSOrderedSet = ["A", "B", "C", "D", "E", "F"]
let osB: NSOrderedSet = ["B", "G", "H", "A", "C", "D", "E", "F", "J", "K"]

let osC = osB.filtered(using: .init(block: { (element, _) in
osA.contains(element as Any)
}))

You can also create a NSMutableOrderedSet of your ordered set B and intersect with A:

var osC = osB.mutableCopy() as! NSMutableOrderedSet
osC.intersect(osA)
print(Array(osC)) // ["B", "A", "C", "D", "E", "F"]

Another option is to implement your own native Swift OrderedSet as I have posted in this answer. After adding the OrderedSet to your project you could simply get ordered set A intersection ordered set B:

let osA: OrderedSet = ["A", "B", "C", "D", "E", "F"]
let osB: OrderedSet = ["B", "G", "H", "A", "C", "D", "E", "F", "J", "K"]

let osC = osB.intersection(osA) // ["B", "A", "C", "D", "E", "F"]



edit/update:

Not sure why would you need a NSOrderedSet of dictionaries. IMO you should structure your data. Anyway if you really want to do it this way you can use reduce(into:):

let osA: NSOrderedSet = [["key1": "test", "key2": "A"], ["key1": "test2", "key2": "B"], ["key1": "test3", "key2":"C"], ["key1": "test4", "key2": "E"]]
let osB: NSOrderedSet = ["B","G","H", "A", "E", "D", "C", "F", "J", "K"]

let osC: NSMutableOrderedSet = osB.reduce(into: .init(), { (os, any) in
guard let object = osA.first(where: { element in
(element as? [String: String] ?? [:])?.values.contains(any as? String ?? "") == true
}) else { return }
os.add(object)
})

Order of items in set has been changed, Set, Swift

Because Set is an unordered data structure.

Set reference:

A set stores distinct values of the same type in a collection with no defined ordering. You can use a set instead of an array when the order of items is not important, or when you need to ensure that an item only appears once.

You should take a look at NSOrderedSet class reference, for more APIs:

NSOrderedSet and its subclass, NSMutableOrderedSet, declare the programmatic interfaces to an ordered collection of objects.

Sorted Set in iOS

You have to convert the Set to an Array.

The reason for this is the following definition:

"Sets are different in the sense that order does not matter and these
will be used in cases where order does not matter."

Whereas a set:

"... stores distinct values of the same type in a collection with no
defined ordering
."

See for further information:
https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/CollectionTypes.html


In this case you will have a list of distinct values (and I consider your decision to use a NSSet as valid argument) you will have to transform your set to an array, you should not run into trouble, as your set already seems to take care of that your objects are of the same type (e.g. "Fruit").

So in this case, we will have

  1. Define Sort Criteria
  2. Sort the Array

I have attached a sample for both Objective-C and Swift, in case you need the one way or the other:

Objective-C Code

NSMutableSet<Fruit> *uniqueFruits = [NSMutableSet new];
[uniqueFruits addObject:[[Fruit alloc] initWithName:@"Apple"]];
[uniqueFruits addObject:[[Fruit alloc] initWithName:@"Banana"]];
[uniqueFruits addObject:[[Fruit alloc] initWithName:@"Orange"]];

// 1 Define Sort Criteria
NSSortDescriptor *descriptor = [[NSSortDescriptor alloc] initWithKey:@"size" ascending:YES]; // Key is the NSString of a certain selector. If it is an attribute of another class reference. Simply use "reference.property".

// 2 Sort the Array
NSArray<Fruit> *sortedArray = [self.uniqueFruits sortedArrayUsingDescriptors:@[descriptor]];

Swift 3 Code

let uniqueFruits = Set<Fruit>(Fruit("Apple"), Fruit("Banana"), Fruit("Orange"))

// 1 & 2 Define Sort Criteria and sort the array, using a trailing closure that sorts on a field/particular property you specify
// Be aware: result is an array
let sortedArray = uniqueFruits.sort({ $0.size < $1.size })

How to write to an Element in a Set?

You are getting the error because columns might be a set of struct. So columns.first will give you an immutable value. If you were to use a class, you will get a mutable result from columns.first and your code will work as expected.
Otherwise, you will have to do as explained by @Sweeper in his answer.

Swift - Set vs Array

There are several third-party libraries implementing an ordered set in Swift, so you could check them out.

Also, you could write your own implementation of an ordered set (you can base it on an existing one) if it is not an overkill for your task. The way you choose really depends on your app.

And in the end, you could use one of two ways that you proposed: using a built-in array or a set. In order to choose between them, take a look at your app: what action will be performed more often? Getting an access to elements in order (use array then) or addition/deletion of existing elements (probably, the set is the way to go).

This part was edited based on comments below

If you go for an array, note, that a built-in contains for arrays will not know that an array is sorted, so it will probably be O(N), not O(log(N)). So you should either write a custom replacement for the contains method, or (this is, once again probably a better way), write a custom collection class that implements contains the right way (however, since contains is a protocol extension method of SequenceType, my knowledge of Swift, I'm afraid, is not good enough yet to tell you how to do it properly, maybe someone else will).

UPDATE (based on your comment to your question):

I believe, in your particular case (a chat app) array is superior. You only have to sort old messages once, and you will not probably try to add very old messages once again, you only have to make sure you don't add new messages twice (it is implementation-dependent though, so you know better, I'm just assuming). So you only have to check that the last messages in your old array do not overlap with first messages in the array that you add. Sort of :)

Manually set list order in swift? (iOS)

This is achievable with the help of table delegates.Just implement below methods.

func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
return .none
}
func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
let itemToReorder = yourList[sourceIndexPath.row]
yourList.remove(at: sourceIndexPath.row)
yourList.insert(itemToReorder, at: destinationIndexPath.row)
}

Don't forget to enable disable editing mode when needed or not.
Use this for enabling and disabling

tableView.setEditing(true, animated: true)


Related Topics



Leave a reply



Submit