なぜ Go 言語にコレクションクラスがないんだ!そうだ。 AI に作らせよう。
という訳で、Gemini だけで ArrayList をGo で作らせてみました。
そのときの内容を6回に分けてお伝えしていこうと思います。
- 連載リンク
- 第一回 まずは作ってもらった
- 第二回 制約つきの構造体を追加してみた
- 第三回 Collection で定義されているメソッドを追加してみた
- 第四回 List で定義されているメソッドを追加してみた
- 第五回 Sort もしたい
- 第六回 Geminiとの対話で生まれたコードとまとめ (このページ)
第六回 Geminiとの対話で生まれたコードとまとめ
ここまで読んでいただきありがとうございました。
ある程度必要なメソッドがそろいました。
今後の展開として
Goのライブラリに合わせてSizeやRemoveをLenやDeleteに名前を変更したほうが馴染みやすいかもしれません。
string制約の構造体も追加してJoinメソッドを追加するのもいいかもしれません。
また、各メソッドの戻り値をもう少し見直して削除したり変更するのもいいかもしれません。
6回に分けてお伝えしてきましたが、
実は、これらすべてのコードはGeminiのページをブラウザで開いてほんの十数分で作成することができました。
今後、もっとAIを活用していけば、
開発にかかる時間も短縮できるかもしれないなと感じました。
まとめ
- 最初に曲げられない条件を言う
- 今回は「go言語でjavaのArrayListと完全に互換性のある構造体を作ってください」と伝えました。
- 「完全に互換性のある」というのは無理要求なんですが、それでもAIは何とかしようとしてくれました。
- 多少の主語がなくても大抵の場合なんとかなる
- ゆずれないものが固まっていれば、ある程度適切に調整してくれます。
- 間違いを指摘する
- AIも信念をもって実装してくれます(実装がそうなった理由を教えてくれます)が、間違っている(人間の思想と異なる)こともあります。
- 変更してほしい理由と共に伝えると、その後の作業でそれを守ってくれるようになります。
- 足らないものを追加する
- 最初はひとつずつメソッドを追加していましたが、まとめて追加するよう指示したほうが効率的です。
- 解説もしっかりしてくれる
- 出来上がったコードひとつひとつに対して検討し、改善の指示をすれば、もっと使い勝手の良い、チューニングされたコードにしてくれるでしょう。
- テスト、ベンチマークや使い方のサンプルも
- 何もお願いしていないのに、使い方のサンプルも一緒に出てきたのには驚かされました。
- 今回はテストやベンチマークまで作成してもらっていませんが、お願いすればおそらくやってくれるはずです。
- 既存のコードをアップロードし、それに対するテストだけを作ってもらうといった活用方法も考えられます。
コード全体
質問
最後に今まで作成したコード全体を見せてもらいます。
コードをもう一度見せてください
回答
はい、承知いたしました。これまでの変更を全て反映した
MyArrayList、MyComparableArrayList、およびMyOrderedArrayListの最新のコードを以下に示します。package main import ( "cmp" "errors" "fmt" "iter" "slices" ) // MyArrayList は基本的な動的配列機能を提供します。 // 任意の型の要素を格納できます。 type MyArrayList[T any] struct { elements []T } // NewMyArrayList は新しいMyArrayListを初期化して返します。 func NewMyArrayList[T any]() *MyArrayList[T] { return &MyArrayList[T]{ elements: make([]T, 0), } } // Add はリストの末尾に指定された要素を追加します。 func (list *MyArrayList[T]) Add(element T) { list.elements = append(list.elements, element) } // AddAll は指定されたコレクション (スライス) のすべての要素をこのリストの末尾に追加します。 func (list *MyArrayList[T]) AddAll(elements []T) { list.elements = append(list.elements, elements...) } // Get はリスト内の指定された位置にある要素を返します。 // インデックスが範囲外の場合はエラーを返します。 func (list *MyArrayList[T]) Get(index int) (T, error) { if index < 0 || index >= len(list.elements) { var zeroValue T return zeroValue, errors.New("IndexOutOfBoundsException: Index " + fmt.Sprint(index) + " out of bounds for length " + fmt.Sprint(len(list.elements))) } return list.elements[index], nil } // Set はリスト内の指定された位置にある要素を指定された要素で置き換えます。 // 置き換えられた(古い)要素を返します。 // インデックスが範囲外の場合はエラーを返します。 func (list *MyArrayList[T]) Set(index int, element T) (T, error) { if index < 0 || index >= len(list.elements) { var zeroValue T return zeroValue, errors.New("IndexOutOfBoundsException: Index " + fmt.Sprint(index) + " out of bounds for length " + fmt.Sprint(len(list.elements))) } oldElement := list.elements[index] list.elements[index] = element return oldElement, nil } // Insert はリスト内の指定された位置に要素を挿入します。 // その位置以降の要素は、すべて1つずつ右にシフトされます。 // インデックスが範囲外の場合はエラーを返します。 func (list *MyArrayList[T]) Insert(index int, element T) error { if index < 0 || index > len(list.elements) { return errors.New("IndexOutOfBoundsException: Index " + fmt.Sprint(index) + " out of bounds for length " + fmt.Sprint(len(list.elements))) } list.elements = append(list.elements, *new(T)) copy(list.elements[index+1:], list.elements[index:]) list.elements[index] = element return nil } // RemoveAt はリスト内の指定された位置にある要素を削除します。 // 削除された要素を返し、リストのサイズを小さくします。 // インデックスが範囲外の場合はエラーを返します。 func (list *MyArrayList[T]) RemoveAt(index int) (T, error) { if index < 0 || index >= len(list.elements) { var zeroValue T return zeroValue, errors.New("IndexOutOfBoundsException: Index " + fmt.Sprint(index) + " out of bounds for length " + fmt.Sprint(len(list.elements))) } removedElement := list.elements[index] list.elements = append(list.elements[:index], list.elements[index+1:]...) return removedElement, nil } // Size はリスト内の要素の数を返します。 func (list *MyArrayList[T]) Size() int { return len(list.elements) } // IsEmpty はリストに要素が含まれていない場合にtrueを返します。 func (list *MyArrayList[T]) IsEmpty() bool { return len(list.elements) == 0 } // Clear はリストからすべての要素を削除します。 func (list *MyArrayList[T]) Clear() { list.elements = make([]T, 0) } // String はMyArrayListの文字列表現を返します。 func (list *MyArrayList[T]) String() string { return fmt.Sprintf("%v", list.elements) } // Slice はMyArrayListに含まれるすべての要素を含む新しいスライスを返します。 // これは内部のスライスが外部から変更されるのを防ぐために、クローンを作成します。 func (list *MyArrayList[T]) Slice() []T { clonedSlice := make([]T, len(list.elements), cap(list.elements)) copy(clonedSlice, list.elements) return clonedSlice } // Iterator はリストの要素を順に返す iter.Seq[T] を返します。 // Go 1.22 以降の for range ループと組み合わせて使用できます。 func (list *MyArrayList[T]) Iterator() iter.Seq[T] { return func(yield func(T) bool) { for _, element := range list.elements { if !yield(element) { return } } } } // RemoveIf は指定された述語 (predicate) がtrueを返すすべての要素をこのリストから削除します。 // 要素が削除された場合はtrueを返します。 func (list *MyArrayList[T]) RemoveIf(predicate func(element T) bool) bool { modified := false newElements := make([]T, 0, len(list.elements)) for _, element := range list.elements { if !predicate(element) { newElements = append(newElements, element) } else { modified = true } } list.elements = newElements return modified } // ReplaceAll はリストの各要素を、指定された関数 (operator) を適用した結果で置き換えます。 // リストが変更された場合はtrueを返します。 func (list *MyArrayList[T]) ReplaceAll(operator func(element T) T) bool { modified := false for i, element := range list.elements { newValue := operator(element) if fmt.Sprintf("%v", newValue) != fmt.Sprintf("%v", element) { list.elements[i] = newValue modified = true } } return modified } // SubList はこのリストの指定された fromIndex (含む) から toIndex (含まない) までの部分のビューを返します。 // 返されるリストは元のリストの要素への参照を含みます。 // fromIndex または toIndex が範囲外の場合、または fromIndex > toIndex の場合はエラーを返します。 func (list *MyArrayList[T]) SubList(fromIndex, toIndex int) (*MyArrayList[T], error) { if fromIndex < 0 || fromIndex > len(list.elements) { return nil, errors.New(fmt.Sprintf("IndexOutOfBoundsException: fromIndex %d out of bounds for length %d", fromIndex, len(list.elements))) } if toIndex < 0 || toIndex > len(list.elements) { return nil, errors.New(fmt.Sprintf("IndexOutOfBoundsException: toIndex %d out of bounds for length %d", toIndex, len(list.elements))) } if fromIndex > toIndex { return nil, errors.New(fmt.Sprintf("IllegalArgumentException: fromIndex (%d) > toIndex (%d)", fromIndex, toIndex)) } subSlice := list.elements[fromIndex:toIndex] newList := NewMyArrayList[T]() newList.AddAll(subSlice) return newList, nil } // AddFirst はリストの先頭に指定された要素を挿入します。 func (list *MyArrayList[T]) AddFirst(element T) { list.Insert(0, element) } // AddLast はリストの末尾に指定された要素を追加します。 func (list *MyArrayList[T]) AddLast(element T) { list.Add(element) } // GetFirst はリストの最初の要素を返します。 // リストが空の場合はエラーを返します。 func (list *MyArrayList[T]) GetFirst() (T, error) { if list.IsEmpty() { var zeroValue T return zeroValue, errors.New("NoSuchElementException: List is empty") } return list.elements[0], nil } // GetLast はリストの最後の要素を返します。 // リストが空の場合はエラーを返します。 func (list *MyArrayList[T]) GetLast() (T, error) { if list.IsEmpty() { var zeroValue T return zeroValue, errors.New("NoSuchElementException: List is empty") } return list.elements[len(list.elements)-1], nil } // RemoveFirst はリストの最初の要素を削除して返します。 // リストが空の場合はエラーを返します。 func (list *MyArrayList[T]) RemoveFirst() (T, error) { if list.IsEmpty() { var zeroValue T return zeroValue, errors.New("NoSuchElementException: List is empty") } return list.RemoveAt(0) } // RemoveLast はリストの最後の要素を削除して返します。 // リストが空の場合はエラーを返します。 func (list *MyArrayList[T]) RemoveLast() (T, error) { if list.IsEmpty() { var zeroValue T return zeroValue, errors.New("NoSuchElementException: List is empty") } return list.RemoveAt(len(list.elements) - 1) } // Reversed はこのリストの要素を逆順にした新しいMyArrayListを返します。 // 元のリストは変更されません。 func (list *MyArrayList[T]) Reversed() *MyArrayList[T] { reversedList := NewMyArrayList[T]() for i := list.Size() - 1; i >= 0; i-- { element, _ := list.Get(i) // Getはエラーを返す可能性があるが、ここではインデックスが有効なので無視 reversedList.Add(element) } return reversedList } --- // MyComparableArrayList は比較可能な要素に特化した動的配列です。 // MyArrayListを埋め込むことで、そのメソッドを継承します。 type MyComparableArrayList[T comparable] struct { MyArrayList[T] // MyArrayListを埋め込む } // NewMyComparableArrayList は新しいMyComparableArrayListを初期化して返します。 func NewMyComparableArrayList[T comparable]() *MyComparableArrayList[T] { return &MyComparableArrayList[T]{ MyArrayList: *NewMyArrayList[T](), // 埋め込んだMyArrayListを初期化 } } // Contains は指定された要素がリストに含まれている場合にtrueを返します。 // Tがcomparableなので、要素の比較が可能です。 func (list *MyComparableArrayList[T]) Contains(element T) bool { for _, item := range list.elements { if item == element { return true } } return false } // IndexOf はリスト内で指定された要素が最初に出現するインデックスを返します。 // 要素が見つからない場合は -1 を返します。 func (list *MyComparableArrayList[T]) IndexOf(element T) int { for i, item := range list.elements { if item == element { return i } } return -1 } // LastIndexOf はリスト内で指定された要素が最後に出現するインデックスを返します。 // 要素が見つからない場合は -1 を返します。 func (list *MyComparableArrayList[T]) LastIndexOf(element T) int { for i := len(list.elements) - 1; i >= 0; i-- { if list.elements[i] == element { return i } } return -1 } // ContainsAll は指定されたコレクション (スライス) のすべての要素がこのリストに含まれている場合にtrueを返します。 func (list *MyComparableArrayList[T]) ContainsAll(elements []T) bool { for _, targetElement := range elements { if !list.Contains(targetElement) { return false } } return true } // Remove はリスト内で最初に見つかった指定された要素を削除します。 // 要素が削除された場合はtrueを返します。 func (list *MyComparableArrayList[T]) Remove(element T) bool { for i, item := range list.elements { if item == element { list.RemoveAt(i) return true } } return false } // RemoveAll は指定されたコレクション (スライス) に含まれるすべての要素をこのリストから削除します。 // 要素が削除された場合はtrueを返します。 func (list *MyComparableArrayList[T]) RemoveAll(elementsToRemove []T) bool { modified := false newElements := make([]T, 0, len(list.elements)) removeMap := make(map[T]bool) for _, val := range elementsToRemove { removeMap[val] = true } for _, element := range list.elements { if _, found := removeMap[element]; !found { newElements = append(newElements, element) } else { modified = true } } list.elements = newElements return modified } // RetainAll は指定されたコレクション (スライス) に含まれる要素のみをこのリストに残し、 // それ以外の要素をすべて削除します。リストが変更された場合はtrueを返します。 func (list *MyComparableArrayList[T]) RetainAll(elementsToRetain []T) bool { modified := false newElements := make([]T, 0, len(list.elements)) retainMap := make(map[T]bool) for _, val := range elementsToRetain { retainMap[val] = true } for _, element := range list.elements { if _, found := retainMap[element]; found { newElements = append(newElements, element) } else { modified = true } } if modified || len(newElements) != len(list.elements) { list.elements = newElements return true } return false } // Equals はこのリストが指定された別のMyComparableArrayListと等しい場合にtrueを返します。 // 2つのリストが同じ要素を同じ順序で含む場合に等しいと見なされます。 func (list *MyComparableArrayList[T]) Equals(other *MyComparableArrayList[T]) bool { if list == other { return true } if list == nil || other == nil { return false } return slices.Equal(list.elements, other.elements) } --- // 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を初期化 } } // Sort はリストの要素を自然順序でソートします。 func (list *MyOrderedArrayList[T]) Sort() { slices.Sort(list.elements) } func main() { fmt.Println("--- MyArrayList の使用例 ---") myList := NewMyArrayList[string]() myList.AddAll([]string{"a", "b", "c", "d", "e"}) fmt.Println("Original List:", myList) fmt.Println("\n--- AddFirst / AddLast のテスト ---") myList.AddFirst("X") fmt.Println("After AddFirst('X'):", myList) myList.AddLast("Y") fmt.Println("After AddLast('Y'):", myList) fmt.Println("\n--- GetFirst / GetLast のテスト ---") first, err := myList.GetFirst() if err == nil { fmt.Println("First element:", first) } last, err := myList.GetLast() if err == nil { fmt.Println("Last element:", last) } emptyList := NewMyArrayList[int]() _, err = emptyList.GetFirst() if err != nil { fmt.Println("GetFirst on empty list error:", err) } fmt.Println("\n--- RemoveFirst / RemoveLast のテスト ---") removedFirst, err := myList.RemoveFirst() if err == nil { fmt.Println("Removed first:", removedFirst, "New List:", myList) } removedLast, err := myList.RemoveLast() if err == nil { fmt.Println("Removed last:", removedLast, "New List:", myList) } _, err = emptyList.RemoveFirst() if err != nil { fmt.Println("RemoveFirst on empty list error:", err) } fmt.Println("\n--- SubList のテスト ---") sub, err := myList.SubList(1, 4) // インデックス1から4まで (b, c, d) if err == nil { fmt.Println("SubList (1,4):", sub) } else { fmt.Println("SubList error:", err) } sub2, err := myList.SubList(0, 0) // 空のサブリスト if err == nil { fmt.Println("SubList (0,0):", sub2) } _, err = myList.SubList(0, 10) // 範囲外 if err != nil { fmt.Println("SubList error (out of bounds):", err) } _, err = myList.SubList(3, 1) // fromIndex > toIndex if err != nil { fmt.Println("SubList error (from > to):", err) } fmt.Println("\n--- Reversed のテスト ---") original := NewMyArrayList[int]() original.AddAll([]int{1, 2, 3, 4, 5}) fmt.Println("Original List for Reversed:", original) reversed := original.Reversed() fmt.Println("Reversed List:", reversed) fmt.Println("Original List (unchanged):", original) emptyReversed := NewMyArrayList[string]().Reversed() fmt.Println("Reversed empty list:", emptyReversed) fmt.Println("\n--- MyComparableArrayList の使用例 ---") stringListC := NewMyComparableArrayList[string]() stringListC.AddAll([]string{"apple", "banana", "cherry", "date", "banana", "fig", "banana"}) fmt.Println("List (Comparable):", stringListC) fmt.Println("\n--- IndexOf() / LastIndexOf() のテスト ---") idxOfBanana := stringListC.IndexOf("banana") fmt.Printf("Index of 'banana': %d\n", idxOfBanana) lastIdxOfBanana := stringListC.LastIndexOf("banana") fmt.Printf("Last index of 'banana': %d\n", lastIdxOfBanana) idxOfGrape := stringListC.IndexOf("grape") fmt.Printf("Index of 'grape': %d\n", idxOfGrape) fmt.Println("\n--- MyOrderedArrayList の使用例 ---") orderedList := NewMyOrderedArrayList[int]() orderedList.AddAll([]int{50, 20, 80, 30, 10}) fmt.Println("Original OrderedList:", orderedList) fmt.Println("\n--- Sort() のテスト ---") orderedList.Sort() fmt.Println("Sorted OrderedList:", orderedList) fmt.Println("\n--- MyOrderedArrayList でのSet(), Insert(), AddFirst() など(埋め込みによる) ---") _, err = orderedList.Set(2, 70) if err == nil { fmt.Println("After Set(2, 70):", orderedList) } err = orderedList.Insert(1, 15) if err == nil { fmt.Println("After Insert(1, 15):", orderedList) } orderedList.AddFirst(5) fmt.Println("After AddFirst(5):", orderedList) }