Swiftでコレクションを扱うためのOSS: TraverSwift

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

をつくりました。

TraverSwift

主にScalaのTraversableトレイトHaskellのData.Listにあるような一連の関数をSwiftで使ってみようという試みになっています。

入れ方

iOS 8 以上対象のプロジェクトでSwiftに対応したCocoapods0.36beta1を用いてインストールできます。

pod 'TraverSwift'

使い方

使い方についてはドキュメントページにも解説がありますが、こちらにも記載します。

以下各節は第一引数にどのようなプロトコルに準拠した型やストラクチャをとるかで区分けしています。

SequenceTypeプロトコル全般

any関数, all関数

any関数は少なくともひとつの要素が第二引数の条件式を満たす時にtrueを、all関数はすべての要素が第二引数の条件式を満たす時にtrueを返します。

// 少なくともひとつの要素が条件を満たす
let seq = [1,2,3,4,5,6,7]
any (seq) { a in a > 6 } // true

// すべての要素が条件を満たす
all (seq) { a in a > 0 } // true

intersperse関数

intersperse関数はすべての要素の間に新しく別の要素を入れて新たな配列を生成します。

// すべての要素の間に要素を新たに追加した配列を取得する
let str = "abcde"
let result = intersperse(str, ",")
result == ["a",",","b",",","c",",","d",",","e"] // true

subsequence関数

部分列全般を配列として返します。

// 要素の部分列全般を配列として返す
let seq = [1,2,3]
let result = subsequences(seq)
result == [[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]] // true

scan関数

reduce関数の途中経過を配列として返します。

// reduceまでの過程を配列として返す
let seq1 = SequenceOf([4, 2, 4])
scan(seq1, 64, /) == [64, 16, 8, 2] // true
scan(seq2, 4) { x, y in 2 * x + y } == [4, 9, 20, 43] // true

flatMap関数

各要素からSequenceTypeを返すような関数を第二引数にとり、各要素に適用した結果を結合して配列として返します。

// 各要素への第二引数の適用結果を結合した配列として返す 
let seq1 = SequenceOf([4, 2, 3])
let result = flatMap(seq1) { elem in SequenceOf([elem, elem * 10 + elem]) }
result == [4,44,2,22,3,33] // true

groupBy関数

第二引数に定義した条件式をもとにグループ分けして新しい配列を返します。

// 第二引数をもとにグループ分けされた配列を生成
let seq1 = SequenceOf([1,2,3,4,5,6,7,8,9])
let result1 = groupBy(seq1, <)
result1 == [[1,2,3,4,5,6,7,8,9]] // true
let result2 = groupBy(seq1, >)
result2 == [[1],[2],[3],[4],[5],[6],[7],[8],[9]] // true
        
let seq2 = SequenceOf([5,6,1,3,4,5,6,7,8,9,10])
let result3 = groupBy(seq2, <)
result3 == [[5,6], [1,3,4,5,6,7,8,9,10]] // true
let result4 = groupBy(seq2, >)
result4 == [[5],[6,1],[3],[4],[5],[6],[7],[8],[9],[10]] // true

tail関数 & rtail関数

tail関数は先頭要素を、rtail(Scala, Haskellのinit)は最後の要素を引数から除外した新たな配列を生成します。

// 先頭要素の除外
let seq1 = SequenceOf([1, 2, 3, 4, 5, 6])
tail(seq1) == [2, 3, 4, 5, 6] // true

// 最後の要素の除外
rtail(seq1) == [1, 2, 3, 4, 5] // true

tails関数 & rtails関数

tails関数はtail関数を引数に順次適用していった結果の配列を返します。 rtails関数はrtail関数を引数に順次適用していった結果の配列を返します。(順番は後ろの方が長い)

// tail関数を順次適用した結果の配列 
let seq1 = SequenceOf([1, 2, 3, 4])
tails(seq1) == [[1,2,3,4],[2,3,4],[3,4],[4],[]] // true

// rtail関数を順次適用した結果の配列
rtails(seq1) == [[],[1],[1,2],[1,2,3],[1,2,3,4]] // true

takeWhile関数 & dropWhile関数 & span関数

takeWhile関数は先頭から各要素に対して第二引数の条件を調べていき、いったんfalseになったらそこまでの要素を配列として返します。

dropWhile関数はtakeWhileで得られる結果を差し引いたものを配列として返します。

span関数はtakeWhile, dropWhileの結果をタプルで返します。

// 条件が当てはまる最大の先頭配列
let seq1 = [1,2,3,4,1,2,3,4]
takeWhile(seq1) { elem in elem < 3 } == [1,2] // true

// takeWhileで得られる結果を除いた残りの配列
dropWhile(seq1) { elem in elem < 3 } == [3,4,1,2,3,4] // true

// takeWhile, dropWhileの結果をタプルで得る
let result1 = span(seq1) { elem in elem < 3 }
result1.0 == [1, 2] // true
result1.1 == [3,4,1,2,3,4] // true

cast関数

暗黙的アンラップ型にくるまれた配列に対しては配列に対するasキャストが失敗する場合があるため、それを補う為の関数です。 第二引数には要素のキャスト先の型を渡します。

// 型が混合した配列についてはキャストが失敗し、Optional.Noneを返す
let objs: [NSObject]! = [NSString(string: "aaa"), NSNumber(int: 123), NSString(string: "ag")]
cast(objs, NSString.self) == nil // true

// 型が混合していない配列についてはキャストが成功し、Optional.Someを返す
let strs: [NSObject]! = [NSString(string: "aaa"), NSString(), NSString(string: "ag")]
cast(strs, NSString.self) != nil // true

Equatable プロトコルに準拠した型の要素を持った SequenceTypeプロトコル

group関数

先頭から走査した各要素を等値演算子が真であるかどうかでグループ分けした配列を返します。

// == によるグループ分けを行った配列を返す
let seq1 = SequenceOf([1,1,1,1,2,2,2,2,3,3,2,2,2,5,6,7])
let result1 = group(seq1)
result1 == [[1,1,1,1],[2,2,2,2],[3,3],[2,2,2],[5],[6],[7]] // true

stripPrefix関数

第二引数のSequenceTypeを第一引数の先頭と比較し、合致していたら第二引数を除外した配列をOptional.Someにくるんで生成、合致していなかったらOptional.Noneを返します。

// 合致していたら第二引数を除外した配列をOptional.Someにくるんで生成
stripPrefix("foobar", "foo")! == ["b","a","r"] // true
stripPrefix("foo", "foo")! == [] // true

// 合致していなかったらOptional.Noneを返す
stripPrefix("barfoo", "foo") == nil // true
stripPrefix("barfoobaz", "foo") == nil // true

distinct関数

SequenceTypeから重複要素を除外して新たな配列を返します。

// 重複要素の除外された新たな配列を生成
distinct([1,2,3,4,5,4,3,2,3,1,0]) == [1,2,3,4,5,0] // true
distinct("bannana") == ["b", "a", "n"] // true

union関数

2つの引数に対して和集合をとります。各引数に重複があった時は除外されます。

// 和集合の配列を生成
union([1,2,3], [2,3,4]) == [1,2,3,4] // true

intersect関数

2つの引数に対して積集合をとりますが、一つ目の引数の重複は除外されません。

// 積集合の配列を得る
intersect([1,2,3,4], [2,4,6,8]) == [2,4] // true

// 一つ目の重複要素は除外されない
intersect([1,2,2,3,4], [6,4,4,2]) == [2,2,4] // true

equel演算子

2つの引数の各要素が等しく、かつ並び順も等しい場合にtrueを返す演算子です。

// 各要素、並び順の等値性判定
let seq1 = SequenceOf([1,2,3,4,5,6])
let seq2 = SequenceOf([1,2,3,4,5,6])  
seq1 == seq2 // true

特殊な型を持った SequenceTypeプロトコル

sum関数 & product関数

IntegerArithmeticType, FloatingArithmeticType(Double, Float)の要素を持つSequenceTypeについて、各要素の総和、総積を計算します。

// 総和
let seq1 = SequenceOf([1,2,3,4,5,6])
sum(seq1) == 21 // true
let seq2 = SequenceOf([1.1,2.2,3.3,4.0,5.2,6.1])
sum(seq2) == 21.9 // true

// 総積
let seq3 = SequenceOf([1.0,2.2,3.0,4.1,5.0,6.0])
product(seq1) == 720 // true
product(seq3) == 1.0 * 2.2 * 3.0 * 4.1 * 5.0 * 6.0 // true

and関数 & or関数

BooleanTypeの要素を持つSequenceTypeについて and関数はすべてがtrueかどうかを判定し、or関数は少なくともひとつがtrueかどうかを判定します。

 
// すべての要素がtrueかどうか
let seq1 = SequenceOf([true, true, true, true])
and(seq1) // true

// 少なくともひとつの要素がtrueかどうか
let seq2 = SequenceOf([false, true, false, false])
or(seq2) // true

CollectionTypeプロトコル

findIndex関数

引数を走査していき、要素に対する条件式が真である最初のインデックスをOptionalにくるんで返します。条件式が真であるような要素が見つからなかった場合はOptional.Noneを返します。

// 要素に対する条件式が真であるような最初のインデックスを得る
let col1 = [1,2,3,4,4,6]
findIndex(col1) { elem in elem > 3 }! == 3 // true

// 条件式が真であるような要素が見つからない場合はOptional.Noneを得る
findIndex(col1) { elem in elem > 6 } == nil // true

take関数 & drop関数 & split関数

take関数は最初から指定インデックス番目(指定インデックスの要素自体は含まない)までの配列を取得します。

drop関数は指定インデックス番目から最後まで配列を取得します。

splitAt関数はtake, drop関数の結果を一緒にタプルとして取得します。

// 最初から指定インデックス番目までの配列を取得
let col1 = [1,2,3,4,5,6,7]
take(col1, 3) == [1, 2, 3] // true
take(col1, 7) == [1, 2, 3, 4, 5, 6, 7] // true

// 指定インデックス番目から最後までの配列を取得
drop(col1, 3) == [4, 5, 6, 7] // true
drop(col1, 0) == [1, 2, 3, 4, 5, 6, 7] // true

// take, dropの結果をタプルで取得
let result1 = splitAt(col1, 3)
result1.0 == [1, 2, 3] // true
result1.1 == [4, 5, 6, 7] // true

Arrayストラクチャ

existsAny関数 & existsAll関数

各要素がオプショナルであるような配列に対して、少なくともひとつの要素が.SomeであるかどうかをexistsAny関数は判定し、すべての要素が.SomeであるかどうかをexistsAll関数は判定します。

現在はコンパイラクラッシュの為、T?型を要素に持つSequenceTypeプロトコルを第一引数に持つ関数にすることを断念しています。

// 少なくともひとつの要素が.Someであるかどうか
let arr1 = ["1e3","123","rf3","rf3"].map{ str in str.toInt() }
existsAny(arr1) // true

// すべての要素が.Someであるかどうか
let arr2 = ["13","123","3","312"].map{ str in str.toInt() }
existsAll(arr2) // true

concat関数

配列の配列を結合し、配列として返します。

現在はコンパイラクラッシュの為、SequenceTypeを要素に持つSequenceTypeを第一引数に持つ関数にすることを断念しています。

// 配列の結合
let arr1 = [[1,2,3],[4,5,6],[7,8,9]]
concat(arr1) == [1,2,3,4,5,6,7,8,9] // true

ライセンス

MITです