2015/07/24

[Swift] 再帰enumで単連結リストを実装する

Xcode 7 beta 4からSwiftのenumcaseの型に自分と同じ型を利用して、再帰構造をつくることができるようになりました。

これを利用して、単連結リストを実装してみました。

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型でmapreduceを使えるようにしましょう。

Swift 2.0からはmapreduceはプロトコル拡張として、SequenceTypeプロトコルに実装済みです。

よって、ListSequenceTypeプロトコルに適合させるだけでこれらのメソッドが利用できるようになります。

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
        }
    }
}

これで、mapreduceが使えるようになります。ただし、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 件のコメント:

コメントを投稿

注: コメントを投稿できるのは、このブログのメンバーだけです。