4
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Swift】シーケンスとコレクション

Last updated at Posted at 2020-12-05

#シーケンスとコレクション

シーケンスとは、その要素に一方向から順次アクセス可能なデータ構造のことをさします。
つまり、配列は先頭のインデックスから順次アクセスできるので、
シーケンスの一種であるということになります。

また、標準ライブラリには
シーケンスを汎用的に扱うためのSequenceプロトコルが用意されています。

コレクションは、単に値の集まりという認識の方が多いと思いますが、
正確には一方向から順次アクセスと、
特定のインデックスの値への直接アクセスが可能なデータ構造を意味するらしいです。

シーケンスとコレクションの機能の差からわかるように、
コレクションはシーケンスを包含する概念になっております。

また、標準ライブラリには、
コレクションを汎用的に扱うためのCollectionプロトコルが用意されています。

以前記事にした、Array<Element>型やDictionary<Key, Value>型、
Range<Bound>型、String型は、全てSequenceプロトコルとCollectionプロトコルに準拠しています。

どちらも似たもののようなイメージがありますが、
個人的にはコレクションは、何かしらのデータの集まりで、
シーケンスが、その集まりに対する操作という認識でいます。

##Sequenceプロトコル

Sequenceプロトコルは、シーケンスを表現したプロトコルです。
Sequenceプロトコルに準拠する型はfor-in文を用いて、その要素に順次アクセスできます。

また、Sequenceプロトコルは、様々な機能を持っているのでそれらについても紹介します。

メソッド 説明
forEach(_:)メソッド 要素に対して順次アクセスする
filter(_:)メソッド 要素を絞り込む
map(_:)メソッド 要素を変換する
flatMap(_:)メソッド 要素をシーケンスに変換し、それを一つのシーケンスに連結する
compactMap(_:)メソッド 要素を、潮お会いする可能性のある処理を用いて変換する
reduce(_:)メソッド 要素を一つにまとめる

###forEach(_:)メソッド

forEach(_:)メソッドは、全ての要素に対して順次アクセスするためのメソッドです。
引数のクロージャ内で各要素にアクセスします。
(クロージャについては別記事で詳しく説明する予定です。)


let string = "abcde"   // String型
var characterArray: [Character] = []   // Character型の配列

string.forEach { (Character) in
    characterArray.append(Character)
}

print(characterArray)

実行結果
["a", "b", "c", "d", "e"]

string.forEach
定数のstringでforEachメソッドを実行しています。

forEach(_:)メソッドは先頭から全ての要素に対して順次アクセスするので、
今回の場合は、定数string(abcde)のaからアクセスしていきます。

取得した要素を(Character) inのCharacterの中に格納します。
Characterに何か値が入ると、次の処理(すなわち{ }内)に移行します。

forEach(_:)で値を取得するまで(Characterの中に値が入るまで)は、
その次の{ }内の処理は実行されません。

Characterに値が入ると、{ }内の処理が行われるので、
characterArray.append(Character)が実行されます。

こちらの処理は、Array<Character>型の配列に、
定数stringから取得した値をappend()メソッドで追加しています。

append(Character)のCharacterは、
取得した要素が入っている変数になります。
つまり、(Character) inのCharacterです。

文字列から一つずつ値をとる場合はCharacter型なので注意!

a -> b -> c -> d -> e と全ての値を取得した後は、
Characterに値が入ることがないので処理は終了になります。

###filter(_:)メソッド

filter(_:)メソッドは、指定した条件を満たす要素のみを含む、
新しいシーケンスを返すメソッドになります。

条件は、引数のクロージャを用いて指定します。

クロージャの返り値はBool型の戻り値を返し、
戻り値がtrueなら要素は新しいシーケンスに含まれます。

クロージャの返り値とは、
クロージャから返される結果になります。
filter()メソッドの場合はtrueかfalseが返されます。

let integers = [1, 2, 3, 4, 5, 6]   // Array<Int>型
let filterInt = integers.filter { (int) -> Bool in
    int % 2 == 0
}
print(filterInt)

実行結果
[2, 4, 6]

先ほどのfoeEach()と少し違うところは、(int) -> Bool inとクロージャ内の処理です。

-> Boolの部分が返り値の型になります。
先ほどのforEach()メソッドは返り値の型に指定がなかったので記載されていませんでした。

今回のように返り値が記載されている場合は、
{ }内の処理の結果が返り値の型にならないといけません。

処理の流れとしては、
let filterInt = integers.filter {・・・}の箇所で
integersの要素の先頭から順次アクセスしていきます。
②アクセスした最初の値は、先頭の1になります。
③1が(int) -> Bool inの int の中に入ります。
④ int に値が入ったので、{ } 内の処理に移行します。
int % 2 == 0が実行されます。今回は int に1が入っているので結果はfalse
%というのは、割った余りの数を示します。
今回の場合は、1 / 2 の余り == 0 ・・・結果は余り1なのでfalse
⑥filterメソッドはtrueの値を返すので2, 4, 6が返される。
という流れになります。

###map(:)メソッド
map(
:)メソッドは、全ての要素を、特定の処理を用いて変換します。
要素を変換する処理は引数のクロージャを用いて指定します。

let integers = [1, 2, 3, 4, 5, 6]   // Array<Int>型
let mapInt = integers.map { (int) in
    int * 2
}
print(mapInt)

実行結果
[2, 4, 6, 8, 10, 12]

map(_:)メソッドを用いて別の型のシーケンスへと変更することも可能です。

let integers = [1, 2, 3, 4, 5, 6]   // Array<Int>型
let mapString = integers.map { (int) in
    String(int)
}
print(mapString)

実行結果
["1", "2", "3", "4", "5", "6"]

String(int)の箇所でイニシャライザを使い変換しています。

それ以外の処理はforEach()メソッドやfilter()メソッドと変わりませんので説明は省きます。

###flatMap(_:)メソッド

flatMap(_:)メソッドは、全ての要素をシーケンスに変換し、
それをさらに1つのシーケンスに統合します。

要素をシーケンスに変換する処理は引数のクロージャを用いて指定します。
要素をシーケンスへと変換するため、
引数のクロージャの戻り値はSequenceプロトコルに準拠している型を指定する必要があります。

let integers = [1, 2, 3]   // Array<Int>型
let flatMapInt = integers.flatMap { (int) -> [Int] in
    [int, int]
}
print(flatMapInt)

実行結果
[1, 1, 2, 2, 3, 3]

flatMap()メソッドは、シーケンスに返還後さらに一つに統合するのでこのような結果になります。

これをmap()メソッドで実行するとこのようになります。

let integers = [1, 2, 3]   // Array<Int>型
let mapInt = integers.map { (int) -> [Int] in
    [int, int]
}
print(mapInt)

実行結果
[[1, 1], [2, 2], [3, 3]]

1つに統合されることがないので、各シーケンスに別れた二次元配列になります。

###compactMap(:)メソッド
compactMap(
:)メソッドは、全ての要素を特定の処理で変換するメソッドです。
しかし、map()メソッドと異なり、変換できない値を無視する機能が備わっています。

要素を変換する処理は、引数のクロージャを用いて指定します。
また、引数のクロージャの戻り値はOptional<Wrapped>型になります。

クロージャ内でnilを返すことにより、その値を無視することができます。

let strings = ["123","abc","456","def"]   // Array<String>型
let compactMapInt = strings.compactMap { (string) -> Int? in
    Int(string)
}
print(compactMapInt)

実行結果
["123", "456"]

Int(string)で変換に失敗した、"abc", "def"はnilを返します
nilの値は無視されるので結果は["123", "456"]となります。

###reduce(_::)メソッド
reduce(_:
:)メソッドは、全ての要素を一つにまとめます。
第一引数に初期値を指定し、第二引数に要素の結果に反映する処理を指定します。

クロージャの第一引数から結果の途中経過、第二引数からその要素にアクセスできます。

let integers = [1, 2, 3, 4, 5, 6]   // Array<Int>型
let sum = integers.reduce(0) { (result, int) -> Int in
    result + int
}

let concat = integers.reduce("") { (result, int) -> String in
    result + String(int)
}

print(sum)
print(concat)

実行結果
21
123456

引数が2つあるので今までのメソッドより少し難しいかもしれません。

integers.reduce(0){・・・}の部分ですが、
第一引数に0が指定されています。第二引数は{・・・}になります。
第二引数がreduce( )の( )から出ていますがそういう記述方法なので気にしないでください・・・。

第一引数の0が、(result, int) -> Int inのresultに入ります。
intの部分はいつも通り各要素が順に入っていきます。

まずは、integers配列の0番目の要素である、1からスタートします。

この時のresultは0、intは1になります。

result + int の結果がクロージャの返り値になります。
(result, int) -> Int in->の右に指定されている型を返り値の型にしてください。

まだ順次アクセスの途中なので、この返り値はresultに代入されます。
{ }内の処理が終了したらintegers配列の1番目の2を処理します。

この時のresultは1、intは2になります。
これを6まで繰り返した結果がsum変数に代入される訳です。

2つ目の、let concat =の方は、String型なので実際に計算はされず、
文字同士の結合になりますので、"123456"という結果になります。

##Collectionプロトコル
Collectionプロトコルは、コレクションを表現したプロトコルで、
Sequenceプロトコルを継承しております。

なので、Collectionプロトコルに準拠している型は、
Sequenceプロトコルが提供しているメソッドなどの操作もできます。

また、Collectionプロトコルは次のような機能を提供しています。

・サブスクリプトによる要素への書き込み
指定した要素の読み書きを行う

・isEmptyプロパティ
コレクションが空かどうか

・countプロパティ
要素の個数を数える

・firstプロパティ
最初の要素を取得する

・lastプロパティ
最後の要素を取得する

それぞれ下記のような結果になります。

let array = [1, 2, 3, 4, 5]

array[3]   // 4
array.isEmpty   // false
array.count   // 5
array.first   // 1
array.last   // 5

様々なメソッドを紹介しましたが、
どれも便利な機能なので頭の片隅においていいと思います!

また、クロージャなどが出てきましたが、
それに関しても別の記事で紹介する予定なので、是非ご覧になってください。

以上、長くなってしまいましたが最後までご覧いただきありがとうございました。

4
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?