Skip to content

Instantly share code, notes, and snippets.

@khanhldt
Last active March 23, 2020 10:53
Show Gist options
  • Save khanhldt/bbaa073485abbcf0dad1673ef0ad6bfe to your computer and use it in GitHub Desktop.
Save khanhldt/bbaa073485abbcf0dad1673ef0ad6bfe to your computer and use it in GitHub Desktop.
OrderedDictionary behaves like a Dictionary except that it maintains the insertion order of the keys, so iteration order matches insertion order.
//
// Created by Khanh Le Do on 16/4/16.
// Copyright © 2016 kloc. All rights reserved.
//
import Foundation
/// Attribution to:
/// http://stackoverflow.com/questions/28633703/insertion-order-dictionary-like-javas-linkedhashmap-in-swift
/// With following updates:
/// - Fix syntax from Swift 1.0 to 2.0
/// - Add method to sort the keys
/// - Make the class and methods public to be used in a library providing helper to other components.
// OrderedDictionary behaves like a Dictionary except that it maintains the insertion order of the keys,
// so iteration order matches insertion order.
public struct OrderedDictionary<KeyType:Hashable, ValueType> {
private var _dictionary:Dictionary<KeyType, ValueType>
private var _keys:Array<KeyType>
public init() {
_dictionary = [:]
_keys = []
}
public init(minimumCapacity:Int) {
_dictionary = Dictionary<KeyType, ValueType>(minimumCapacity:minimumCapacity)
_keys = Array<KeyType>()
}
public init(_ dictionary:Dictionary<KeyType, ValueType>) {
_dictionary = dictionary
_keys = [KeyType](dictionary.keys)
}
public subscript(key:KeyType) -> ValueType? {
get {
return _dictionary[key]
}
set {
if newValue == nil {
self.removeValueForKey(key)
}
else {
self.updateValue(newValue!, forKey: key)
}
}
}
public mutating func updateValue(value:ValueType, forKey key:KeyType) -> ValueType? {
let oldValue = _dictionary.updateValue(value, forKey: key)
if oldValue == nil {
_keys.append(key)
}
return oldValue
}
public mutating func removeValueForKey(key:KeyType) {
_keys = _keys.filter { $0 != key }
_dictionary.removeValueForKey(key)
}
public mutating func removeAll(keepCapacity:Int) {
_keys = []
_dictionary = Dictionary<KeyType,ValueType>(minimumCapacity: keepCapacity)
}
public mutating func sortKeys(@noescape isOrderedBefore: (KeyType, KeyType) -> Bool) {
_keys.sortInPlace(isOrderedBefore)
}
public var count: Int { get { return _dictionary.count } }
// keys isn't lazy evaluated because it's just an array anyway
public var keys:[KeyType] { get { return _keys } }
// values is lazy evaluated because of the dictionary lookup and creating a new array
public var values:AnyGenerator<ValueType> {
get {
var index = 0
return anyGenerator({ () -> ValueType? in
if index >= self._keys.count {
return nil
}
else {
let key = self._keys[index]
index++
return self._dictionary[key]
}
})
}
}
}
extension OrderedDictionary : SequenceType {
public func generate() -> AnyGenerator<(KeyType, ValueType)> {
var index = 0
return anyGenerator({ () -> (KeyType, ValueType)? in
if index >= self._keys.count {
return nil
}
else {
let key = self._keys[index]
index++
return (key, self._dictionary[key]!)
}
})
}
}
public func ==<Key: Equatable, Value: Equatable>(lhs: OrderedDictionary<Key, Value>, rhs: OrderedDictionary<Key, Value>) -> Bool {
return lhs._keys == rhs._keys && lhs._dictionary == rhs._dictionary
}
public func !=<Key: Equatable, Value: Equatable>(lhs: OrderedDictionary<Key, Value>, rhs: OrderedDictionary<Key, Value>) -> Bool {
return lhs._keys != rhs._keys || lhs._dictionary != rhs._dictionary
}
@yigit-darcin
Copy link

nice

@maikbartsch
Copy link

Interested in an updated Version for Swift 3 + an initializer to init it with an OrderedDictionary?
Leave a comment and let me know if this works out.

import Foundation


/// Attribution to:
/// http://stackoverflow.com/questions/28633703/insertion-order-dictionary-like-javas-linkedhashmap-in-swift
/// With following updates:
/// - Fix syntax from Swift 1.0 to 2.0 to 3.0
/// - Add method to sort the keys
/// - Make the class and methods public to be used in a library providing helper to other components.

// OrderedDictionary behaves like a Dictionary except that it maintains the insertion order of the keys,
// so iteration order matches insertion order.
public struct OrderedDictionary<KeyType:Hashable, ValueType> {
    fileprivate var _dictionary:Dictionary<KeyType, ValueType>
    fileprivate var _keys:Array<KeyType>
    
    public init() {
        _dictionary = [:]
        _keys = []
    }
    
    public init(minimumCapacity:Int) {
        _dictionary = Dictionary<KeyType, ValueType>(minimumCapacity:minimumCapacity)
        _keys = Array<KeyType>()
    }
    
    public init(_ dictionary:Dictionary<KeyType, ValueType>) {
        _dictionary = dictionary
        _keys = [KeyType](dictionary.keys)
    }
    
    public init(_ orderedDictionary: OrderedDictionary<KeyType, ValueType>){
        _dictionary = orderedDictionary._dictionary
        _keys = orderedDictionary._keys
    }
    
    public subscript(key:KeyType) -> ValueType? {
        get {
            return _dictionary[key]
        }
        set {
            if newValue == nil {
                self.removeValueForKey(key: key)
            }
            else {
                self.updateValue(value: newValue!, forKey: key)
            }
        }
    }
    
    public mutating func updateValue(value:ValueType, forKey key:KeyType) -> ValueType? {
        let oldValue = _dictionary.updateValue(value, forKey: key)
        if oldValue == nil {
            _keys.append(key)
        }
        return oldValue
    }
    
    public mutating func removeValueForKey(key:KeyType) {
        _keys = _keys.filter { $0 != key }
        _dictionary.removeValue(forKey: key)
    }
    
    public mutating func removeAll(keepCapacity:Int) {
        _keys = []
        _dictionary = Dictionary<KeyType,ValueType>(minimumCapacity: keepCapacity)
    }
    
    public mutating func sortKeys( isOrderedBefore: (KeyType, KeyType) -> Bool) {
        _keys.sort(by: isOrderedBefore)
    }
    
    public var count: Int { get { return _dictionary.count } }
    
    // keys isn't lazy evaluated because it's just an array anyway
    public var keys:[KeyType] { get { return _keys } }
    
    // values is lazy evaluated because of the dictionary lookup and creating a new array
    public var values:AnyIterator<ValueType> {
        get {
            var index = 0
            return AnyIterator({ () -> ValueType? in
                if index >= self._keys.count {
                    return nil
                }
                else {
                    let key = self._keys[index]
                    index += 1
                    return self._dictionary[key]
                }
            })
        }
    }
}

extension OrderedDictionary : Sequence {
    public func makeIterator() -> AnyIterator<(KeyType, ValueType)> {
        var index = 0
        return AnyIterator({ () -> (KeyType, ValueType)? in
            if index >= self._keys.count {
                return nil
            }
            else {
                let key = self._keys[index]
                index += 1
                return (key, self._dictionary[key]!)
            }
        })
    }
}

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

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

@Kuurse
Copy link

Kuurse commented Oct 17, 2017

@maikbartsch

Interested in an updated Version for Swift 3 + an initializer to init it with an OrderedDictionary?
Leave a comment and let me know if this works out.

Thanks, this works like a charm.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment