Swiftプログラミングにおいて、クロージャは軽量で柔軟なコードブロックとして非常に有用です。特に、データのソート処理においては、クロージャを使うことでコードを簡潔にし、読みやすくすることが可能です。本記事では、Swiftでクロージャを使用してソートアルゴリズムをコンパクトに実装する方法を段階的に説明します。クロージャの基本から、カスタムソート、さらにはパフォーマンス最適化まで、実用的なコード例を通じて理解を深めていきます。
クロージャとは何か
クロージャは、Swiftにおけるコードのブロックであり、変数や定数をキャプチャしながら他の関数やメソッドで使用できる機能です。Swiftでは、関数も一種のクロージャと考えられており、より簡潔に定義できるのが特徴です。クロージャは、無名関数とも呼ばれ、引数や戻り値を持つことができますが、そのシンプルさから、短いロジックを一箇所にまとめて表現したいときに非常に便利です。
クロージャの基本構文
クロージャは、以下のように定義されます。
{ (引数) -> 戻り値型 in
実行するコード
}
例えば、二つの整数を加算するクロージャは、以下のようになります。
let addClosure = { (a: Int, b: Int) -> Int in
return a + b
}
このように、クロージャはコンパクトな形で関数的な処理を記述できるため、コードの簡素化に大いに役立ちます。
ソートアルゴリズムの基本
ソートアルゴリズムとは、データを特定の順序に並べ替えるための手法を指します。データが適切に並べ替えられることで、検索や整列が容易になり、プログラム全体の効率が向上します。ソートアルゴリズムにはさまざまな種類があり、状況やデータの特性に応じて適切なものを選択する必要があります。
代表的なソートアルゴリズム
ソートアルゴリズムにはいくつかの代表的な種類があります。
バブルソート
バブルソートは、隣り合った要素を比較して入れ替えるシンプルなアルゴリズムです。効率は良くありませんが、理解しやすいアルゴリズムです。
クイックソート
クイックソートは、分割統治法を用いた高速なソートアルゴリズムです。配列をピボット要素で二つに分割し、それぞれを再帰的にソートします。
マージソート
マージソートも分割統治法を用い、リストを分割し、分割されたリストをマージすることで整列します。安定したソートを実現し、計算量は常にO(n log n)です。
Swiftの標準ソートメソッド
Swiftには、これらのソートアルゴリズムを自動的に実行する標準メソッドsort()
やsorted()
が用意されています。これらのメソッドは、内部的に効率の良いアルゴリズムを用いており、用途に応じて簡単に使用できます。この基本的なメソッドにクロージャを加えることで、独自のソート条件を簡単に実装することが可能です。
Swiftでのソートの基本方法
Swiftでは、配列をソートするために標準ライブラリが提供するメソッドを使って、簡単にデータを並べ替えることができます。主に使用されるメソッドにはsort()
とsorted()
があります。それぞれ、配列を破壊的に(自身を変更する)ソートするか、新しいソート済みの配列を返すかの違いがあります。
`sort()`メソッド
sort()
メソッドは、元の配列を破壊的にソートします。つまり、このメソッドを呼び出すと元の配列が直接並べ替えられます。
var numbers = [5, 2, 9, 1, 7]
numbers.sort()
print(numbers) // [1, 2, 5, 7, 9]
この場合、numbers
配列は昇順にソートされます。
`sorted()`メソッド
一方、sorted()
メソッドは元の配列を変更せず、新しいソート済みの配列を返します。
let numbers = [5, 2, 9, 1, 7]
let sortedNumbers = numbers.sorted()
print(sortedNumbers) // [1, 2, 5, 7, 9]
print(numbers) // [5, 2, 9, 1, 7](元の配列は変更されない)
sorted()
は非破壊的なので、元の配列はそのままの順序を保ち、新しい配列が生成されます。
カスタムソートの基本
デフォルトでは、数値や文字列は昇順でソートされますが、カスタムな順序でソートする場合には、クロージャを使って比較方法を指定することができます。例えば、降順でソートしたい場合は以下のように書きます。
let numbers = [5, 2, 9, 1, 7]
let descendingSortedNumbers = numbers.sorted { $0 > $1 }
print(descendingSortedNumbers) // [9, 7, 5, 2, 1]
このように、クロージャを利用することで、さまざまな条件でのソートを簡単に実現することができます。
クロージャを使ったカスタムソート
Swiftでは、クロージャを使うことでデフォルトの昇順ソートに加えて、特定の条件に基づいたカスタムソートを簡単に実装することができます。クロージャは、ソートアルゴリズムが二つの要素を比較する際に、その比較方法を動的に指定するために利用されます。
カスタムソートの基本的な使い方
配列のsorted(by:)
メソッドやsort(by:)
メソッドにクロージャを渡すことで、任意の比較ロジックを使って配列をソートできます。クロージャには、配列の二つの要素を比較し、比較結果に応じたブール値を返すロジックを記述します。
例えば、以下のコードは降順にソートするためのクロージャを使った例です。
let numbers = [5, 2, 9, 1, 7]
let descendingNumbers = numbers.sorted(by: { (a: Int, b: Int) -> Bool in
return a > b
})
print(descendingNumbers) // [9, 7, 5, 2, 1]
このクロージャは、a
とb
という二つの要素を受け取り、a > b
という条件でソートを行っています。a
がb
より大きい場合にtrue
を返すことで、降順のソートを実現しています。
クロージャの省略記法
Swiftでは、クロージャの記法を省略することでさらに簡潔なコードを書くことができます。上記のコードは次のように書き換え可能です。
let descendingNumbers = numbers.sorted { $0 > $1 }
ここでは、$0
がクロージャ内の最初の引数、$1
が二番目の引数を表しています。このようにクロージャの省略記法を用いると、コードをよりコンパクトに記述できます。
文字列のカスタムソート
カスタムソートは、数値だけでなく文字列や複雑なオブジェクトにも適用可能です。例えば、文字列を長さでソートする場合は次のようにクロージャを使います。
let words = ["apple", "banana", "pear", "orange"]
let sortedByLength = words.sorted { $0.count < $1.count }
print(sortedByLength) // ["pear", "apple", "banana", "orange"]
この例では、文字列の長さcount
を基準にソートしています。クロージャを使うことで、特定の要件に応じた柔軟なソートが可能です。
クロージャを用いたカスタムソートにより、ソートの基準を自由に定義でき、簡単に動的なデータ処理を実現できます。
ソートアルゴリズムをクロージャでコンパクトに実装する
Swiftでは、クロージャを用いることで、ソートアルゴリズムを非常にコンパクトに実装することができます。これにより、コードが読みやすくなるだけでなく、ソート条件を直感的に定義できるため、柔軟な実装が可能です。
クロージャを用いたコンパクトなソート
クロージャを用いたソートでは、デフォルトのソート関数に比べて比較条件を自由にカスタマイズできます。例えば、次のようなコードで整数の降順ソートを簡潔に実装することができます。
let numbers = [10, 3, 7, 1, 9]
let sortedNumbers = numbers.sorted { $0 > $1 }
print(sortedNumbers) // [10, 9, 7, 3, 1]
ここでは、$0
がクロージャ内の最初の引数、$1
が二番目の引数を表しており、これを比較して降順にソートしています。この形式は、短く直感的なコードを書く際に非常に有効です。
クロージャの引数名を省略してさらにコンパクトに
Swiftは、クロージャの省略記法により、ソートをさらにシンプルに表現することができます。例えば、次のコードは二つの整数を昇順にソートする例です。
let sortedNumbers = numbers.sorted(by: <)
print(sortedNumbers) // [1, 3, 7, 9, 10]
ここでは、<
演算子をクロージャとして直接渡すことで、クロージャの定義自体を省略し、非常に短い記述でソートを実現しています。
カスタム条件を使ったソート
クロージャの柔軟性は、数値のソートだけでなく、複雑な条件でのソートにも活用できます。例えば、オブジェクトのプロパティに基づいてソートする場合、以下のようなコードで実装できます。
struct Person {
let name: String
let age: Int
}
let people = [
Person(name: "Alice", age: 30),
Person(name: "Bob", age: 25),
Person(name: "Charlie", age: 35)
]
let sortedByAge = people.sorted { $0.age < $1.age }
for person in sortedByAge {
print("\(person.name): \(person.age)")
}
// Bob: 25, Alice: 30, Charlie: 35
この例では、Person
構造体のage
プロパティを基準にして、昇順に並べ替えています。クロージャを使うことで、任意のプロパティに基づいたソート条件を簡単に実装できます。
クロージャを使った高度なソート例
さらに複雑な例として、年齢が同じ場合には名前のアルファベット順でソートする、複数条件でのソートもクロージャで簡単に実装可能です。
let sortedByAgeThenName = people.sorted {
if $0.age == $1.age {
return $0.name < $1.name
} else {
return $0.age < $1.age
}
}
for person in sortedByAgeThenName {
print("\(person.name): \(person.age)")
}
// Bob: 25, Alice: 30, Charlie: 35
このように、クロージャを活用することで、ソートの条件を多様にカスタマイズでき、非常にコンパクトかつ効率的なソートアルゴリズムの実装が可能になります。
パフォーマンス最適化のためのテクニック
クロージャを用いたソートは非常に便利ですが、大規模なデータセットやパフォーマンスが重要な場面では、効率を意識した実装が求められます。Swiftでは、ソート時のパフォーマンスを向上させるためのさまざまなテクニックが存在します。これらのテクニックを活用することで、クロージャによるカスタムソートを効果的に最適化できます。
安定ソートと不安定ソート
まず、ソートの「安定性」を理解することが重要です。安定ソートは、ソート前に同じ値を持つ要素が、ソート後も元の順序を保持するアルゴリズムを指します。Swiftのsorted()
は安定ソートを採用しており、大規模なデータセットで順序が重要な場合に特に有効です。
しかし、安定ソートは計算コストが高い場合があります。安定性が不要な場合は、カスタムソートを利用し、より効率的なアルゴリズムを選択することでパフォーマンス向上が期待できます。
比較回数を減らす
クロージャ内での比較が複雑な場合、比較回数を減らすことでソートのパフォーマンスを向上させることができます。例えば、複数回同じ値を計算する場面では、その値を事前に計算してキャッシュすることで、余分な計算を避けることが可能です。
let sortedByLength = words.sorted {
let firstLength = $0.count
let secondLength = $1.count
return firstLength < secondLength
}
このように、一度計算した結果をキャッシュすることで、不要な再計算を回避し、ソート処理全体の効率を向上させます。
非同期処理でのソート
大量のデータをソートする場合、メインスレッド上でソートを行うと、アプリのレスポンスが低下する可能性があります。Swiftでは、非同期処理(GCDやOperation Queue)を活用して、バックグラウンドでソート処理を行うことで、UIのパフォーマンスを損なわずに処理を進めることが可能です。
DispatchQueue.global().async {
let sortedNumbers = numbers.sorted { $0 < $1 }
DispatchQueue.main.async {
print(sortedNumbers)
}
}
このコードでは、ソート処理をバックグラウンドスレッドで実行し、完了後に結果をメインスレッドに渡して処理しています。
不要なコピーを避ける
sorted()
は、新しい配列を返すため、元の配列をコピーする際にメモリを消費します。パフォーマンスを重視する場合、元の配列を変更しても問題ない場面では、sort()
を使うことで不要なメモリの使用を防ぎます。
var numbers = [10, 3, 7, 1, 9]
numbers.sort() // メモリのコピーを回避
print(numbers)
これにより、メモリ効率を改善し、特に大きな配列を扱う場合には実行速度が向上します。
データの前処理による効率化
大量のデータセットを処理する際には、あらかじめデータをある程度整えておくことも重要です。例えば、ソート対象のデータがすでに部分的に整列している場合、効率的なアルゴリズムがその事実を活用して高速にソートを完了することがあります。
このように、クロージャを使ったソートでも、パフォーマンスを最大限に引き出すためのテクニックを活用することで、大規模なデータセットやリアルタイム処理でもスムーズな動作を実現できます。
応用例:オブジェクトのソート
クロージャを使ったソートは、単なる数値や文字列の配列だけでなく、カスタムオブジェクトを扱う場面でも非常に役立ちます。特に、オブジェクトの特定のプロパティに基づいてソートを行う場合、クロージャの柔軟性が大いに活かされます。ここでは、実際のカスタムオブジェクトを例に、クロージャを使ってオブジェクトをソートする方法を紹介します。
カスタムオブジェクトを使ったソート
例えば、Person
というカスタムオブジェクトがあり、そのオブジェクトにname
とage
というプロパティがあるとします。このオブジェクトのリストをage
(年齢)の昇順でソートする例を見てみましょう。
struct Person {
let name: String
let age: Int
}
let people = [
Person(name: "Alice", age: 30),
Person(name: "Bob", age: 25),
Person(name: "Charlie", age: 35)
]
let sortedByAge = people.sorted { $0.age < $1.age }
for person in sortedByAge {
print("\(person.name): \(person.age)")
}
このコードでは、Person
オブジェクトのリストをage
プロパティに基づいて昇順にソートしています。$0
と$1
はそれぞれ比較するPerson
オブジェクトであり、それぞれの年齢プロパティを比較することでソートを行います。結果は次のようになります。
Bob: 25
Alice: 30
Charlie: 35
複数のプロパティを使ったソート
一つのプロパティだけではなく、複数のプロパティを基準にソートすることも可能です。例えば、まず年齢でソートし、年齢が同じ場合には名前のアルファベット順でソートする例を見てみます。
let sortedByAgeThenName = people.sorted {
if $0.age == $1.age {
return $0.name < $1.name
} else {
return $0.age < $1.age
}
}
for person in sortedByAgeThenName {
print("\(person.name): \(person.age)")
}
この場合、まずage
で昇順にソートし、age
が同じ場合はname
でアルファベット順にソートされます。
プロパティがオプショナルな場合のソート
オブジェクトのプロパティがオプショナル(nil
の可能性がある)場合でも、クロージャを使って適切にソートすることができます。例えば、以下のようにプロパティがオプショナルな場合、nil
を先にソートし、それ以外の値をその後に並べることが可能です。
struct Person {
let name: String
let age: Int?
}
let people = [
Person(name: "Alice", age: nil),
Person(name: "Bob", age: 25),
Person(name: "Charlie", age: 35)
]
let sortedByAgeWithNilFirst = people.sorted {
if let age1 = $0.age, let age2 = $1.age {
return age1 < age2
} else if $0.age == nil {
return true
} else {
return false
}
}
for person in sortedByAgeWithNilFirst {
let ageText = person.age != nil ? "\(person.age!)" : "nil"
print("\(person.name): \(ageText)")
}
このコードでは、年齢がnil
の場合はリストの先頭に配置し、nil
でない場合は通常の昇順で並べ替えています。
応用のポイント
クロージャを使ったオブジェクトのソートは、データの柔軟な操作に非常に役立ちます。以下のポイントに注目することで、さらに効率的なソートが可能になります。
- オブジェクトのプロパティに基づくソート
- 複数のプロパティを組み合わせたソート
- オプショナル値(
nil
)の適切な処理
これにより、実際のアプリケーション開発において、複雑なデータ構造に対しても効率的にソートを適用することができます。
Swiftの高次関数とクロージャの関係
Swiftでは、クロージャと高次関数(higher-order functions)を組み合わせることで、コードの柔軟性と表現力を大幅に向上させることができます。高次関数は、関数を引数として受け取ったり、関数を返り値として返す関数のことを指し、クロージャを活用する際に頻繁に使われます。これにより、配列のフィルタリングや変換、ソートなどを簡潔かつ効率的に行うことができます。
高次関数とは何か
高次関数とは、関数を引数として渡したり、他の関数を返すような関数のことです。Swiftには高次関数がいくつも用意されており、よく使用されるものにmap()
, filter()
, reduce()
などがあります。
`map()`とクロージャ
map()
は、配列の各要素に対して同じ操作を適用し、その結果を新しい配列として返す高次関数です。クロージャを使用して各要素に対する処理を定義します。
例えば、整数の配列をすべて2倍にする例を見てみましょう。
let numbers = [1, 2, 3, 4, 5]
let doubledNumbers = numbers.map { $0 * 2 }
print(doubledNumbers) // [2, 4, 6, 8, 10]
ここでは、map()
関数の中にクロージャを渡し、各要素に対して$0 * 2
という操作を適用しています。クロージャによって、要素ごとのカスタム処理が容易に実装できます。
`filter()`とクロージャ
filter()
は、配列から条件に合致する要素だけを抽出する高次関数です。抽出条件をクロージャで指定します。
例えば、偶数だけを抽出する場合は以下のようになります。
let numbers = [1, 2, 3, 4, 5, 6]
let evenNumbers = numbers.filter { $0 % 2 == 0 }
print(evenNumbers) // [2, 4, 6]
この例では、クロージャ内で$0 % 2 == 0
という条件を指定し、偶数だけを抽出しています。
`reduce()`とクロージャ
reduce()
は、配列の要素を1つの値にまとめる高次関数です。クロージャを使って、要素をどのように組み合わせるかを定義します。
例えば、配列内のすべての要素を合計する場合は次のようになります。
let numbers = [1, 2, 3, 4, 5]
let sum = numbers.reduce(0) { $0 + $1 }
print(sum) // 15
このコードでは、reduce()
に初期値0
と、$0 + $1
というクロージャを渡し、配列のすべての要素を合計しています。
高次関数とソートの組み合わせ
高次関数を使うことで、データ操作とソートを組み合わせた複雑な処理をシンプルに実装することができます。例えば、次のように複数の操作を組み合わせて使うことが可能です。
let people = [
Person(name: "Alice", age: 30),
Person(name: "Bob", age: 25),
Person(name: "Charlie", age: 35)
]
let sortedNames = people
.filter { $0.age >= 30 }
.sorted { $0.name < $1.name }
.map { $0.name }
print(sortedNames) // ["Alice", "Charlie"]
この例では、30歳以上の人をフィルタリングし、名前の昇順でソートし、最終的に名前だけを取り出しています。高次関数とクロージャを組み合わせることで、処理を段階的に行い、直感的にデータを操作できます。
高次関数とクロージャを使う利点
- 簡潔なコード: 高次関数とクロージャを使うことで、ループを明示的に書く必要がなく、コードが簡潔で読みやすくなります。
- 柔軟な操作: クロージャを使用することで、任意のカスタムロジックを簡単に指定できます。
- 可読性向上: 高次関数の組み合わせにより、処理の意図が明確になり、コードの可読性が向上します。
このように、Swiftの高次関数とクロージャを活用することで、ソートやデータ変換などの複雑な操作をシンプルに記述し、効率的なコードを実現できます。
クロージャによるソート処理のメリットとデメリット
クロージャを用いたソート処理は、Swiftにおけるデータ操作の強力な手法の一つです。しかし、他のアプローチと同様に、クロージャを使ったソートにもメリットとデメリットがあります。ここでは、それぞれの側面を詳しく見ていきます。
メリット
1. コードの簡潔さ
クロージャを使うことで、ソート処理を非常に簡潔に表現できます。特に、クロージャの省略記法を利用すれば、数行で複雑なソート条件を指定することが可能です。
let numbers = [3, 1, 4, 1, 5, 9]
let sortedNumbers = numbers.sorted { $0 < $1 }
この例のように、数値の昇順ソートをシンプルな1行で記述できる点は、コードの見通しを良くします。
2. 柔軟なカスタムソート
クロージャは、任意の比較ロジックを記述できるため、ソート条件を柔軟にカスタマイズできます。単純な昇順・降順だけでなく、複数のプロパティに基づくソートや、条件分岐を含むソートも簡単に実装できます。
let people = [("Alice", 30), ("Bob", 25), ("Charlie", 35)]
let sortedByAge = people.sorted { $0.1 < $1.1 }
このように、複雑な条件を組み込んだソート処理も容易です。
3. 再利用性の向上
クロージャは無名関数として機能するため、特定のソートロジックを使い回すことができ、コードの再利用性が向上します。必要に応じて、新たなソートロジックを簡単に組み込むことが可能です。
4. 高次関数との相性の良さ
Swiftの高次関数(map()
, filter()
, reduce()
など)とクロージャを組み合わせることで、ソートに加えて他のデータ操作も簡単に行えます。これにより、データ処理を効率よく一貫したスタイルで実装できます。
デメリット
1. 読みやすさが低下する場合がある
クロージャを使いすぎたり、過度に省略したりすると、コードが短すぎて意図が分かりにくくなることがあります。特に、複雑な比較ロジックをクロージャ内に直接書き込むと、後からコードを読む際に理解しづらくなる可能性があります。
let sortedData = data.sorted { $0.value > $1.value && $0.key < $1.key }
このようなコードは、機能は強力ですが、複雑さが増すと可読性が犠牲になることがあります。
2. デバッグが難しいことがある
クロージャの省略記法は簡潔ですが、デバッグが難しくなることがあります。特に、複雑なロジックをクロージャ内にまとめると、問題が発生した際に原因の特定が困難になることがあります。
3. パフォーマンスへの影響
大規模なデータセットをクロージャを使ってソートする際、特にカスタムロジックが複雑な場合は、パフォーマンスが低下することがあります。Swiftの標準的なソートアルゴリズムは効率的ですが、クロージャの中で複雑な計算や処理を繰り返すと、実行速度に影響が出る場合があります。
4. 過剰な抽象化による理解困難
クロージャは柔軟すぎるため、意図が過度に抽象化されてしまうことがあります。特に、初心者やチーム開発においては、クロージャを多用することでコード全体の理解が難しくなるリスクがあります。
まとめ
クロージャを使ったソート処理には、コードの簡潔さや柔軟性という大きなメリットがありますが、可読性やパフォーマンス面でのデメリットも存在します。これらを理解した上で、適切にクロージャを活用することが重要です。データセットや処理の複雑さに応じて、クロージャの使い方を工夫することで、効率的なプログラムを実現できます。
練習問題:クロージャを使ってリストをソートする
ここでは、クロージャを使ってソートを実際に体験できる練習問題を用意しました。以下の問題に取り組むことで、クロージャを使ったソートの理解を深めることができます。
問題 1: 数値リストの降順ソート
次の整数の配列をクロージャを使って降順にソートしてください。
let numbers = [42, 17, 23, 56, 78, 12]
ヒント
sorted
メソッドにクロージャを渡し、数値が大きい順に並び替えるようにしてください。降順の場合、$0 > $1
の形式を使います。
問題 2: 文字列の長さでソート
次に、以下の文字列リストを文字列の長さ順に昇順でソートしてください。
let words = ["apple", "banana", "kiwi", "orange", "grape"]
ヒント
クロージャの中でcount
プロパティを使い、文字列の長さを比較するロジックを記述します。
問題 3: カスタムオブジェクトのソート
以下のBook
構造体があるとします。このリストを出版年(year
)で昇順にソートしてください。
struct Book {
let title: String
let year: Int
}
let books = [
Book(title: "The Catcher in the Rye", year: 1951),
Book(title: "To Kill a Mockingbird", year: 1960),
Book(title: "1984", year: 1949),
Book(title: "The Great Gatsby", year: 1925)
]
ヒント
オブジェクトのyear
プロパティを比較してソートするために、$0.year < $1.year
の形式でクロージャを記述してください。
問題 4: 複数条件のソート
上記のBook
リストを出版年(year
)で昇順にソートし、同じ年に出版された場合はタイトルのアルファベット順で並び替えてください。
ヒント
if
文を使って、年が同じ場合にタイトルを比較するロジックをクロージャに組み込みます。
解答例
次のようなコードで、各問題を解くことができます。
// 問題 1
let sortedNumbers = numbers.sorted { $0 > $1 }
print(sortedNumbers) // [78, 56, 42, 23, 17, 12]
// 問題 2
let sortedWords = words.sorted { $0.count < $1.count }
print(sortedWords) // ["kiwi", "apple", "grape", "orange", "banana"]
// 問題 3
let sortedBooksByYear = books.sorted { $0.year < $1.year }
for book in sortedBooksByYear {
print("\(book.title): \(book.year)")
}
// The Great Gatsby: 1925, 1984: 1949, The Catcher in the Rye: 1951, To Kill a Mockingbird: 1960
// 問題 4
let sortedBooksByYearThenTitle = books.sorted {
if $0.year == $1.year {
return $0.title < $1.title
} else {
return $0.year < $1.year
}
}
for book in sortedBooksByYearThenTitle {
print("\(book.title): \(book.year)")
}
これらの練習問題を解くことで、クロージャの柔軟性と強力さを体感できるでしょう。必要に応じてソート条件を変更し、様々なケースを試してみてください。
まとめ
本記事では、Swiftにおけるクロージャを使ったコンパクトなソートアルゴリズムの実装方法について詳しく解説しました。クロージャを用いることで、コードを簡潔にし、ソート処理を柔軟にカスタマイズできる利点がある一方で、可読性やパフォーマンスの注意点も理解する必要があります。高次関数との組み合わせや、実際のオブジェクトを用いた応用例を通じて、クロージャを活用したソートの強力さを体感できたはずです。これらの知識を基に、実際のプロジェクトに活かしてみてください。
コメント