📖

[Swift] Array の反転操作でのあれこれ

に公開

概要

Swift で Array を反転する方法として、以下の4パターンに分けて解説していきます。

  • Iteration で用いたい場合
  • 処理を加えた上で、Iteration を行いたい場合
  • 新しい逆順の Array が欲しい場合
  • in-place で反転させたい場合

解説

Iteration で逆順の Array を用いたい場合

Iteration において逆順の Array を用いたい場合には、型を明示的に指定せずに .reversed() を利用することで、逆順に Array 内の要素にアクセスできます。このとき、.reversed() を呼び出す際には、ReversedCollection 型が返され、ReversedCollection 型は元々の Array への参照を保持しているため、メモリの再割り当てをせずに、各要素に逆順でアクセスできます。Array 自体をコピーする必要がないため、パフォーマンスの観点で優れています。

// 元の配列
let nums: [Int] = [1, 2, 3, 4]

for num in nums.reversed() {
    print(num) // 4, 3, 2, 1 が順に出力
}
  • 利点
    • 新しいメモリを使用せずに、各要素に逆順にアクセスできるため、メモリ効率が良い
    • .reversed() の時間・空間計算量はO(1)
  • 欠点
    • 逆順のアクセスは iteration 内での使用に限定される

処理を加えた上で、Iteration を行いたい場合

各要素に何らかの処理を施した上で、逆順にアクセスを行いたい場合は、.mapを用いることで、処理を加えた上でのアクセスが可能になります。また、lazy を用いることによって、重い処理であっても、遅延で処理を行うこともできます。ただし、.map を使用した場合、新しくメモリを使用することになるため、パフォーマンスの低下が起きます。

  • 通常の.mapの使用法
// 元の配列
let nums: [Int] = [1, 2, 3, 4]

let reversedMapped = nums.reversed().map { $0 * 2 }

for str in reversedMapped {
    print(str) // [8, 6, 4, 2]
}
  • lazyでの.mapの使用法
// 元の配列
let nums: [Int] = [1, 2, 3, 4]

let lazyMapped = nums.lazy.reversed().map { $0 * 2 }

// lazyMapped は LazyMapCollection で、
// for-in などで使うときに初めて、必要なメモリが確保される。
for str in lazyMapped {
    print(str) // [8, 6, 4, 2]
}
  • 利点
    • 処理を加えたうえで、逆順にアクセスできる
    • lazyを使用すると、大規模な配列に対する複雑な処理を効率的に実行できる。
  • 欠点
    • 新しくメモリに割り当てる必要があるため、パフォーマンスが落ちる
    • .reversed().map() の時間・空間計算量はO(n)

新しい逆順の Array が欲しい場合

新しい逆順の Array が必要な場合には、明示的に型変換を行うことで、ReversedCollection型ではなく、Array型の変数を得ることができます。やはりメモリの再割り当てが発生するため、パフォーマンスは低下してしまいます。

// 元の配列
let nums: [Int] = [1, 2, 3, 4]

let reversedNums: [Int] = nums.reversed()
// もしくは
let reversedNums = Array(nums.reversed())

print(reversedNums)  // [4, 3, 2, 1]
  • 利点
    • 元の配列とは独立した、新しい逆順の Array を作成できる。
  • 欠点
    • メモリの再割り当てが発生するため、パフォーマンスが低下する。
    • 時間・空間計算量はO(n)

in-place で反転させたい場合

in-place で反転させたい場合は、.reverse()を用いることで、新たにメモリを確保することなく、Array を反転させることができます。

// 元の配列
var nums: [Int] = [1, 2, 3, 4]
nums.reverse()
print(nums)  // [4, 3, 2, 1]
  • 利点
    • メモリ効率が良い。(O(1))
  • 欠点
    • 元の配列のデータが変更される
    • 時間計算量は、O(n)

まとめ

Swift で Array を反転させる際のパターンを4つ紹介しました。単純に各要素を逆順にアクセスしたい場合には、そのまま.reversed()を使用するなど、状況に合わせて各手法を使い分けるのが良いでしょう。

各手法の比較

手法 利点 欠点 時間計算量 空間計算量
.reversed() メモリ効率が良い、新しい Array を作成しない iteration 内での使用に限定 O(1) O(1)
.reversed().map() 処理を加えた上で反転できる メモリ効率が悪い O(n) O(n)
Array(nums.reversed()) 新しい逆順の Array を作成できる メモリ効率が悪い O(n) O(n)
.reverse() メモリ効率が良い、パフォーマンスが良い 元の配列の内容が変更される O(n) O(1)

参照

https://developer.apple.com/documentation/swift/array/reversed()
https://developer.apple.com/documentation/swift/lazycollection
https://developer.apple.com/documentation/swift/reversedcollection
https://developer.apple.com/documentation/swift/lazycollection

Discussion