なぜ Go 言語にコレクションクラスがないんだ!そうだ。 AI に作らせよう。
という訳で、Gemini だけで ArrayList をGo で作らせてみました。
そのときの内容を6回に分けてお伝えしていこうと思います。
- 連載リンク
- 第一回 まずは作ってもらった
- 第二回 制約つきの構造体を追加してみた
- 第三回 Collection で定義されているメソッドを追加してみた
- 第四回 List で定義されているメソッドを追加してみた
- 第五回 Sort もしたい (このページ)
- 第六回 Geminiとの対話で生まれたコードとまとめ
第五回 Sort もしたい
分割して掲載する都合上、実際に質問を投げた順番と掲載の順番とを少々入れ替えてあります。
Geminiからは、毎回コード全体が出力されましたが、
記事が長くなりすぎてしまうため、変更があった部分だけをお送りいたします。
並べ替えを可能にするには制約 cmp.Orderd
を満たしている必要があり、
今回はもうひとつその構造体を追加してもらいます。
MyOrderedArrayList 構造体
質問
新たに MyComparableArrayList を埋め込んだ構造体MyOrderedArrayListを作ってください
Tの制約は cmp.Orderd です
おっと。Ordered
タイポした。
回答
承知いたしました。
MyComparableArrayList
を埋め込み、さらに要素の制約をcmp.Ordered
(順序付け可能)にすることで、並べ替えなどの操作が可能な新しいリスト型、MyOrderedArrayList
を作成します。これにより、
MyOrderedArrayList
は以下のような特徴を持ちます。
MyArrayList
とMyComparableArrayList
のすべてのメソッドを継承します。- 要素が
int
,float64
,string
などの順序付け可能な型に限定されます。- 将来的にソートなどの順序に関連するメソッドを追加できるようになります。
MyOrderedArrayList
の実装以下に、
MyOrderedArrayList
構造体の定義と、その初期化関数、および使用例を示します。package main import ( "cmp" // cmp.Ordered 制約のためにインポート "fmt" ) // MyArrayList は基本的な動的配列機能を提供します。 // 任意の型の要素を格納できます。 type MyArrayList[T any] struct { elements []T } // MyComparableArrayList は比較可能な要素に特化した動的配列です。 // MyArrayListを埋め込むことで、そのメソッドを継承します。 type MyComparableArrayList[T comparable] struct { MyArrayList[T] // MyArrayListを埋め込む } // MyOrderedArrayList は順序付け可能な要素に特化した動的配列です。 // MyComparableArrayListを埋め込むことで、そのメソッドを継承します。 // T は cmp.Ordered (比較演算子 <, <=, ==, !=, >=, > が使える型) の制約を受けます。 type MyOrderedArrayList[T cmp.Ordered] struct { MyComparableArrayList[T] // MyComparableArrayListを埋め込む } // NewMyOrderedArrayList は新しいMyOrderedArrayListを初期化して返します。 func NewMyOrderedArrayList[T cmp.Ordered]() *MyOrderedArrayList[T] { return &MyOrderedArrayList[T]{ MyComparableArrayList: *NewMyComparableArrayList[T](), // 埋め込んだMyComparableArrayListを初期化 } } func main() { fmt.Println("--- MyOrderedArrayList の使用例 ---") intList := NewMyOrderedArrayList[int]() intList.Add(50) intList.Add(20) intList.Add(80) intList.Add(30) fmt.Println("Original IntList:", intList) // Original IntList: [50 20 80 30] // MyArrayList と MyComparableArrayList から継承したメソッドの使用 fmt.Println("\n--- 継承メソッドのテスト ---") fmt.Println("Size:", intList.Size()) // Size: 4 fmt.Println("Contains 20?", intList.Contains(20)) // Contains 20? true fmt.Println("Remove 80?", intList.Remove(80)) // Remove 80? true fmt.Println("List after Remove 80:", intList) // List after Remove 80: [50 20 30] otherNums := []int{10, 30, 60} fmt.Printf("Contains all %v? %t\n", otherNums, intList.ContainsAll(otherNums)) // Contains all [10 30 60]? false (10, 60がない) intList.AddAll([]int{10, 60}) fmt.Println("List after AddAll:", intList) // List after AddAll: [50 20 30 10 60] // RemoveIf も使用可能 (MyArrayListから継承) removedSmall := intList.RemoveIf(func(e int) bool { return e < 30 }) fmt.Println("Removed numbers < 30?", removedSmall) // Removed numbers < 30? true fmt.Println("List after RemoveIf (<30):", intList) // List after RemoveIf (<30): [50 30 60] // RetainAll も使用可能 (MyComparableArrayListから継承) toRetain := []int{50, 30, 90} retained := intList.RetainAll(toRetain) fmt.Printf("Retained %v in list: %t\n", toRetain, retained) // Retained [50 30 90] in list: true fmt.Println("List after RetainAll:", intList) // List after RetainAll: [50 30] // Equals も使用可能 (MyComparableArrayListから継承) anotherList := NewMyOrderedArrayList[int]() anotherList.AddAll([]int{50, 30}) fmt.Println("Another List:", anotherList) fmt.Printf("IntList equals Another List? %t\n", intList.Equals(anotherList)) // IntList equals Another List? true // ReplaceAll も使用可能 (MyArrayListから継承) intList.ReplaceAll(func(e int) int { return e * 10 }) fmt.Println("List after ReplaceAll (*10):", intList) // List after ReplaceAll (*10): [500 300] fmt.Println("\n--- String型のMyOrderedArrayList ---") stringList := NewMyOrderedArrayList[string]() stringList.AddAll([]string{"zebra", "apple", "cat", "dog"}) fmt.Println("Original StringList:", stringList) // Original StringList: [zebra apple cat dog] // 例えば、ソートメソッドをここに追加できる // sort.Slice(stringList.elements, func(i, j int) bool { // return stringList.elements[i] < stringList.elements[j] // }) // fmt.Println("Sorted StringList:", stringList) // Sorted StringList: [apple cat dog zebra] }
MyOrderedArrayList
の解説
type MyOrderedArrayList[T cmp.Ordered] struct { ... }
:
- ジェネリクス型パラメータTに
cmp.Ordered
という制約が適用されています。cmp.Ordered
はGo 1.21で導入されたプリデクレア型(predeclared type
)で、比較演算子(<
,<=
,==
,!=
,>=
,>
)が定義されているすべての型(整数型、浮動小数点型、文字列型など)を表します。これにより、これらの型を安全に順序付けて比較できます。MyComparableArrayList[T]
を埋め込み:MyOrderedArrayList
はMyComparableArrayList[T]
をフィールドとして埋め込んでいます。これにより、MyArrayList
とMyComparableArrayList
のすべてのメソッドが自動的にMyOrderedArrayList
でも利用可能になります。これはGoにおける「コンポジションによる継承」の強力な例です。NewMyOrderedArrayList
関数:
MyOrderedArrayList
のインスタンスを初期化するためのコンストラクタ関数です。- 内部で埋め込んだ
MyComparableArrayList
も適切にNewMyComparableArrayList[T]()
を呼び出して初期化しています。利用例とメリット
- 継承されたメソッド:
Add
,Get
,RemoveAt
,Contains
,Equals
,RemoveIf
,ReplaceAll
,RetainAll
など、これまでのすべてのメソッドがMyOrderedArrayList
のインスタンスでそのまま利用できます。- 型安全性:
MyOrderedArrayList[T]
を作成する際に、T
がcmp.Ordered
ではない型(例:struct
や[]byte
)を指定しようとすると、コンパイルエラーになります。これにより、順序付けが必要な操作に対して安全な型保証が提供されます。- 将来の拡張性:
cmp.Ordered
制約があるため、Sort()
やMin()
,Max()
などの、要素の順序に依存するメソッドをMyOrderedArrayList
に容易に追加できるようになります。これで、Goの型システムとジェネリクスを段階的に活用し、より特定の用途に特化したリスト構造を構築できました。
感想
タイポしたのに何も言わずに修正してくれた。
やさしい。
Sort メソッド
質問
MyOrderedArrayList
を作成したときの解説でも言われていたSort
メソッドを追加してみます。
sortを追加してください
回答
承知いたしました。
MyOrderedArrayList
に、要素をソートするSort()
メソッドを追加します。
Sort()
メソッドは要素の順序に依存するため、cmp.Ordered
制約を持つMyOrderedArrayList
に実装するのが適切です。Goの標準ライブラリのslices.Sort
(Go 1.21以降)を利用することで、効率的かつ簡潔にソート機能を実装できます。
MyOrderedArrayList
にSort()
メソッドを追加以下に、
Sort()
メソッドを追加したMyOrderedArrayList
の完全なコードを示します。package main import ( "cmp" "fmt" "slices" // slices.Sortのためにインポート (Go 1.21+) ) // MyArrayList は基本的な動的配列機能を提供します。 // 任意の型の要素を格納できます。 type MyArrayList[T any] struct { elements []T } // MyComparableArrayList は比較可能な要素に特化した動的配列です。 // MyArrayListを埋め込むことで、そのメソッドを継承します。 type MyComparableArrayList[T comparable] struct { MyArrayList[T] // MyArrayListを埋め込む } // MyOrderedArrayList は順序付け可能な要素に特化した動的配列です。 // MyComparableArrayListを埋め込むことで、そのメソッドを継承します。 // T は cmp.Ordered (比較演算子 <, <=, ==, !=, >=, > が使える型) の制約を受けます。 type MyOrderedArrayList[T cmp.Ordered] struct { MyComparableArrayList[T] // MyComparableArrayListを埋め込む } // Sort はリストの要素を自然順序でソートします。 func (list *MyOrderedArrayList[T]) Sort() { slices.Sort(list.elements) } func main() { fmt.Println("--- MyOrderedArrayList の使用例 ---") intList := NewMyOrderedArrayList[int]() intList.Add(50) intList.Add(20) intList.Add(80) intList.Add(30) fmt.Println("Original IntList:", intList) // Original IntList: [50 20 80 30] fmt.Println("\n--- Sort() のテスト (int) ---") intList.Sort() fmt.Println("Sorted IntList:", intList) // Sorted IntList: [20 30 50 80] stringList := NewMyOrderedArrayList[string]() stringList.AddAll([]string{"zebra", "apple", "cat", "dog", "Banana"}) // 'B'anana と 'a'pple の順序もテスト fmt.Println("\nOriginal StringList:", stringList) // Original StringList: [zebra apple cat dog Banana] fmt.Println("\n--- Sort() のテスト (string) ---") stringList.Sort() // Goのデフォルトの文字列比較はASCII/UTF-8順なので、大文字が小文字より前に来る fmt.Println("Sorted StringList:", stringList) // Sorted StringList: [Banana apple cat dog zebra] floatList := NewMyOrderedArrayList[float64]() floatList.AddAll([]float64{3.14, 1.618, 2.718, 0.577}) fmt.Println("\nOriginal FloatList:", floatList) // Original FloatList: [3.14 1.618 2.718 0.577] fmt.Println("\n--- Sort() のテスト (float64) ---") floatList.Sort() fmt.Println("Sorted FloatList:", floatList) // Sorted FloatList: [0.577 1.618 2.718 3.14] // 継承されたEqualsメソッドのlices.Equalへの変更をテスト list1 := NewMyComparableArrayList[int]() list1.AddAll([]int{1, 2, 3}) list2 := NewMyComparableArrayList[int]() list2.AddAll([]int{1, 2, 3}) fmt.Printf("\nList1 equals List2? %t (using slices.Equal)\n", list1.Equals(list2)) // true }
Sort() メソッドの解説
func (list *MyOrderedArrayList[T]) Sort()
:
- このメソッドは引数を取らず、リストの要素を インプレース(in-place) でソートします。つまり、既存のリストの順序が変更されます。
slices.Sort(list.elements)
:
- Go 1.21 で導入されたslicesパッケージの](https://www.google.com/search?q=https://pkg.go.dev/slices)%E3%83%91%E3%83%83%E3%82%B1%E3%83%BC%E3%82%B8%E3%81%AE)Sort関数を利用しています。
slices.Sort
は、cmp.Ordered
制約を満たす型を要素とするスライスを直接ソートできます。内部的に効率的なソートアルゴリズム(通常はクイックソートやヒープソートの組み合わせ)を使用します。MyOrderedArrayList
のelements
フィールドはcmp.Ordered
の型であるため、この関数を直接適用できます。
Sort()
メソッドの配置
Sort()
メソッドは要素の順序に直接アクセスして並べ替えを行うため、cmp.Ordered
制約を持つMyOrderedArrayList
に実装するのが最も適切です。その他の更新
MyComparableArrayList
のEquals()
メソッドの内部実装を、より簡潔でGoの標準ライブラリに沿ったslices.Equal
を使用するように変更しました](https://www.google.com/search?q=https://pkg.go.dev/slices%23Equal)%E3%82%92%E4%BD%BF%E7%94%A8%E3%81%99%E3%82%8B%E3%82%88%E3%81%86%E3%81%AB%E5%A4%89%E6%9B%B4%E3%81%97%E3%81%BE%E3%81%97%E3%81%9F)。これにより、`MyComparableArrayListのEquals`は、長さが同じで要素が同じ順序で現れる場合に`true`を返すという定義により厳密に合致するようになりました。これで、順序付け可能な要素を持つリストに対してソート機能が追加されました。
感想
お、リンク張ってくれたマークダウンがずれてる。
まとめ
ここまできたら、MyOrderedArrayList
を作成したときの解説にも合った通り、Min
, Max
なども追加していくこともできるでしょう。
次回で最終回、いままでの振り返りになります。