Insertion-Order Dictionary (Like Java's Linkedhashmap) in Swift

Insertion-Order Dictionary (like Java's LinkedHashMap) in Swift?

Didn't know of one and it was an interesting problem to solve (already put it in my standard library of stuff) Mostly it's just a matter of maintaining a dictionary and an array of the keys side-by-side. But standard operations like for (key, value) in od and for key in od.keys will iterate in insertion order rather than a semi random fashion.

// OrderedDictionary behaves like a Dictionary except that it maintains
// the insertion order of the keys, so iteration order matches insertion
// order.
struct OrderedDictionary<KeyType:Hashable, ValueType> {
private var _dictionary:Dictionary<KeyType, ValueType>
private var _keys:Array<KeyType>

init() {
_dictionary = [:]
_keys = []
}

init(minimumCapacity:Int) {
_dictionary = Dictionary<KeyType, ValueType>(minimumCapacity:minimumCapacity)
_keys = Array<KeyType>()
}

init(_ dictionary:Dictionary<KeyType, ValueType>) {
_dictionary = dictionary
_keys = map(dictionary.keys) { $0 }
}

subscript(key:KeyType) -> ValueType? {
get {
return _dictionary[key]
}
set {
if newValue == nil {
self.removeValueForKey(key)
}
else {
self.updateValue(newValue!, forKey: key)
}
}
}

mutating func updateValue(value:ValueType, forKey key:KeyType) -> ValueType? {
let oldValue = _dictionary.updateValue(value, forKey: key)
if oldValue == nil {
_keys.append(key)
}
return oldValue
}

mutating func removeValueForKey(key:KeyType) {
_keys = _keys.filter { $0 != key }
_dictionary.removeValueForKey(key)
}

mutating func removeAll(keepCapacity:Int) {
_keys = []
_dictionary = Dictionary<KeyType,ValueType>(minimumCapacity: keepCapacity)
}

var count: Int { get { return _dictionary.count } }

// keys isn't lazy evaluated because it's just an array anyway
var keys:[KeyType] { get { return _keys } }

// values is lazy evaluated because of the dictionary lookup and creating a new array
var values:GeneratorOf<ValueType> {
get {
var index = 0
return GeneratorOf<ValueType> {
if index >= self._keys.count {
return nil
}
else {
let key = self._keys[index]
index++
return self._dictionary[key]
}
}
}
}
}

extension OrderedDictionary : SequenceType {
func generate() -> GeneratorOf<(KeyType, ValueType)> {
var index = 0
return GeneratorOf<(KeyType, ValueType)> {
if index >= self._keys.count {
return nil
}
else {
let key = self._keys[index]
index++
return (key, self._dictionary[key]!)
}
}
}
}

func ==<Key: Equatable, Value: Equatable>(lhs: OrderedDictionary<Key, Value>, rhs: OrderedDictionary<Key, Value>) -> Bool {
return lhs._keys == rhs._keys && lhs._dictionary == rhs._dictionary
}

func !=<Key: Equatable, Value: Equatable>(lhs: OrderedDictionary<Key, Value>, rhs: OrderedDictionary<Key, Value>) -> Bool {
return lhs._keys != rhs._keys || lhs._dictionary != rhs._dictionary
}

Ordered map in Swift

You can order them by having keys with type Int.

var myDictionary: [Int: [String: String]]?

or

var myDictionary: [Int: (String, String)]?

I recommend the first one since it is a more common format (JSON for example).

Create a Swift Dictionary subclass?

Swift dictionaries are structs, not classes, so they cannot be subclassed. Ideally, the methods you're working with would be declared to take an appropriately constrained generic CollectionType (or ExtensibleCollectionType, or SequenceType, depending on the situation), rather than specifically a Dictionary.

If that doesn't work for you for whatever reason, you could subclass NSDictionary instead.

(edit) and as Antonio points out, you can do extension Dictionary { … } to add things to the Dictionary struct, which can replace subclassing in some cases.

How to effectively process HashMap in order described by list?

Something like this should be equivalent, using Stream.reduce:

eventsOrder.stream()          // "event1", "event2", "event3", "event4", "event5"
.map(eventsData::get) // "data1", "data2", "data3", null, null
.filter(Objects::nonNull) // "data1", "data2", "data3"
.reduce(YourClass::merge) // merge(merge("data1", "data2"), "data3")
.get() // gets the result from the Optional

The Optional.get method throws NoSuchElementException if there are no events with data; your original code's namesToProcess.get(0) will throw IndexOutOfBoundsException in that case. That should be the only difference in behaviour, assuming your eventsData map doesn't have any null values.

How to sort hashtable/enumeration based on original input

You should be using LinkedHashMap<>, which combines a hash map for quick access but also keeps the elements ordered by their insertion order. For example

Map<String,String> dict = new LinkedHashMap<>();
dict.put("Emma", "Watson");
dict.put("Tom", "Hanks");
dict.put("Keanu", "Reeves");

for (String s : dict.keySet())
System.out.println(s);

This will output the keys in the order in which they were inserted into the map.

BTW, Dictionary, Hashtable and related classes are very old and are superseded by Map and its implementations.

SwiftyJSON order of elements inside JSON object

Dictionaries are, by design, not stable on insertion order (insertion order doesn't matter when iterating keys) EF is arguably broken in requiring order of JS objects, which also isn't specified. That said, you might be able to solve the problem using something like the answers to this question: Insertion-Order Dictionary (like Java's LinkedHashMap) in Swift?. Barring that, you'll need to customize your serialization operation (whatever turns your Dictionary into JSON) to special case this.

What is the equivalent of a Java HashMap String,Integer in Swift

I believe you can use a dictionary. Here are two ways to do the dictionary part.

var someProtocol = [String : Int]()
someProtocol["one"] = 1
someProtocol["two"] = 2

or try this which uses type inference

var someProtocol = [
"one" : 1,
"two" : 2
]

as for the for loop

var index: Int
for (e, value) in someProtocol {
index = value
}

How do I get the key at a specific index from a Dictionary in Swift?

That's because keys returns LazyMapCollection<[Key : Value], Key>, which can't be subscripted with an Int. One way to handle this is to advance the dictionary's startIndex by the integer that you wanted to subscript by, for example:

let intIndex = 1 // where intIndex < myDictionary.count
let index = myDictionary.index(myDictionary.startIndex, offsetBy: intIndex)
myDictionary.keys[index]

Another possible solution would be to initialize an array with keys as input, then you can use integer subscripts on the result:

let firstKey = Array(myDictionary.keys)[0] // or .first

Remember, dictionaries are inherently unordered, so don't expect the key at a given index to always be the same.



Related Topics



Leave a reply



Submit