2つのDictionaryを結合するextensionと演算子を書く

Monday, April 18, 2016

新しい環境に移ってから早2週間が経ちました。
今日は実際開発中のライブラリでも使った、2つのDictionaryを結合するExtensionと演算子を紹介します。

2つのDictionaryを結合する関数を定義する

まずは、結合する関数unionを定義してみます。

public extension Dictionary {
    public func union(other: Dictionary) -> Dictionary {
        var tmp = self
        other.forEach { tmp[$0.0] = $0.1 }
        return tmp
    }
}

こう定義することで、

let a = ["key1": 1]
let b = ["key2": 2]
let c = a.union(b)

と書くことができます。

もし、 mutating に扱いたい場合は、

public mutating func united(other: Dictionary) {
    other.forEach { self[$0.0] = $0.1 }
}
//----

var a = ["key1": 1]
let b = ["key2": 2]
a.united(b) // ["key1": 1, "key2": 2]

と定義してあげるといい感じになります。

演算子 + を定義する

より直感的に使いやすくするために、2つのDictionaryを結合する演算子 + を定義してみます。

public func +<K, V>(lhs: Dictionary<K, V>, rhs: Dictionary<K, V>) -> Dictionary<K, V> {
    return lhs.union(rhs)
}

public func +=<K, V>(inout lhs: Dictionary<K, V>, rhs: Dictionary<K, V>) {
    lhs = lhs + rhs
}

これで、

let a = ["key1": 1]
let b = ["key2": 2]
let c = a + b // ["key1": 1, "key2": 2]

と書くことができます。

Optionalの場合にも対応してみる

ただ、上記のままでは、どちらかがOptionalなDictionaryだった場合には、そのまま結合することができません。

let a: [String: Int] = ["key1": 1]
let b: [String: Int]? = ["key2": 2]
let c = a + b // error!

なので、Optionalでも問題なく扱えるように 演算子 + をオーバーロードします。
また、今回の結合では、以下のように扱うことにします。

A \ B Some(let b) None (nil)
Some(let a) a + b a
None (nil) b nil


この表を基に、演算子を以下のようにオーバーロードして定義します。

public func +<K, V>(lhs: Dictionary<K, V>?, rhs: Dictionary<K, V>?) -> Dictionary<K, V>? {
    switch (lhs, rhs) {
    case (.Some(let l), .Some(let r)):
        return l + r
    case (.Some(let l), .None):
        return l
    case (.None, .Some(let r)):
        return r
    default:
        return nil
    }
}
public func +=<K, V>(inout lhs: Dictionary<K, V>?, rhs: Dictionary<K, V>?) {
    lhs = lhs + rhs
}

※もし片方がnilのDictionaryだったら、結果としてnilにしたい!という場合条件を変えてあげれば実装できるかと思います。

パターンマッチがいい感じ

左辺、右辺のDictionaryがnilかどうかを見るのに、 if let を使うとあまりスッキリと書けないので、
switch文で、パターンマッチを用いてみました。

Optionalな変数に対して、 .Some(let variable) (値がある)と、 .None (nilである)でパターンマッチをして、 .Some()内で定義した変数名で、アンラップした値を扱うことができます。

otherSwiftTips

Objective-CからSwift化したクラスをstoryboard/xibで使っている時に、コマンドラインで一括でModule設定を付ける

今更ながら、try! Swiftで紹介されていたValueTransformerが正しく動作するように直してみた