はじめに (対象読者・この記事でわかること)
この記事は、iOS アプリや macOS アプリの開発に Swift を使っているエンジニア、または Swift の基礎を学び始めたプログラミング初心者を対象としています。
二次元配列([[Int]] や [[String]] など)を扱う際に、各行の 先頭要素だけを集めた一次元配列 を取得したいケースは意外に多いです。例えば、テーブルビューのヘッダー情報や、CSV データの最初のカラムだけを抽出したいときなどです。本記事を読むことで、以下が理解・実装できるようになります。
- Swift の配列操作の基本と、
map・compactMapなど高階関数の使い方 - 二次元配列から先頭要素だけを抽出する具体的なコード例と、可読性・安全性を高めるベストプラクティス
- 実装時に陥りやすいエッジケース(空配列や行が空の場合)の対処法
この記事を書くきっかけは、社内のコードレビューで「二次元配列の先頭要素だけを取得するロジックが散在していて読みにくい」旨の指摘を受けたことです。その経験を踏まえて、統一的で安全な実装パターンをまとめました。
前提知識
この記事を読み進める上で、以下の知識があるとスムーズです。
- Swift の基本的な文法(変数宣言、関数定義、クロージャ)
- 配列(
Array)の基本操作(インデックスアクセス、append、for-inループ)
二次元配列から先頭要素だけを抽出する概要
二次元配列は「配列の中に配列が入っている」構造で、[[Element]] の形で表現されます。各行(内部配列)の先頭要素だけを取り出すには、行ごとに先頭要素を取得し、結果を新しい一次元配列にまとめるというシンプルな処理です。
しかし、単に for ループでインデックスを参照するだけでは、以下のような問題が発生しやすいです。
- 空行の処理:内部配列が空の場合にインデックスアクセス (
row[0]) を行うとランタイムエラー(Index out of range)が発生します。 - 型安全性:要素の型が
IntやStringといった具体的な型であっても、汎用的に扱えるようにジェネリックに書くと再利用性が上がります。 - 可読性と宣言的記述:Swift では高階関数(
map,compactMap)を使うことで、意図を一目で理解できるコードが書けます。
そこで本稿では、安全かつ宣言的な実装として、compactMap とオプショナルバインディングを組み合わせた方法を中心に解説します。加えて、単純な for ループによる実装例や、Result を使ったエラーハンドリングのパターンも紹介し、読者が自分のプロジェクトに最適な形を選択できるようにします。
実装手順:先頭要素の配列取得
以下では、具体的な実装例をステップごとに示します。コードは Swift 5.9(Xcode 15)相当を前提としていますが、基本的な概念は以前のバージョンでも同様に適用できます。
ステップ1:基本的な for ループ版
Swiftfunc firstElements<T>(of matrix: [[T]]) -> [T] { var result: [T] = [] for row in matrix { if !row.isEmpty { result.append(row[0]) } } return result } // 使用例 let numbers = [[1, 2, 3], [4, 5, 6], [7, 8]] let firsts = firstElements(of: numbers) // [1, 4, 7]
この実装は最も直感的です。row.isEmpty で空行を除外し、先頭要素を result に追加しています。ただし、手続き的で冗長に感じる場合があります。
ステップ2:宣言的に compactMap を利用
Swiftfunc firstElementsCompactMap<T>(of matrix: [[T]]) -> [T] { return matrix.compactMap { $0.first } } // 使用例 let strings = [["apple", "banana"], [], ["cherry"]] let firstStrings = firstElementsCompactMap(of: strings) // ["apple", "cherry"]
compactMap はクロージャの戻り値が nil の場合は除外するため、空行は自動的にスキップされます。$0.first は各行の先頭要素(オプショナル)を返し、compactMap が nil を除外してくれるので、エラーハンドリングが不要です。
ステップ3:ジェネリックかつエラー情報を返すバージョン
プロジェクトによっては「空行があるかどうか」を呼び出し側に知らせたいケースがあります。その場合は Result 型で成功と失敗を明示できます。
Swiftenum FirstElementError: Error { case emptyRow(at: Int) } func firstElementsResult<T>(of matrix: [[T]]) -> Result<[T], FirstElementError> { var result: [T] = [] for (index, row) in matrix.enumerated() { guard let first = row.first else { return .failure(.emptyRow(at: index)) } result.append(first) } return .success(result) } // 使用例 let mixed = [[10, 20], [], [30]] switch firstElementsResult(of: mixed) { case .success(let values): print("先頭要素:", values) case .failure(let error): print("エラー:", error) // エラー: emptyRow(at: 1) }
この実装は、どの行が空であったか を正確に把握できるため、デバッグやユーザーへのフィードバックに有用です。
ハマった点やエラー解決
1. Index out of range エラー
row[0] を直接参照した際に、空行が原因でクラッシュしました。対策として row.isEmpty のチェックと、compactMap の活用が効果的です。
2. 型推論エラー
compactMap { $0.first } の戻り値が T? になるため、変数の型を明示しないとコンパイラが [[T]] と T の混同を起こすことがあります。以下のように型注釈を加えると解決します。
Swiftlet result: [Int] = matrix.compactMap { $0.first }
3. 空配列全体への対応
入力が全くの空配列([])の場合でも、上記実装は問題なく空の結果配列 [] を返します。特別なチェックは不要です。
解決策まとめ
| 手法 | メリット | デメリット |
|---|---|---|
for ループ + if !row.isEmpty |
書きやすく初心者向き | 冗長で可読性が低い |
compactMap { $0.first } |
宣言的で短く、空行自動除外 | 空行の位置情報が取れない |
Result + enumerated() |
エラーハンドリングが明示的 | 若干コード量が増える |
プロジェクトの要件に合わせて、上記のいずれかを選択してください。特に 可読性と安全性 を重視するなら compactMap がデファクトスタンダードです。
まとめ
本記事では、Swift で二次元配列から各行の先頭要素だけを抽出する方法 を解説しました。
- 基本的な
forループ での実装は分かりやすいが、冗長になる。 compactMapを使った宣言的実装 が最もシンプルかつ安全で、空行は自動的に除外される。Resultを用いたエラーハンドリング により、空行があった位置を呼び出し側に通知できる。
これらのテクニックを活用すれば、配列操作に伴うバグや可読性の低下を防ぎ、コードベースの品質を向上させられます。次回は、二次元配列の行や列を動的に入れ替えるパターン について詳しく取り上げる予定です。
参考資料
- Swift公式ドキュメント – Collection Types
- Apple Developer Documentation – compactMap
- 「Swift実践入門」(技術評論社) – 配列と高階関数の章
