Xcode 7 beta 4からSwiftのenum
のcase
の型に自分と同じ型を利用して、再帰構造をつくることができるようになりました。
これを利用して、単連結リストを実装してみました。
indirect case
Xcode 7 beta 4からSwiftに新キーワードindirect
が追加されています。
これを利用するとAssociate Valueに自分と同じ型を指定することができます。
それを利用することで、次のように単連結リストをつくることができます。
enum List<T> { case Nil indirect case Cons(head: T, tail: List<T>) }
例えば、次のようにリストをつくることができます。
let list = List.Cons(head: 7, tail: .Cons(head: 5, tail: .Cons(head: 8, tail: .Nil))) // => Cons(7, List<Swift.Int>.Cons(5, List<Swift.Int>.Cons(8, List<Swift.Int>.Nil)))
このままではリストを作るのがかなり面倒だったり、表示が見づらいので、そこらを拡張していきます。
CustomStringConvertible
まず、CustomStringConvertible
プロトコルを実装して、結果の表示がわかりやすくなるようにカスタマイズします。
extension List: CustomStringConvertible { var description: String { switch self { case .Nil: return "[]" case .Cons(let v): return "\(v.head):\(v.tail)" } } }
これで次のように表示されるようになります。
print(list) // => "7:5:8:[]"
ArrayLiteralConvertible
続いて、配列から簡単にリストを作成できるようにします。
extension List { init(_ elements: [T]) { self = elements .reverse() .reduce(.Nil) { (a, e) in return List.Cons(head: e, tail: a) } } }
外部パラメタ名を_
にしたので、パラメタ名を省略して簡単に書けるようになりました。
let list2 = List([9, 8, 4, 5, 6]) // => 9:8:4:5:6:[]
このままでもかなり楽になりました。配列リテラルから自動で変換させたいので、さらにArrayLiteralConvertible
プロトコルを実装します。
extension List: ArrayLiteralConvertible { init(arrayLiteral elements: T...) { self.init(elements) } }
これで配列リテラルからの自動変換が効くようになります。
let list3: List = [1, 5, 4, 2] // => 1:5:4:2:[]
SequenceType, map, reduce
最後にこのList
型でmap
やreduce
を使えるようにしましょう。
Swift 2.0からはmap
やreduce
はプロトコル拡張として、SequenceType
プロトコルに実装済みです。
よって、List
をSequenceType
プロトコルに適合させるだけでこれらのメソッドが利用できるようになります。
SequenceType
プロトコルに適合させるには、先頭から要素をひとつづつ取るようなジェネレータを得るメソッドgenerate
を実装します。
extension List: SequenceType { typealias Generator = ListGenerator<T> func generate() -> Generator { return ListGenerator(list: self) } } struct ListGenerator<T>: GeneratorType { typealias Element = T var list: List<T> mutating func next() -> Element? { switch list { case .Nil: return .None case .Cons(let v): self.list = v.tail return v.head } } }
これで、map
やreduce
が使えるようになります。ただし、map
で返されるものは配列になってしまいますので、必要ならList
でラップする必要があります。
let mapped = List(a.map { $0 * 3 }) // => 3:15:12:6:[] let len = list.reduce(0) { (a, e) in return a + 1 } // => 4
おわりに
再帰enum
の個人的な問題点は、いろいろな型を混在させられる点です。
let mix: List = [9, "doo", 11.5]
逆にこれを利用できれば、いろいろおもしろいことができるかもしれませんね。
0 件のコメント:
コメントを投稿
注: コメントを投稿できるのは、このブログのメンバーだけです。