なぜ Go 言語にコレクションクラスがないんだ!そうだ。 AI に作らせよう。
という訳で、Gemini だけで ArrayList をGo で作らせてみました。
そのときの内容を6回に分けてお伝えしていこうと思います。
- 連載リンク
- 第一回 まずは作ってもらった
- 第二回 制約つきの構造体を追加してみた
- 第三回 Collection で定義されているメソッドを追加してみた
- 第四回 List で定義されているメソッドを追加してみた (このページ)
- 第五回 Sort もしたい
- 第六回 Geminiとの対話で生まれたコードとまとめ
第四回 List で定義されているメソッドを追加してみた
分割して掲載する都合上、実際に質問を投げた順番と掲載の順番とを少々入れ替えてあります。
Geminiからは、毎回コード全体が出力されましたが、
記事が長くなりすぎてしまうため、変更があった部分だけをお送りいたします。
この回は淡々とメソッドを追加してもらっています。
結構長くなってしまったので、一部の回答を折り畳んでいます。
今回は、
について注目していただけたらと思います。
Set メソッド
質問
ここまで作ってもらったのに Set メソッドがない。
setメソッドも追加してください
回答
はい、承知いたしました。`set` メソッドを追加します。
このメソッドは、指定されたインデックス位置の要素を新しい要素で置き換え、元の要素を返します。この
setメソッドは要素の比較を必要としないため、最も汎用的なMyArrayListに追加するのが適切です。
MyArrayListにSet()メソッドを追加以下に、
Set()メソッドを追加したMyArrayListの完全なコードを示します。package main import ( "cmp" "errors" "fmt" ) // MyArrayList は基本的な動的配列機能を提供します。 // 任意の型の要素を格納できます。 type MyArrayList[T any] struct { elements []T } // 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 } // MyComparableArrayList は比較可能な要素に特化した動的配列です。 // MyArrayListを埋め込むことで、そのメソッドを継承します。 type MyComparableArrayList[T comparable] struct { MyArrayList[T] // MyArrayListを埋め込む } func main() { fmt.Println("--- MyArrayList の使用例 ---") stringList := NewMyArrayList[string]() stringList.AddAll([]string{"apple", "banana", "cherry", "date"}) fmt.Println("Original List:", stringList) // Original List: [apple banana cherry date] fmt.Println("\n--- Set() のテスト ---") // インデックス1の要素を "grape" で置き換え oldVal, err := stringList.Set(1, "grape") if err == nil { fmt.Printf("Replaced index 1: old value = %s, new list = %v\n", oldVal, stringList) // Replaced index 1: old value = banana, new list = [apple grape cherry date] } // インデックスが範囲外の場合 _, err = stringList.Set(10, "kiwi") if err != nil { fmt.Println("Error setting index 10:", err) // Error setting index 10: IndexOutOfBoundsException: Index 10 out of bounds for length 4 } type Item struct { ID int Name string } itemList := NewMyArrayList[Item]() itemList.AddAll([]Item{{ID: 1, Name: "Pen"}, {ID: 2, Name: "Book"}}) fmt.Println("\nOriginal Item List:", itemList) // Original Item List: [{1 Pen} {2 Book}] oldItem, err := itemList.Set(0, Item{ID: 10, Name: "Pencil"}) if err == nil { fmt.Printf("Replaced index 0: old value = %v, new list = %v\n", oldItem, itemList) // Replaced index 0: old value = {1 Pen}, new list = [{10 Pencil} {2 Book}] } fmt.Println("\n--- MyComparableArrayList の使用例 ---") compList := NewMyComparableArrayList[int]() compList.AddAll([]int{10, 20, 30, 40}) fmt.Println("Original CompList:", compList) // Original CompList: [10 20 30 40] // 埋め込みにより MyComparableArrayList でも Set が利用可能 oldInt, err := compList.Set(2, 99) if err == nil { fmt.Printf("Replaced index 2: old value = %d, new list = %v\n", oldInt, compList) // Replaced index 2: old value = 30, new list = [10 20 99 40] } }
Set()メソッドの解説
func (list *MyArrayList[T]) Set(index int, element T) (T, error):
- このメソッドは、指定された
indexにある要素を新しいelementで置き換えます。- 置き換えられた(元の)要素を
T型で返します。indexがリストの有効な範囲外である場合、エラーを返します。- 実装ロジック:
- インデックスの範囲チェック:
if index < 0 || index >= len(list.elements)で、インデックスが有効な範囲内にあるかを確認します。範囲外であればエラーを返します。- 元の要素の保存:
oldElement := list.elements[index]で、置き換えられる前の要素を変数oldElementに保存します。- 要素の置き換え:
list.elements[index] = elementで、指定されたインデックスに新しい要素を代入します。- 元の要素の返却:
return oldElement, nilで、保存しておいた元の要素とnilエラーを返します。
Set()メソッドの配置
Set()メソッドは要素の値を直接変更するだけで、要素同士の比較(==や<)やマップの使用を必要としません。そのため、最も汎用的な型であるT anyを扱うMyArrayListに実装するのが適切です。これにより、MyComparableArrayListも埋め込みを通じてこのSet()メソッドを利用できるようになります。これで、インデックス指定による要素の取得 (
Get) と置き換え (Set) の両方が可能になりました。
感想
Javaの仕様にあわせて変更前の値を返してくれている。
Goの仕様にあわせてエラーも返してくれている。
必要ない気もするけど今回はJavaのArrayListを作ってとお願いした訳だから放置。
Insert メソッド
質問
Javaだと末尾に追加するadd(E element)とadd(int index, E element)が同じaddでオーバーロードされているけど、
Goだと同じ名前のメソッドは作ることができない。
なので、インデックスを指定する方をInsertとすることにします。
add(int, T)メソッドをInsertとして追加してください
回答
はい、承知いたしました。Javaの `ArrayList` における `add(int index, E element)` メソッドと同様の機能を持つ `Insert()` メソッドを追加します。
この
Insert()メソッドは、指定されたインデックスに要素を挿入し、その位置以降の既存の要素を1つずつ後ろにずらします。要素の比較を必要としないため、最も汎用的なMyArrayListに追加するのが適切です。
MyArrayListにInsert()メソッドを追加以下に、
Insert()メソッドを追加したMyArrayListの完全なコードを示します。package main import ( "cmp" "errors" "fmt" ) // MyArrayList は基本的な動的配列機能を提供します。 // 任意の型の要素を格納できます。 type MyArrayList[T any] struct { elements []T } // 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)) // ダミー要素を追加して容量を確保 // 挿入位置から末尾までを1つずつ右にシフト copy(list.elements[index+1:], list.elements[index:]) // 指定された位置に新しい要素を挿入 list.elements[index] = element return nil } // MyComparableArrayList は比較可能な要素に特化した動的配列です。 // MyArrayListを埋め込むことで、そのメソッドを継承します。 type MyComparableArrayList[T comparable] struct { MyArrayList[T] // MyArrayListを埋め込む } func main() { fmt.Println("--- MyArrayList の使用例 ---") stringList := NewMyArrayList[string]() stringList.AddAll([]string{"apple", "banana", "date"}) fmt.Println("Original List:", stringList) // Original List: [apple banana date] fmt.Println("\n--- Insert() のテスト ---") // インデックス1に "cherry" を挿入 err := stringList.Insert(1, "cherry") if err == nil { fmt.Println("List after inserting 'cherry' at index 1:", stringList) // List after inserting 'cherry' at index 1: [apple cherry banana date] } else { fmt.Println("Error:", err) } // 末尾に "elderberry" を挿入 (サイズと同じインデックス) err = stringList.Insert(stringList.Size(), "elderberry") if err == nil { fmt.Println("List after inserting 'elderberry' at end:", stringList) // List after inserting 'elderberry' at end: [apple cherry banana date elderberry] } else { fmt.Println("Error:", err) } // 先頭に "apricot" を挿入 (インデックス0) err = stringList.Insert(0, "apricot") if err == nil { fmt.Println("List after inserting 'apricot' at index 0:", stringList) // List after inserting 'apricot' at index 0: [apricot apple cherry banana date elderberry] } else { fmt.Println("Error:", err) } // インデックスが範囲外の場合 (大きすぎる) err = stringList.Insert(10, "kiwi") if err != nil { fmt.Println("Error inserting at index 10:", err) // Error inserting at index 10: IndexOutOfBoundsException: Index 10 out of bounds for length 6 } // インデックスが範囲外の場合 (小さすぎる) err = stringList.Insert(-1, "grape") if err != nil { fmt.Println("Error inserting at index -1:", err) // Error inserting at index -1: IndexOutOfBoundsException: Index -1 out of bounds for length 6 } fmt.Println("\n--- MyComparableArrayList の使用例 ---") compList := NewMyComparableArrayList[int]() compList.AddAll([]int{10, 30, 50}) fmt.Println("Original CompList:", compList) // Original CompList: [10 30 50] // 埋め込みにより MyComparableArrayList でも Insert が利用可能 err = compList.Insert(1, 20) if err == nil { fmt.Println("CompList after inserting 20 at index 1:", compList) // CompList after inserting 20 at index 1: [10 20 30 50] } }
Insert()メソッドの解説
func (list *MyArrayList[T]) Insert(index int, element T) error:
- このメソッドは、指定された
indexに新しいelementを挿入します。indexがリストのサイズと等しい場合(つまり、リストの末尾への追加)も有効です。indexが有効な範囲外であればエラーを返します。- Javaの
add(int index, E element)と異なり、Goのスライス操作の慣例に合わせてエラーを返します。- 実装ロジック:
- インデックスの範囲チェック:
if index < 0 || index > len(list.elements)でインデックスが有効な範囲(0からlen(list.elements)まで)にあるかを確認します。len(list.elements)と等しい場合は末尾への追加とみなします。- 範囲外であればエラーを返します。
- スライスの拡張:
list.elements = append(list.elements, *new(T))を使用して、スライスの末尾にダミー要素を追加し、新しい要素を挿入するためのスペースを確保します。*new(T)は、型Tのゼロ値のポインタをdereferenceして、そのゼロ値を作成します(例:intなら0,stringなら "")。- 既存要素のシフト:
copy(list.elements[index+1:], list.elements[index:])がこのメソッドの核心です。- これは、
indexから始まるすべての要素を1つずつ右にシフトします。- 例えば、
list.elementsが[A, B, C]でindex=1に挿入する場合、まず[A, B, C, (zero value)]となります。copy関数によりlist.elements[1:](元の[B, C]) がlist.elements[2:]にコピーされ、結果的に[A, B, B, C]となり、index=1の位置に新しい要素を書き込む準備ができます。- 新しい要素の挿入:
list.elements[index] = elementで、指定されたindexに新しい要素を代入します。
Insert()メソッドの配置
Insert()メソッドはインデックスに基づいて要素を操作するだけで、要素自体の比較は必要ありません。したがって、any型を扱う最も汎用的なMyArrayListに実装するのが適切です。これにより、MyComparableArrayListも、埋め込みを通じてこのInsert()メソッドを利用できるようになります。これで、Goの
ArrayList実装は、指定位置への要素挿入機能を持つようになりました。
IndexOf メソッド
質問
indexOfメソッドも追加してください
回答
はい、承知いたしました。Javaの`ArrayList`における`indexOf()`メソッドと同様の機能を持つメソッドを`MyComparableArrayList`に追加します。
indexOf()メソッドは、指定された要素がこのリスト内で最初に出現するインデックスを返します。要素の比較を伴うため、comparable制約を持つMyComparableArrayListに実装するのが適切です。
MyComparableArrayListにIndexOf()メソッドを追加以下に、
IndexOf()メソッドを追加したMyComparableArrayListの完全なコードを示します。package main import ( "cmp" "fmt" ) // MyArrayList は基本的な動的配列機能を提供します。 // 任意の型の要素を格納できます。 type MyArrayList[T any] struct { elements []T } // MyComparableArrayList は比較可能な要素に特化した動的配列です。 // MyArrayListを埋め込むことで、そのメソッドを継承します。 type MyComparableArrayList[T comparable] struct { MyArrayList[T] // MyArrayListを埋め込む } // IndexOf はリスト内で指定された要素が最初に出現するインデックスを返します。 // 要素が見つからない場合は -1 を返します。 func (list *MyComparableArrayList[T]) IndexOf(element T) int { for i, item := range list.elements { if item == element { return i } } return -1 } func main() { fmt.Println("--- MyComparableArrayList の使用例 ---") stringList := NewMyComparableArrayList[string]() stringList.AddAll([]string{"apple", "banana", "cherry", "date", "banana", "fig"}) fmt.Println("List:", stringList) // List: [apple banana cherry date banana fig] fmt.Println("\n--- IndexOf() のテスト ---") // 存在する要素の検索 idx1 := stringList.IndexOf("banana") fmt.Printf("Index of 'banana': %d\n", idx1) // Index of 'banana': 1 (最初の出現) idx2 := stringList.IndexOf("cherry") fmt.Printf("Index of 'cherry': %d\n", idx2) // Index of 'cherry': 2 // 存在しない要素の検索 idx3 := stringList.IndexOf("grape") fmt.Printf("Index of 'grape': %d\n", idx3) // Index of 'grape': -1 // 最初の要素の検索 idx4 := stringList.IndexOf("apple") fmt.Printf("Index of 'apple': %d\n", idx4) // Index of 'apple': 0 // 最後の要素の検索 idx5 := stringList.IndexOf("fig") fmt.Printf("Index of 'fig': %d\n", idx5) // Index of 'fig': 5 }
IndexOf()メソッドの解説
func (list *MyComparableArrayList[T]) IndexOf(element T) int:
- このメソッドは、リスト内で指定された
elementが最初に出現するインデックスを返します。- 要素が見つからない場合は、Javaの慣例に従い
-1を返します。- 実装ロジック:
- ループによる探索:
for i, item := range list.elementsを使用して、リストの内部スライスelementsをインデックスiと要素itemとともにループで反復処理します。- 要素の比較: 各
itemが検索対象のelementと等しいか (item == element) をチェックします。- 最初に見つかった場合: もし等しい要素が見つかった場合、その時点のインデックス
iを直ちに返し、探索を終了します。- 見つからない場合: ループが最後まで実行されても要素が見つからなかった場合、リストにその要素は存在しないため、
-1を返します。IndexOf() メソッドの配置
IndexOf()メソッドは、内部で要素同士を==演算子で比較するため、Tが**comparable**制約を満たす必要があります。したがって、MyComparableArrayListに実装するのが適切です。これで、リスト内の特定要素の位置を効率的に検索する機能が追加されました。
LastIndexOf メソッド
質問
lastIndexOfメソッドも追加してください
回答
はい、承知いたしました。Javaの`ArrayList`における`lastIndexOf()`メソッドと同様の機能を持つメソッドを`MyComparableArrayList`に追加します。
lastIndexOf()メソッドは、指定された要素がこのリスト内で最後に出現するインデックスを返します。要素の比較を伴うため、comparable制約を持つMyComparableArrayListに実装するのが適切です。
MyComparableArrayListにLastIndexOf()メソッドを追加以下に、
LastIndexOf()メソッドを追加したMyComparableArrayListの完全なコードを示します。package main import ( "cmp" "fmt" ) // MyArrayList は基本的な動的配列機能を提供します。 // 任意の型の要素を格納できます。 type MyArrayList[T any] struct { elements []T } // MyComparableArrayList は比較可能な要素に特化した動的配列です。 // MyArrayListを埋め込むことで、そのメソッドを継承します。 type MyComparableArrayList[T comparable] struct { MyArrayList[T] // MyArrayListを埋め込む } // 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 // 要素が見つからなかった場合 } func main() { fmt.Println("--- MyComparableArrayList の使用例 ---") stringList := NewMyComparableArrayList[string]() stringList.AddAll([]string{"apple", "banana", "cherry", "date", "banana", "fig", "banana"}) // bananaが複数回出現 fmt.Println("List:", stringList) // List: [apple banana cherry date banana fig banana] fmt.Println("\n--- LastIndexOf() のテスト ---") // 複数回出現する要素の検索 idx1 := stringList.LastIndexOf("banana") fmt.Printf("Last index of 'banana': %d\n", idx1) // Last index of 'banana': 6 // 1回だけ出現する要素の検索 idx2 := stringList.LastIndexOf("cherry") fmt.Printf("Last index of 'cherry': %d\n", idx2) // Last index of 'cherry': 2 // 存在しない要素の検索 idx3 := stringList.LastIndexOf("grape") fmt.Printf("Last index of 'grape': %d\n", idx3) // Last index of 'grape': -1 // 最初の要素の検索 idx4 := stringList.LastIndexOf("apple") fmt.Printf("Last index of 'apple': %d\n", idx4) // Last index of 'apple': 0 // 最後の要素の検索 (単独) idx5 := stringList.LastIndexOf("fig") fmt.Printf("Last index of 'fig': %d\n", idx5) // Last index of 'fig': 5 }
LastIndexOf()メソッドの解説
func (list *MyComparableArrayList[T]) LastIndexOf(element T) int:- このメソッドは、リスト内で指定された
elementが最後に出現するインデックスを返します。- 要素が見つからない場合は、Javaの慣例に従い
-1を返します。- 実装ロジック:
- 末尾からの探索:
for i := len(list.elements) - 1; i >= 0; i--を使用して、リストの内部スライスelementsを末尾から先頭に向かってループで反復処理します。これにより、最初に見つかった要素が、その要素の最後の出現になります。- 要素の比較: 各
list.elements[i]が検索対象のelementと等しいか (list.elements[i] == element) をチェックします。- 最初に見つかった場合(末尾から): もし等しい要素が見つかった場合、その時点のインデックス
iを直ちに返し、探索を終了します。- 見つからない場合: ループが最後まで実行されても要素が見つからなかった場合、リストにその要素は存在しないため、
-1を返します。
LastIndexOf()メソッドの配置
LastIndexOf()メソッドは、内部で要素同士を==演算子で比較するため、Tがcomparable制約を満たす必要があります。したがって、MyComparableArrayListに実装するのが適切です。これで、リスト内の特定要素の最後の位置を効率的に検索する機能が追加されました。他に何か追加したいメソッドはありますか?
ReplaceAll メソッド
質問
replaceAllメソッドも追加してください
回答
はい、承知いたしました。Javaの
ArrayListにおけるreplaceAll()メソッドと同様の機能を持つメソッドをMyArrayListに追加します。
replaceAll()メソッドは、リストの各要素を指定された関数(UnaryOperator)を適用した結果で置き換えます。この操作は要素の比較を伴わないため、any型を受け入れる**MyArrayList**に実装するのが適切です。
MyArrayListにReplaceAll()メソッドを追加以下に、
ReplaceAll()メソッドを追加したMyArrayListの完全なコードを示します。package main import ( "fmt" ) // MyArrayList は基本的な動的配列機能を提供します。 // 任意の型の要素を格納できます。 type MyArrayList[T any] struct { elements []T } // ReplaceAll はリストの各要素を、指定された関数 (operator) を適用した結果で置き換えます。 // リストが変更された場合はtrueを返します。 func (list *MyArrayList[T]) ReplaceAll(operator func(element T) T) bool { modified := false for i, element := range list.elements { // operatorが常に新しい値を返すとは限らないが、 // 少なくとも元の値と異なる値が返された場合は変更とみなす newValue := operator(element) if fmt.Sprintf("%v", newValue) != fmt.Sprintf("%v", element) { // 簡易的な値の比較 list.elements[i] = newValue modified = true } } return modified } // MyComparableArrayList は比較可能な要素に特化した動的配列です。 // MyArrayListを埋め込むことで、そのメソッドを継承します。 type MyComparableArrayList[T comparable] struct { MyArrayList[T] // MyArrayListを埋め込む } func main() { fmt.Println("--- MyArrayList の使用例 ---") numbers := NewMyArrayList[int]() numbers.AddAll([]int{1, 2, 3, 4, 5, 6}) fmt.Println("Original Numbers:", numbers) // Original Numbers: [1 2 3 4 5 6] fmt.Println("\n--- ReplaceAll のテスト (数値) ---") // 各要素を2倍にする replaced := numbers.ReplaceAll(func(element int) int { return element * 2 }) fmt.Println("Numbers after ReplaceAll (x2):", numbers) // Numbers after ReplaceAll (x2): [2 4 6 8 10 12] fmt.Println("List modified (x2)?", replaced) // true type Person struct { Name string Age int } people := NewMyArrayList[Person]() people.Add(Person{Name: "Alice", Age: 20}) people.Add(Person{Name: "Bob", Age: 25}) fmt.Println("\nOriginal People:", people) // Original People: [{Alice 20} {Bob 25}] fmt.Println("\n--- ReplaceAll のテスト (構造体) ---") // 各人の年齢を+10する replacedPeople := people.ReplaceAll(func(p Person) Person { p.Age += 10 return p }) fmt.Println("People after ReplaceAll (+10 age):", people) // People after ReplaceAll (+10 age): [{Alice 30} {Bob 35}] fmt.Println("List modified (+10 age)?", replacedPeople) // true fmt.Println("\n--- MyComparableArrayList の使用例 ---") // MyComparableArrayListも埋め込みによりMyArrayListのReplaceAllを使用可能 compStrings := NewMyComparableArrayList[string]() compStrings.AddAll([]string{"hello", "world", "go"}) fmt.Println("Original CompStrings:", compStrings) // Original CompStrings: [hello world go] fmt.Println("\n--- ReplaceAll のテスト (文字列) ---") // 各文字列を大文字にする replacedStrings := compStrings.ReplaceAll(func(s string) string { return s + "!" // 簡単な変更 }) fmt.Println("CompStrings after ReplaceAll (!):", compStrings) // CompStrings after ReplaceAll (!): [hello! world! go!] fmt.Println("List modified (!)?", replacedStrings) // true }ReplaceAll()メソッドの解説
func (list *MyArrayList[T]) ReplaceAll(operator func(element T) T) bool:
- このメソッドは、引数として
operatorという関数を受け取ります。operatorのシグネチャはfunc(element T) Tです。これは、リストの各要素elementを受け取り、その要素を変換した結果の新しいT型の値を返す関数です。JavaのUnaryOperatorに相当します。- 戻り値は
boolで、リストが変更された場合はtrue、そうでなければfalseを返します。- 実装戦略:
modifiedフラグ: リストが変更されたかどうかを追跡するためのブール値modifiedを初期化します。- 要素の変換と更新:
- リストの
list.elementsをインデックスiと要素elementを使ってループします。- 各要素に対して
operator関数を呼び出し、変換されたnewValueを取得します。if fmt.Sprintf("%v", newValue) != fmt.Sprintf("%v", element):- ここで、変換後の
newValueが元のelementと異なるかどうかをチェックします。Goのジェネリクスでは、any型の場合に==演算子で任意の値を比較することはできません(特に構造体やスライスを含む場合)。- そのため、ここでは
fmt.Sprintf("%v", ...)を使って、値を文字列に変換して比較するという簡易的な方法 をとっています。これは、すべての型で機能する比較方法ですが、パフォーマンスや厳密な等価性チェックが必要な場合は、ユーザーがoperator関数内で適切な比較ロジックを実装する必要があります。- もし値が異なっていれば、
list.elements[i] = newValueとして要素を更新し、modifiedをtrueに設定します。- 戻り値:
modifiedフラグの値を返します。
ReplaceAll()の配置
ReplaceAll()メソッドは、要素を変換するoperator関数を受け取ります。この変換は、要素がcomparableである必要がないため、MyArrayList[T any]に実装するのが最も適切です。これにより、MyComparableArrayListも埋め込みによってこのメソッドを利用でき、any型の要素(例えば、比較できないスライスやマップを含む構造体)も変換できるようになります。これで、Javaの
ArrayListが提供する主要な要素操作の機能がさらに充実しました。
感想
replaceAll()メソッドは、変更されたかどうかをbooleanで返しています。
Goでは変更されたかどうかを知るためだけに、制約をcomparableにする必要がありますが、
それを簡易的な値の比較で誤魔化してくれたところに感動しました。
(文字列変換して比較しているのですごく遅そうですが)
MyComparableArrayList では == で比較してくれるようにオーバーライドしてくれてもよかったんですが、
そもそもこの戻り値いらないかも説もあったり。。。
まとめて追加してみた ( SubList、AddFirst、AddLast、GetFirst、GetLast、RemoveFirst、RemoveLast、Reversedメソッド )
質問
他に実装されていないListインターフェイスのメソッドは・・・
結構あるのでめんどくさくなって以下のようにしました。
subList、addFirst、addLast、getFirst、getLast、removeFirst、removeLast、reversedメソッドも追加してください
回答
承知いたしました。ご要望の
subList、addFirst、addLast、getFirst、getLast、removeFirst、removeLast、reversedメソッドをMyArrayListに追加します。これらのメソッドは、要素の比較や順序付けを必要としない一般的なリスト操作であるため、最も汎用的な
MyArrayListに実装するのが適切です。これにより、MyComparableArrayListもこれらのメソッドを埋め込みによって利用できるようになります。
MyArrayListに新しいメソッドを追加以下に、ご要望のメソッドを追加した
MyArrayListの完全なコードを示します。package main import ( "cmp" "errors" "fmt" ) // MyArrayList は基本的な動的配列機能を提供します。 // 任意の型の要素を格納できます。 type MyArrayList[T any] struct { elements []T } // 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] // 新しいMyArrayListを作成し、部分スライスのコピーを格納 // 元のリストの変更がサブリストに影響を与えないように、要素をコピーする 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-- { // Getはエラーを返す可能性があるが、ここではインデックスが有効なので無視 element, _ := list.Get(i) reversedList.Add(element) } return reversedList } // MyComparableArrayList は比較可能な要素に特化した動的配列です。 // MyArrayListを埋め込むことで、そのメソッドを継承します。 type MyComparableArrayList[T comparable] struct { MyArrayList[T] // MyArrayListを埋め込む } func main() { fmt.Println("--- MyArrayList の使用例 ---") myList := NewMyArrayList[string]() myList.AddAll([]string{"a", "b", "c", "d", "e"}) fmt.Println("Original List:", myList) // Original List: [a b c d e] fmt.Println("\n--- AddFirst / AddLast のテスト ---") myList.AddFirst("X") fmt.Println("After AddFirst('X'):", myList) // After AddFirst('X'): [X a b c d e] myList.AddLast("Y") fmt.Println("After AddLast('Y'):", myList) // After AddLast('Y'): [X a b c d e Y] fmt.Println("\n--- GetFirst / GetLast のテスト ---") first, err := myList.GetFirst() if err == nil { fmt.Println("First element:", first) // First element: X } last, err := myList.GetLast() if err == nil { fmt.Println("Last element:", last) // Last element: Y } emptyList := NewMyArrayList[int]() _, err = emptyList.GetFirst() if err != nil { fmt.Println("GetFirst on empty list error:", err) // GetFirst on empty list error: NoSuchElementException: List is empty } fmt.Println("\n--- RemoveFirst / RemoveLast のテスト ---") removedFirst, err := myList.RemoveFirst() if err == nil { fmt.Println("Removed first:", removedFirst, "New List:", myList) // Removed first: X New List: [a b c d e Y] } removedLast, err := myList.RemoveLast() if err == nil { fmt.Println("Removed last:", removedLast, "New List:", myList) // Removed last: Y New List: [a b c d e] } _, err = emptyList.RemoveFirst() if err != nil { fmt.Println("RemoveFirst on empty list error:", err) // RemoveFirst on empty list error: NoSuchElementException: List is empty } fmt.Println("\n--- SubList のテスト ---") sub, err := myList.SubList(1, 4) // インデックス1から4まで (b, c, d) if err == nil { fmt.Println("SubList (1,4):", sub) // SubList (1,4): [b c d] } else { fmt.Println("SubList error:", err) } sub2, err := myList.SubList(0, 0) // 空のサブリスト if err == nil { fmt.Println("SubList (0,0):", sub2) // SubList (0,0): [] } _, err = myList.SubList(0, 10) // 範囲外 if err != nil { fmt.Println("SubList error (out of bounds):", err) // SubList error (out of bounds): IndexOutOfBoundsException: toIndex 10 out of bounds for length 5 } _, err = myList.SubList(3, 1) // fromIndex > toIndex if err != nil { fmt.Println("SubList error (from > to):", err) // SubList error (from > to): IllegalArgumentException: fromIndex (3) > toIndex (1) } fmt.Println("\n--- Reversed のテスト ---") original := NewMyArrayList[int]() original.AddAll([]int{1, 2, 3, 4, 5}) fmt.Println("Original List for Reversed:", original) // Original List for Reversed: [1 2 3 4 5] reversed := original.Reversed() fmt.Println("Reversed List:", reversed) // Reversed List: [5 4 3 2 1] fmt.Println("Original List (unchanged):", original) // Original List (unchanged): [1 2 3 4 5] emptyReversed := NewMyArrayList[string]().Reversed() fmt.Println("Reversed empty list:", emptyReversed) // Reversed empty list: [] }新しいメソッドの解説
SubList(fromIndex, toIndex int) (*MyArrayList[T], error)
- 機能: このリストの指定された
fromIndex(含む) からtoIndex(含まない) までの部分の新しいリストを返します。- 注意: Javaの
subListは元のリストの「ビュー」を返しますが、Goのスライスは部分スライスを返すと元の配列を共有するため、元のリストの変更がサブリストに影響を与えたり、その逆が発生したりする可能性があります。ここでは、より安全な設計として、新しいMyArrayListを作成し、要素をコピーするようにしました。これにより、元のリストとサブリストが独立して存在します。- エラーハンドリング:
fromIndexまたはtoIndexが範囲外の場合、またはfromIndex > toIndexの場合はエラーを返します。AddFirst(element T)
- 機能: リストの先頭に指定された要素を挿入します。
- 実装: 内部で既存の
Insert(0, element)メソッドを呼び出します。AddLast(element T)
- 機能: リストの末尾に指定された要素を追加します。
- 実装: 内部で既存の
Add(element)メソッドを呼び出します。GetFirst() (T, error)
- 機能: リストの最初の要素を返します。
- エラーハンドリング: リストが空の場合は
NoSuchElementExceptionに相当するエラーを返します。GetLast() (T, error)
- 機能: リストの最後の要素を返します。
- エラーハンドリング: リストが空の場合は
NoSuchElementExceptionに相当するエラーを返します。RemoveFirst() (T, error)
- 機能: リストの最初の要素を削除して返します。
- 実装: 内部で既存の
RemoveAt(0)メソッドを呼び出します。- エラーハンドリング: リストが空の場合は
NoSuchElementExceptionに相当するエラーを返します。RemoveLast() (T, error)
- 機能: リストの最後の要素を削除して返します。
- 実装: 内部で既存の
RemoveAt(len(list.elements) - 1)メソッドを呼び出します。- エラーハンドリング: リストが空の場合は
NoSuchElementExceptionに相当するエラーを返します。Reversed() *MyArrayList[T]
- 機能: このリストの要素を逆順にした**新しい
MyArrayList**を返します。元のリストは変更されません。- 実装: 新しいリストを作成し、元のリストの要素を末尾から順に新しいリストに追加していきます。
これらのメソッドはすべて、要素の型が
anyであれば動作するため、MyArrayListに配置しました。これにより、MyComparableArrayListのインスタンスでもこれらの便利な操作が利用できます。
感想
ほんとすごいですね。
一回で複数のメソッドを実装出来ちゃいました。
まとめ
ある程度、話題が固まっていれば、まとめてメソッドを追加することも可能なようです。