LoginSignup
1
0

GitHub Copilot Chatくんにやってもらえた事例まとめ

Last updated at Posted at 2023-12-23

この記事で書きたいこと

Github Copilotの入力補完めちゃくちゃ便利ですよね! Github Copilotには入力補完以外にもChat機能があるのですが、こちらは活用してますでしょうか?

この記事では プライベートベータから利用してきて、試しにやってもらってもらうと意外とできてしまった事例、Copilotの癖等を 個人的備忘録として、ざっくりまとめます。

注意点

  • 231021時点での話なので、今後のモデルの進化等で各仕様は変わる可能性が高そうです
  • うそはうそであると見抜ける人でないと(AIを使うのは)難しい
  • やり取りは趣味コードで試したので業務コードではまた違う結果が出ると思います
  • 途中で力尽きてしまったため中途半端なままですが、Done is better than none ということで投稿します

Copilot Chatの癖

Copilot Chatができることは ぱっと見大体ChatGPTと同じように思うのですが、実はChatGPTと異なる点がいくつかあります。

今開いているコードを読める

今アクティブなタブのコードのみ ですが、ファイルの内容を把握することができます。

※開いているファイルの行数が多い場合は トークンサイズ制限等にかかってしまう可能性が高そうです。あまり過信しない方がいいと思います
※Copilot(入力補完)と違い、現状では別タブで関連するファイルの内容を読んでくれません。。

Q. 今開いているファイルの内容を解説してください。

回答

開いているファイルは、Go言語で書かれたAPIサービスの一部で、
アプリケーションのバージョン情報を取得するための
GetAppVersionメソッドを定義しています。

具体的には以下のような処理を行っています:

AppVersionApiServiceという構造体を定義しています。
この構造体はAppVersionServiceというサービスをフィールドとして持っています。

NewAppVersionApiServiceという関数を通じて、
AppVersionApiServiceの新しいインスタンスを作成します。
この関数はAppVersionServiceを引数として受け取り、
それをフィールドに設定したAppVersionApiServiceの新しいインスタンスを返します。

GetAppVersionメソッドは、アプリケーションのバージョン情報を取得します。
このメソッドは、context.Context、プラットフォーム(int64型)、
バージョン(string型)を引数に取ります。

GetAppVersionメソッドの中で
まずobservability.Tracer.StartInterfaceSpanを呼び出してトレースを開始し
関数の終了時にトレースを終了します。

次に、value_version.NewPlatformを呼び出してプラットフォームの値を検証します。
もし無効な値であれば、エラーメッセージを記録し、レスポンスを返します。

同様に、value_version.NewVersionを呼び出してバージョンの値を検証します。
もし無効な値であれば、エラーメッセージを記録し、レスポンスを返します。

このコードは、APIリクエストを処理し、
適切なレスポンスを生成するためのビジネスロジックを実装しています。
Q. これらの4ファイルでやっている処理を正確にまとめてください。

(下記4ファイルを開いていた)

api_app_version_service.go
app_version_service.go
version_usecase.go
version_persistence.go

回答

申し訳ありませんが、あなたが提供した情報では、
4つのファイルについての詳細な説明を提供することはできません。

具体的なファイル名やコードの一部を提供していただければ、
それらのファイルで行われている処理についての詳細な説明を提供することが可能です。

それぞれのファイルが何を行っているのか、
または特定のコードブロックについて質問がある場合は、
それらの詳細を提供してください。

"選択範囲" という文脈を理解できる

"選択範囲"のコードをレビューしてください という指示を出すと 今開いているファイルの"選択範囲"についてのみを対象とした返事を返してくれます。"このコードが正しく動くかレビューしてください" "この関数は正しいでしょうか?"のような 無茶な質問をしてもメソッド名やコメントが十分説明していれば正しい回答を返してくれます。

Q. 選択中のコードが意図通り動作するかレビューしてください

(質問時に選択していたコード)

func (u *User) AddNamedTypeExps(exp uint32, namedTypes []uint16) {
	for i := range u.ManagedNamedTypes {
		for namedType := range namedTypes {
			if u.ManagedNamedTypes[i].NamedType == uint16(namedType) {
				u.ManagedNamedTypes[i].AddExp(exp)
			}
		}
	}
}

回答

選択されたコードは、意図通りに動作しない可能性があります。
以下の点に注意してください。

`namedTypes` スライスの範囲を正しく処理していません。
`namedTypes` スライスの要素数を取得する必要がありますが、
現在は `range` を使用しているため、
インデックスではなく要素がループされています。

正しい方法は、`len(namedTypes)` を使用して、
スライスの要素数を取得し、`for i := 0; i < len(namedTypes); i++` を使用して、
スライスの要素をループすることです。
`namedType` 変数は、`namedTypes` スライスの要素であるべきですが、
現在は `range` によって要素のインデックスがループされています。
正しい方法は、`namedTypes[i]` を使用して、スライスの要素を取得することです。

修正したコードは以下のようになります。


func (u *User) AddNamedTypeExps(exp uint32, namedTypes []uint16) {
    for i := range u.ManagedNamedTypes {
        for j := 0; j < len(namedTypes); j++ {
            if u.ManagedNamedTypes[i].NamedType == namedTypes[j] {
                u.ManagedNamedTypes[i].AddExp(exp)
            }
        }
    }
}

最新の情報は合っているか怪しい

これは事前学習済みモデルである以上ChatGPT同様で仕方ないことですが、最新のNuxt3/Vuetify3等のことを訊くと、知ったかぶりをされてしまいます。知らないことがあることを予め覚えておきましょう。困ったことに具体的にいつまでの情報を持っているか正確なソースが見つからないため、最近のことは訊かないのが無難です。

Q. Vuetify2とVuetify3の変更点を5つ以上挙げてください

回答

以下は、Vuetify2とVuetify3の変更点の例です。

- Composition APIのサポート
  - Vuetify3は、Vue3のComposition APIをサポートしています。これにより、よりシンプルで柔軟なコンポーネントの実装が可能になります。
- テーマの変更方法の変更
  - Vuetify3では、テーマの変更方法が変更されました。Vuetify2では、vuetify.jsファイル内でテーマを定義していましたが、Vuetify3では、vuetify.config.jsファイル内でテーマを定義する必要があります。
- レイアウトの変更
  - Vuetify3では、レイアウトの変更が行われました。例えば、v-rowとv-colの代わりに、v-containerとv-layoutが導入されました。
- アイコンの変更
  - Vuetify3では、アイコンの変更が行われました。Vuetify2では、Material Design Iconsがデフォルトで使用されていましたが、Vuetify3では、Vue3の公式アイコンライブラリである@vue/iconsがデフォルトで使用されます。
- フォームの変更
  - Vuetify3では、フォームの変更が行われました。例えば、v-text-fieldの代わりに、v-inputが導入されました。また、v-formの代わりに、v-form-groupが導入されました。
- テーブルの変更
  - Vuetify3では、テーブルの変更が行われました。例えば、v-data-tableの代わりに、v-data-gridが導入されました。また、v-data-iteratorの代わりに、v-forが推奨されるようになりました。
- ナビゲーションの変更
  - Vuetify3では、ナビゲーションの変更が行われました。例えば、v-navigation-drawerの代わりに、v-sidebarが導入されました。また、v-tabsの代わりに、v-tabが導入されました。

以上が、Vuetify2とVuetify3の変更点の例です。

答え合わせ

- Composition APIのサポート: 正解
  - 確かにサポートしている
  - (しかし Vue3がサポートしていることから推測されたものの可能性が高そう)
- テーマの変更方法の変更: 正解
  - 確かに変更されたが言っていることが謎
- レイアウトの変更 不正解
  - v-rowとv-colとv-containerはVuetify3にもある。
  - v-layoutは存在せず完全にハルシネーション
- テーブルの変更 不正解
  - VDataTableは Labs版であるがVuetify3にもある。
  - VDataGridは存在せず完全にハルシネーション
- フォームの変更 不正解
  - v-text-fieldとv-formとv-form-groupはVuetify3にもある
  - v-inputは存在するが、v-text-field等の基幹モデルなのでハルシネーション
- ナビゲーションの変更 不正解
  - v-navigation-drawerはVuetify3にある
  - v-tabsもv-tabもVuetify3にある

"Chat"とは言うが 直前の会話を忘れがち

チャットのはずなのですが、直前の会話を続けることがCopilotは苦手そうに感じます。"続けて"と言う指示は避けた方が良さそうです。代わりに出力結果をもう一度コピーして訊くと知りたいことを教えてくれることが多いです。

Q. Vue2とVue3では破壊的な変更がいくつかありました。破壊的な変更の例を5つ以上挙げてください
(めちゃくちゃだったため省略)
Q. もっと他の例を出してください
申し訳ありません。以下は、別の例です。

Active selection: Excerpt from main.js, lines 1 to 10:
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

Vue config productionTip false

new Vue
  router
  store
  render h => h App
 mount app

The cursor is on line 0

プログラミングに関することしか答えてくれない(として誤判定されることがある)

CopilotChatは プログラミングに関することしか答えないようにと指示されています。(プロンプトインジェクションにより 事前に与えられているプロンプトが特定されています) そのため、場合によっては プログラミングに関する事柄ではないとして 拗ねられてしまうことがあります。(やってくれることもあります)

Q. 下記コードのコメントを より自然な英文にしてください

(選択していたコード)

// how many times the item can be bought (-1 means unlimited)
Stock int64 json:"stock"

回答

A. Sorry, but I can only assist with programming related questions.
(訳: "ごめんね、プログラミングに関係する質問しかアシストしないよ")
Q. 取引を行うオブジェクトを定義したいです。 Want の対義語となる英単語はなんでしょう?
A. "Want"の対義語としては、"Have"が一般的に使われます。

Q. Haveではない気がします。ここでいう取引とは交換です。
欲しい物をwantとした場合、受け渡すものを英語でなんといいますか?

A. ご指摘ありがとうございます。
交換において、受け渡すものに対しては "Give" が一般的に使われます。

やってもらえること

以下では、Copilotにやってもらえたことを プロンプト例とともに紹介します

簡単な解説をお願いする (ど忘れしたものを思い出す)

知っていることに関連する事項を訊くとかなり良いです。

Q Pythonで JavascriptのMap関数のような処理を書く方法を教えてください。確かLambdaとか書く仕様だった気がします。

回答

Pythonには、JavaScriptのMap関数に相当する機能として、リスト内包表記があります。
リスト内包表記は、リストの各要素に対して式を評価し、その結果を新しいリストに格納する方法です。リスト内包表記には、ラムダ式を使用することができます。

以下は、リスト内包表記を使用して、リストの各要素に対してラムダ式を適用する例です。
old_list = [1, 2, 3, 4, 5]
new_list = [lambda x: x * 2 for x in old_list]

この例では、リストold_listの各要素に対して、
ラムダ式lambda x: x * 2を適用し、新しいリストnew_listに格納しています。
このラムダ式は、引数xを受け取り、
xを2倍にした値を返します。

リスト内包表記を使用することで、
JavaScriptのMap関数と同様の処理を実現することができます。
Q PythonでJavaScriptのFilterのような処理がしたいです

回答

Pythonには、JavaScriptのFilter関数に相当する機能として、リスト内包表記があります。
リスト内包表記は、リストの各要素に対して式を評価し、
その結果がTrueとなる要素だけを新しいリストに格納する方法です。
リスト内包表記には、ラムダ式を使用することができます。

以下は、リスト内包表記を使用して、リストの各要素に対してラムダ式を適用し、
結果がTrueとなる要素だけを新しいリストに格納する例です。
old_list = [1, 2, 3, 4, 5]
new_list = [x for x in old_list if x % 2 == 0]

この例では、リストold_listの各要素に対して、
ラムダ式x % 2 == 0を適用し、結果がTrueとなる要素だけを新しいリストnew_listに格納しています。
このラムダ式は、引数xを受け取り、xを2で割った余りが0であるかどうかを判定します。

リスト内包表記を使用することで、JavaScriptのFilter関数と同様の処理を実現することができます。
Q Go言語でPython f-stringのように 複数の変数を割り当て、文字列に変換する方法を教えて

回答

Go言語には、Pythonのf-stringのような文字列フォーマット機能はありませんが、
fmt.Sprintf関数を使用して同様のことができます。

以下は、複数の変数を割り当て、文字列に変換する例です:

package main

import (
    "fmt"
)

func main() {
    // 変数の定義
    name := "Alice"
    age := 30

    // 文字列のフォーマット
    str := fmt.Sprintf("My name is %s and I'm %d years old.", name, age)

    // 結果の出力
    fmt.Println(str)
}

このコードでは、fmt.Sprintf関数を使用して、nameとageの値を文字列に埋め込んでいます。
%sは文字列を、%dは整数を表すフォーマット指定子です。
fmt.Sprintf関数は、指定されたフォーマット文字列に従って、引数を文字列に変換します。
変換された文字列は、str変数に格納され、fmt.Println関数を使用して出力されます。

トラブルシューティング

エラーが起こったらそのエラー原因を訊くと、だいたいの場合それらしき原因を教えてくれます。
もちろんミスもあるので 3回訊いて全く違うことが返ってくるようなら Googleに行ったほうがいいです。
(Googleと違う、とても嬉しい点はエラーだけを丸投げしてもある程度察してくれることですが、なるべく背景情報を書いた方が正しい回答を出してくれます)

Q. 下記エラーを修正してください

エラー本文

 mysql> INSERT INTO "goadmin_site" ("id","key","value","state","description","created_at","updated_at") VALUES (72,'hide_visitor_user_center_entrance','false',1,NULL,'2023-06-11 16:52:19','2023-06-11 16:52:19'); ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '"goadmin_site" ("id","key","value","state","description","created_at","updated_a' at line 1

回答

A. このエラーは、ダブルクォーテーションで囲まれたテーブル名と列名が、MySQLではサポートされていないために発生しています。MySQLでは、バッククォートを使用してテーブル名や列名をエスケープする必要があります。

以下は、修正例です。
INSERT INTO `goadmin_site` (`id`, `key`, `value`, `state`, `description`, `created_at`, `updated_at`) VALUES (72, 'hide_visitor_user_center_entrance', 'false', 1, NULL, '2023-06-11 16:52:19', '2023-06-11 16:52:19');

このSQL文では、バッククォートを使用してテーブル名と列名をエスケープしています。この修正を加えたSQL文を実行することで、エラーを回避することができます。

整形

間違った整形をされそうな気がしてしまいますが、意外なことに殆どの場合 指示どおりのきれいな整形を行ってくれます。唐突に要件定義を変えるような雑な指示をしても怒らずきれいにやってくれるのでありがたいです。

Q 下記リンク集を ReadonlyArray<{ title: string description: string icon: string link: string }> 型の配列に整形してください。

本文

Official

Website https://example.com News (at website) https://2.example.com https://3.example.com Archives https://4.example.com Wiki

Main wiki https://5.example.com Sub wiki (fork) https://6.example.com

回答

A 以下は、整形したコードです。

const links: ReadonlyArray<{ title: string, description: string, icon: string, link: string }> = [
  {
    title: 'Website',
    description: 'https://example.com',
    icon: '',
    link: 'https://example.com'
  },
  {
    title: 'News (at website)',
    description: 'https://2.example.com https://3.example.com',
    icon: '',
    link: 'https://example.com'
  },
  {
    title: 'Archives',
    description: 'https://4.example.com',
    icon: '',
    link: 'https://4.example.com'
  },
  {
    title: 'Main wiki',
    description: 'https://5.example.com',
    icon: '',
    link: 'https://5.example.com'
  },
  {
    title: 'Sub wiki (fork)',
    description: 'https://6.example.com',
    icon: '',
    link: 'https://6.example.com'
  }
];
このコードでは、ReadonlyArray を使って、配列を読み取り専用にしています。各要素は、title、description、icon、link の4つのプロパティを持つオブジェクトです。description には、複数のリンクを含めることができます。

質問その2

Q やっぱりカテゴリ別に表示するようにしたいので、先程の型定義を元に 若干変えて作り直してください。

回答その2

A 以下は、カテゴリ別にリンクを表示するために、型定義を変更したコードです。
type LinkCategory = 'Official' | 'News' | 'Archives' | 'Wiki';

type Link = {
  title: string;
  description: string;
  icon: string;
  link: string;
};

type Links = ReadonlyArray<{
  category: LinkCategory;
  links: ReadonlyArray<Link>;
}>;

const links: Links = [
  {
    category: 'Official',
    links: [
      {
        title: 'Website',
        description: 'https://example.com',
        icon: '',
        link: 'https://example.com'
      }
    ]
  },
  {
    category: 'News',
    links: [
      {
        title: 'News (at website)',
        description: 'https://2.example.com https://3.example.com',
        icon: '',
        link: 'https://example.com'
      }
    ]
  },
  {
    category: 'Archives',
    links: [
      {
        title: 'Archives',
        description: 'https://4.example.com',
        icon: '',
        link: 'https://4.example.com'
      }
    ]
  },
  {
    category: 'Wiki',
    links: [
      {
        title: 'Main wiki',
        description: 'https://5.example.com',
        icon: '',
        link: 'https://5.example.com'
      },
      {
        title: 'Sub wiki (fork)',
        description: 'https://6.example.com',
        icon: '',
        link: 'https://6.example.com'
      }
    ]
  }
];

このコードでは、Links 型を定義して、カテゴリ別にリンクをグループ化しています。各カテゴリは、LinkCategory 型で定義されています。Link 型は、リンクの詳細を表します。Links 型は、カテゴリとリンクの配列を持つオブジェクトの配列です。各オブジェクトは、category プロパティと links プロパティを持ちます。
Q 下記をマークダウンのテーブル記法に書き直して

本文


Language	Traces	Metrics	Logs
C++	Stable	Stable	Stable
C#/.NET	Stable	Stable	Stable
Erlang/Elixir	Stable	Experimental	Experimental
Go	Stable	Stable	Not yet implemented
Java	Stable	Stable	Stable
JavaScript	Stable	Stable	Development
PHP	Release candidate	Release candidate	Release candidate
Python	Stable	Stable	Experimental
Ruby	Stable	Not yet implemented	Not yet implemented
Rust	Beta	Alpha	Alpha
Swift	Stable	Experimental	In development
A
| Language        | Traces                | Metrics               | Logs                  |
|-----------------|-----------------------|-----------------------|-----------------------|
| C++             | Stable                | Stable                | Stable                |
| C#/.NET         | Stable                | Stable                | Stable                |
| Erlang/Elixir   | Stable                | Experimental          | Experimental          |
| Go              | Stable                | Stable                | Not yet implemented   |
| Java            | Stable                | Stable                | Stable                |
| JavaScript      | Stable                | Stable                | Development           |
| PHP             | Release candidate     | Release candidate     | Release candidate     |
| Python          | Stable                | Stable                | Experimental          |
| Ruby            | Stable                | Not yet implemented  | Not yet implemented  |
| Rust            | Beta                  | Alpha                 | Alpha                 |
| Swift           | Stable                | Experimental          | In development        |
Q 今選択している範囲のコードのすべての行において、1つ目の引数をアッパーキャメルケースに変えてください
A 以下は、選択された範囲のコードのすべての行において、1つ目の引数をアッパーキャメルケースに変更した例です。
    info.AddField("CreatedAt", "created_at", db.Datetime).FieldHide()
    info.AddField("UpdatedAt", "updated_at", db.Datetime).FieldHide()
    info.AddField("DeletedAt", "deleted_at", db.Datetime).FieldHide()
    info.AddField("ManagedWeaponId", "managed_weapon_id", db.Integer)
    info.AddField("ManagedSupportId", "managed_support_id", db.Integer)

このコードでは、各行の1つ目の引数をアッパーキャメルケースに変更しています。アッパーキャメルケースでは、単語の最初の文字を大文字にし、単語の区切りをなくします。このようにすることで、変数名がより読みやすくなります。
Q. 下記 JSONの順番に合うように 選択中のモデルのフィールド名を並び替えてください

本文

JSON:
{ "id": 1001, "startAt": null, "endAt": null, "restockType": 0, "stock": -1, "uiPriority": 2000, "srcType": 9, "srcId": -1, "srcAmount": 50, "rewardType": 11, "rewardId": 400000, "rewardAmount": 1, "description": "", "detailText": "", "buyCount": 0, "badgeType": 0, "restockAt": null },
}

モデル:
type ExchangeShop struct {
Id          int64   `json:"id"`
StartAt     *string `json:"startAt"`
EndAt       *string `json:"endAt"`
RestockType int64   `json:"restockType"`

RestockAt *string `json:"restockAt"`
RewardId  int64   `json:"rewardId"`

Description string `json:"description"`

BadgeType int64 `json:"badgeType"`

BuyCount int64 `json:"buyCount"`

DetailText string `json:"detailText"`

RewardAmount int64 `json:"rewardAmount"`

RewardType int64 `json:"rewardType"`

SrcAmount int64 `json:"srcAmount"`

SrcId int64 `json:"srcId"`

SrcType int64 `json:"srcType"`

Stock int64 `json:"stock"`

UiPriority int64 `json:"uiPriority"`
}

回答

以下は、JSONの順番に合うようにフィールド名を並び替えたExchangeShopモデルの例です。
type ExchangeShop struct {
    Id           int64    `json:"id"`
    StartAt      *string  `json:"startAt"`
    EndAt        *string  `json:"endAt"`
    RestockType  int64    `json:"restockType"`
    Stock        int64    `json:"stock"`
    UiPriority   int64    `json:"uiPriority"`
    SrcType      int64    `json:"srcType"`
    SrcId        int64    `json:"srcId"`
    SrcAmount    int64    `json:"srcAmount"`
    RewardType   int64    `json:"rewardType"`
    RewardId     int64    `json:"rewardId"`
    RewardAmount int64    `json:"rewardAmount"`
    Description  string   `json:"description"`
    DetailText   string   `json:"detailText"`
    BuyCount     int64    `json:"buyCount"`
    BadgeType    int64    `json:"badgeType"`
    RestockAt    *string  `json:"restockAt"`
}
フィールド名をJSONの順番に合わせて並び替えました。
Q. 下記モデルからJSON定義を消してください

本文

type FriendFavoriteMember struct { ManagedCharacterId int64 json:"ManagedCharacterId" CharacterId int64 json:"CharacterId" Level int64 json:"Level" Exp int64 json:"Exp" LevelBreak int64 json:"LevelBreak" DuplicatedCount int64 json:"DuplicatedCount" ArousalLevel int64 json:"ArousalLevel" SkillLevel1 int64 json:"SkillLevel1" SkillLevel2 int64 json:"SkillLevel2" SkillLevel3 int64 json:"SkillLevel3" WeaponId int64 json:"WeaponId" WeaponLevel int64 json:"WeaponLevel" WeaponSkillLevel int64 json:"WeaponSkillLevel" WeaponSkillExp int64 json:"WeaponSkillExp" NamedLevel int64 json:"NamedLevel" NamedExp int64 json:"NamedExp" AbilityBoardId int64 json:"AbilityBoardId" EquipItemIds []uint32 json:"EquipItemIds" }

回答

A. 以下は、JSON定義を消したFriendFavoriteMemberのstruct定義例です。
type FriendFavoriteMember struct {
    ManagedCharacterId int64
    CharacterId        int64
    Level              int64
    Exp                int64
    LevelBreak         int64
    DuplicatedCount    int64
    ArousalLevel       int64
    SkillLevel1        int64
    SkillLevel2        int64
    SkillLevel3        int64
    WeaponId           int64
    WeaponLevel        int64
    WeaponSkillLevel   int64
    WeaponSkillExp     int64
    NamedLevel         int64
    NamedExp           int64
    AbilityBoardId     int64
    EquipItemIds       []uint32
}
Q 下記JSONを今選択しているモデル構造に一致するように変換してください。

本文

    {
        "id": 20221100,
        "limitedFlag": 1,
        "dstType": 2,
        "dstId": 998,
        "dstAmount": 1,
        "altType": 0,
        "altId": -1,
        "altAmount": 0,
        "messageMakeId": -1,
        "altMessageMakeId": -1,
        "srcType1": 2,
        "srcId1": 9002,
        "srcAmount1": 2,
        "srcType2": 0,
        "srcId2": -1,
        "srcAmount2": 0,
        "startAt": "2022-11-05T00:00:00",
        "endAt": "2023-02-28T15:59:59",
        "limitCount": -1,
        "tradeMax": 999,
        "resetFlag": 0,
        "totalTradeCount": 0,
        "monthlyTradeCount": 0,
        "groupId": 3011,
        "eventType": -1,
        "sort": 20221100,
        "labelId": 0
    },
A 以下は、今選択しているモデル構造に一致するようにJSONを変換した例です。
{
    Id:                20221100,
    LimitedFlag:       1,
    DstType:           2,
    DstId:             998,
    DstAmount:         1,
    AltType:           0,
    AltId:             -1,
    AltAmount:         0,
    MessageMakeId:     -1,
    AltMessageMakeId:  -1,
    SrcType1:          2,
    SrcId1:            9002,
    SrcAmount1:        2,
    SrcType2:          0,
    SrcId2:            -1,
    SrcAmount2:        0,
    StartAt:           "2022-11-05T00:00:00",
    EndAt:             "2023-02-28T15:59:59",
    LimitCount:        -1,
    TradeMax:          999,
    ResetFlag:         0,
    TotalTradeCount:   0,
    MonthlyTradeCount: 0,
    GroupId:           3011,
    EventType:         -1,
    Sort:              20221100,
    LabelId:           0,
}

簡単なものの実装

簡単な実装であれば ほぼ確実に正しいものが出てきます。
間違いやすい点があれば補足してくれる場合もあります。

Q Goで 文字列長を知る方法を教えてください

回答

A Goで文字列の長さを知るには、len関数を使用します。
以下は、len関数を使用して文字列"Hello, world!"の長さを取得する例です。

package main

import "fmt"

func main() {
    str := "Hello, world!"
    length := len(str)
    fmt.Println(length)
}

上記の例では、len関数を使用して
文字列"Hello, world!"の長さを取得しています。
len関数は、引数に渡された文字列のバイト数を返します。
日本語のようにマルチバイト文字を含む場合は、
len関数で正確な文字数を取得することができません。
その場合は、utf8.RuneCountInString関数を使用して、
文字数を取得する必要があります。
Q Goで値が重複しない配列を作る方法を教えて

回答

A Goで値が重複しない配列を作るには、mapを使用することができます。以下は、mapを使用して値が重複しない配列を作成する例です。

package main

import "fmt"

func main() {
    // 重複した値を含む配列
    arr := []int{1, 2, 3, 2, 4, 3, 5}

    // 重複しない値を保持するマップ
    m := make(map[int]bool)

    // 重複しない値を保持する配列
    uniqueArr := []int{}

    // 配列の値をマップに追加し、重複しない値をuniqueArrに追加する
    for _, v := range arr {
        if _, ok := m[v]; !ok {
            m[v] = true
            uniqueArr = append(uniqueArr, v)
        }
    }

    fmt.Println(uniqueArr) // [1 2 3 4 5]
}

Q Goで文字列をintに変換する例を教えてください。
A Goで文字列をintに変換するには、strconvパッケージのAtoi関数を使用します。
以下は、Atoi関数を使用して文字列をintに変換する例です。

package main

import (
    "fmt"
    "strconv"
)

func main() {
    str := "123"
    num, err := strconv.Atoi(str)
    if err != nil {
        fmt.Println("Failed to convert string to int")
        return
    }
    fmt.Println(num)
}
Q. UnixTimeから time.Timeオブジェクトを作る方法教えて
A. Go言語のtimeパッケージを使用して、UnixTimeからtime.Timeオブジェクトを作成することができます。
以下のように、time.Unix()関数を使用して、
UnixTimeとタイムゾーンを指定してtime.Timeオブジェクトを作成することができます。

unixTime := int64(1628764800) // UnixTime
location, err := time.LoadLocation("Asia/Tokyo") // タイムゾーン
if err != nil {
    // エラー処理
}
timeObj := time.Unix(unixTime, 0).In(location)

time.Unix()関数は、UnixTimeと秒未満のナノ秒を受け取り、time.Timeオブジェクトを返します。In()メソッドを使用して、指定したタイムゾーンに変換することができます。time.LoadLocation()関数を使用して、タイムゾーンを指定することができます。この関数は、タイムゾーンの名前を受け取り、*time.Locationオブジェクトを返します。タイムゾーンの名前は、IANA Time Zone Databaseに基づいています。

上記の例では、Asia/Tokyoというタイムゾーンを指定しています。このタイムゾーンは、日本標準時を表します。タイムゾーンを指定しない場合、time.Unix()関数はUTCを使用してtime.Timeオブジェクトを作成します。
Q GORMで ItemというカラムのCategoryというフィールドが 1 10 100 のどれかのレコードだけを取得する例を作成してください。
A 以下は、GORMを使用して、ItemテーブルからCategoryが1、10、または100のレコードを取得する例です。
var items []model_item.Item
result := db.Where("Category IN (?)", []int{1, 10, 100}).Find(&items)
if result.Error != nil {
    // handle error
}

上記の例では、dbはGORMのDBインスタンスであり、model_item.ItemはItemテーブルのモデルです。Where()メソッドを使用して、Categoryが1、10、または100のレコードを取得する条件を指定しています。Find()メソッドを使用して、条件に一致するレコードを取得しています。取得したレコードは、itemsスライスに格納されます。result.Errorがnilでない場合は、エラーが発生したことを示します。
Q 1011から1137まで 連番で挿入するようなSQL文の例を教えてください
A 1011から1137までの数値を連番で挿入するSQL文の例を以下に示します。
INSERT INTO table_name (column_name) VALUES
(1011), (1012), (1013), (1014), (1015), (1016), (1017), (1018), (1019), (1020),
(1021), (1022), (1023), (1024), (1025), (1026), (1027), (1028), (1029), (1030),
(1031), (1032), (1033), (1034), (1035), (1036)...;

このSQL文では、table_nameというテーブルのcolumn_nameというカラムに、
1011から1137までの数値を連番で挿入しています。
VALUESの後にカンマで区切って、各数値を括弧で囲んで指定しています。
また、数値の範囲が大きい場合は、SQL文を生成するプログラムを作成するなどして、自動的に生成することもできます。

Q ROW_NUMBERなどを使うともう少しシンプルにできそうな気がします
A はい、ROW_NUMBERを使うことで、よりシンプルにSQL文を記述することができます。
以下は、ROW_NUMBERを使った例です。
INSERT INTO table_name (column_name)
SELECT num
FROM (
  SELECT ROW_NUMBER() OVER () + 1010 AS num
  FROM information_schema.columns
  LIMIT 127
) AS t

このSQL文では、ROW_NUMBERを使って、
1011から1137までの数値を生成しています。
ROW_NUMBERは、SELECT文の中で連番の値を生成するための関数であり、
OVER句を使って、どのように連番を生成するかを指定することができます。
この例では、ROW_NUMBER() OVER ()として、単純に連番を生成しています。

また、SELECT文の中で、information_schema.columnsというテーブルを参照しています。
これは、MySQLに標準で用意されているテーブルであり、
任意のSELECT文の中で参照することができます。
この例では、LIMIT句を使って、127行までの連番を生成しています。

最後に、SELECT文の結果をINSERT INTO文で指定したテーブルに挿入しています。
このようにすることで、ROW_NUMBERを使って、よりシンプルにSQL文を記述することができます。
Q JSONのすべての要素のキーの1文字目を大文字に変えるPythonスクリプトを書いて欲しいです。

A 以下は、JSONのすべての要素のキーの1文字目を大文字に変えるPythonスクリプトです。

import json

# JSONファイルの読み込み
with open("unique_event_quest_datas.json", "r", encoding="utf-8") as f:
    data = json.load(f)

# キーの1文字目を大文字に変換する関数
def capitalize_first_letter(s):
    return s[0].upper() + s[1:]

# JSONのすべての要素のキーの1文字目を大文字に変換する
def capitalize_keys(obj):
    if isinstance(obj, dict):
        return {capitalize_first_letter(k): capitalize_keys(v) for k, v in obj.items()}
    elif isinstance(obj, list):
        return [capitalize_keys(elem) for elem in obj]
    else:
        return obj

# キーの1文字目を大文字に変換したJSONを出力する
print(json.dumps(capitalize_keys(data), ensure_ascii=False, indent=4))

このスクリプトでは、jsonモジュールを使って、JSONファイルを読み込みます。次に、capitalize_first_letter()関数を定義し、キーの1文字目を大文字に変換する処理を行います。そして、capitalize_keys()関数を定義し、JSONのすべての要素のキーの1文字目を大文字に変換する処理を再帰的に行います。最後に、json.dumps()関数を使って、キーの1文字目を大文字に変換したJSONを出力します。

このスクリプトを実行すると、キーの1文字目が大文字に変換されたJSONが出力されます。
Q 下記3種類のような関数にマッチし、ctx context.Context を 1つ目の引数として追加するVSCodeで使える 検索置き換え用の正規表現パターンを作成してください。

本文

func (s *achievementService) GetAllPlayerAchievement(internalUserId uint) ([]model_user.UserAchievement, error) {

func (s *characterService) SetShownCharacter(internalUserId uint, managedCharacterId value_user.ManagedCharacterId, shown bool) error {

func (s *playerAgeService) SetPlayerAge(internalUserId uint, age value_user.Age) error {

回答

A 以下は、VSCodeで使える検索置き換え用の正規表現パターンです。

検索パターン: func \((\w+) \*([\w.]+)\) ([\w]+)\(([\w]+) ([\w]+)

置換パターン: func ($1 *$2) $3(ctx context.Context, $4 $5, $6 $7

この正規表現パターンは、以下のような構造を持ちます。

func: funcキーワード
\((\w+): 関数名の後に続く(を検索し、関数名をキャプチャする
\*([\w.]+)\): *に続く構造体名をキャプチャする
([\w]+): 関数名をキャプチャする
([\w]+): 1つ目の引数の型をキャプチャする
([\w]+): 1つ目の引数の変数名をキャプチャする
置換パターンでは、以下のような構造を持ちます。

func: funcキーワード
$1: 関数名
*$2: 構造体名
$3: 関数名
ctx context.Context: context.Context型の引数を追加する
$4: 1つ目の引数の型
$5: 1つ目の引数の変数名

少し難しめの実装

GPT-4のような難しいことはできなさそうですが、1ファイル程度で実現できることであればプロンプト通りに作ってくれます。

Q github.com/urfave/cli と gormを使い、1日ごとにデータベースの特定のテーブルの内容を更新するようなバッチを実装したいです。実装例を提示してください。

A 以下は、github.com/urfave/cliとgormを使用して、1日ごとにデータベースの特定のテーブルの内容を更新するバッチの実装例です。

package main

import (
    "log"
    "time"

    "github.com/jinzhu/gorm"
    _ "github.com/jinzhu/gorm/dialects/mysql"
    "github.com/urfave/cli"
)

type User struct {
    ID        uint `gorm:"primary_key"`
    Name      string
    CreatedAt time.Time
    UpdatedAt time.Time
}

func main() {
    app := cli.NewApp()
    app.Name = "batch"
    app.Usage = "run batch process"
    app.Action = func(c *cli.Context) error {
        db, err := gorm.Open("mysql", "user:password@/dbname?charset=utf8&parseTime=True&loc=Local")
        if err != nil {
            log.Fatal(err)
        }
        defer db.Close()

        // テーブルが存在しない場合は作成する
        if !db.HasTable(&User{}) {
            db.CreateTable(&User{})
        }

        // 1日ごとにデータを更新する
        for {
            // データを更新する処理をここに記述する
            log.Println("update data")

            // 1日待機する
            time.Sleep(24 * time.Hour)
        }
    }

    err := app.Run(os.Args)
    if err != nil {
        log.Fatal(err)
    }
}

上記の例では、gormを使用してMySQLデータベースに接続し、1日ごとにデータベースの特定のテーブルの内容を更新するバッチを実装しています。Userという構造体を定義し、gormのタグを使用してテーブルのカラムとマッピングしています。cliパッケージを使用して、コマンドライン引数を処理し、gormを使用してデータベースに接続しています。HasTable関数を使用して、テーブルが存在するかどうかを確認し、存在しない場合はCreateTable関数を使用してテーブルを作成しています。forループを使用して、1日ごとにデータを更新する処理を実行しています。time.Sleep関数を使用して、1日待機しています。この例では、データを更新する処理は記述されていませんが、forループの中に記述することで、1日ごとにデータを更新する処理を実行することができます。

Q time.Sleepを使わず 毎日確実に午前0時に実行させるようにしてほしいです。
A time.Sleepを使用せずに、毎日確実に午前0時に実行する方法としては、time.Tickerを使用する方法があります。time.Tickerは、指定された間隔で定期的にイベントを発生させるための機能です。以下は、time.Tickerを使用して、毎日確実に午前0時に実行するバッチの実装例です。

package main

import (
    "log"
    "time"

    "github.com/jinzhu/gorm"
    _ "github.com/jinzhu/gorm/dialects/mysql"
    "github.com/urfave/cli"
)

type User struct {
    ID        uint `gorm:"primary_key"`
    Name      string
    CreatedAt time.Time
    UpdatedAt time.Time
}

func main() {
    app := cli.NewApp()
    app.Name = "batch"
    app.Usage = "run batch process"
    app.Action = func(c *cli.Context) error {
        db, err := gorm.Open("mysql", "user:password@/dbname?charset=utf8&parseTime=True&loc=Local")
        if err != nil {
            log.Fatal(err)
        }
        defer db.Close()

        // テーブルが存在しない場合は作成する
        if !db.HasTable(&User{}) {
            db.CreateTable(&User{})
        }

        // 毎日午前0時にデータを更新する
        ticker := time.NewTicker(24 * time.Hour)
        for {
            select {
            case <-ticker.C:
                // データを更新する処理をここに記述する
                log.Println("update data")
            }
        }
    }

    err := app.Run(os.Args)
    if err != nil {
        log.Fatal(err)
    }
}

上記の例では、time.Tickerを使用して、毎日確実に午前0時に実行するバッチを実装しています。Userという構造体を定義し、gormのタグを使用してテーブルのカラムとマッピングしています。cliパッケージを使用して、コマンドライン引数を処理し、gormを使用してデータベースに接続しています。HasTable関数を使用して、テーブルが存在するかどうかを確認し、存在しない場合はCreateTable関数を使用してテーブルを作成しています。time.NewTicker関数を使用して、24時間ごとにイベントを発生させるTickerを作成しています。forループとselect文を使用して、Tickerのイベントを待機し、イベントが発生した場合にデータを更新する処理を実行しています。
Go言語を使いスマートフォンゲームのガチャを実装しています。 下記情報を元にガチャの実装を完成させてください。

本文

## 仕様
基本的には、渡されたrarityに対応する drops内のキャラクターの中からランダムに1体選び、
そのキャラクターのIDを返します。
ただし、pickUpsが存在する場合は、dropsから選ぶかpickUpsから選ぶか 1/2で決定し、
pickUpsから選んだ場合は、pickUpsから選んだキャラクターのIDを返します。

## Rollの引数
- rarityは 3, 4, 5 のいずれかの値の配列
- drops はレアリティに対応したキャラクターのID連想配列
- pickUps はレアリティに対応したピックアップ対象のキャラクターのID連想配列

## 途中まで実装したコード
type Rarity int

const (
    Rarity5 Rarity = 5
    Rarity4 Rarity = 4
    Rarity3 Rarity = 3
)

type GachaCharacterDrops map[Rarity][]uint32 type GachaCharacterRarity []Rarity

type GachaCharacterHandler struct { randomGenerator *rand.Rand }

func NewGachaCharacterHandler(randSource *rand.Source) GachaCharacterHandler {
  if randSource != nil {
    return GachaCharacterHandler{ randomGenerator: rand.New(*randSource), }
  }
  return GachaCharacterHandler{
    randomGenerator: rand.New(rand.NewSource(time.Now().UnixNano())),
  }
}

func (h GachaCharacterHandler) Roll(
  rarity GachaCharacterRarity,
  drops GachaCharacterDrops,
  pickUps GachaCharacterDrops,
) []uint32 {
  // TODO: IMPLEMENT ME
}

回答

A 以下は、上記のコードを完成させたものです。
Roll関数では、rarityに対応するdropsからランダムに1体選び、
pickUpsが存在する場合は、dropsから選ぶかpickUpsから選ぶか1/2で決定し、
pickUpsから選んだ場合は、pickUpsから選んだキャラクターのIDを返します。

type Rarity int

const (
    Rarity5 Rarity = 5
    Rarity4 Rarity = 4
    Rarity3 Rarity = 3
)

type GachaCharacterDrops map[Rarity][]uint32
type GachaCharacterRarity []Rarity

type GachaCharacterHandler struct {
    randomGenerator *rand.Rand
}

func NewGachaCharacterHandler(randSource *rand.Source) GachaCharacterHandler {
    if randSource != nil {
        return GachaCharacterHandler{
            randomGenerator: rand.New(*randSource),
        }
    }
    return GachaCharacterHandler{
        randomGenerator: rand.New(rand.NewSource(time.Now().UnixNano())),
    }
}

func (h GachaCharacterHandler) Roll(
    rarity GachaCharacterRarity,
    drops GachaCharacterDrops,
    pickUps GachaCharacterDrops,
) []uint32 {
    var results []uint32

    for _, r := range rarity {
        var candidates []uint32
        if pickUps != nil && h.randomGenerator.Intn(2) == 0 {
            candidates = pickUps[r]
        } else {
            candidates = drops[r]
        }
        if len(candidates) == 0 {
            continue
        }
        index := h.randomGenerator.Intn(len(candidates))
        results = append(results, candidates[index])
    }

    return results
}

簡単なテストコード

どうせならテストコードまで書いて欲しいと思うことは多いと思います。
こちらはあまり上手く行かない場合が多いですが、修正する前提で頼むとある程度それらしいものは書いてくれます。

Q 下記コードに対する テストコードを作成してください。

本文

func (h GachaCharacterHandler) Roll(rarity GachaCharacterRarity, drops GachaCharacterDrops, pickUps GachaCharacterDrops,  ([]uint32, error) { // validate params if rarity == nil { return nil, errors.New("rarity must not be nil") } for _, r := range rarity { if r != Rarity5 && r != Rarity4 && r != Rarity3 { return nil, errors.New("rarity must contain only 5, 4, 3") } } if drops == nil { return nil, errors.New("drops must not be nil") } for _, r := range []Rarity{Rarity5, Rarity4, Rarity3} { _, ok := drops[r] if !ok { return nil, errors.New("drops must contain all rarity") } } if pickUps != nil { for _, r := range []Rarity{Rarity5, Rarity4, Rarity3} { _, ok := pickUps[r] if !ok { return nil, errors.New("pickUps must contain all rarity") } } } results := make([]uint32, len(rarity)) // gacha without pickup if pickUps == nil { for i := range rarity { characterIDs := drops[rarity[i]] index := h.randomGenerator.Intn(len(characterIDs)) results[i] = characterIDs[index] } return results, nil } // gacha with pickup for i := range rarity { var characterIDs []uint32 // 50% chance to use pickup shouldUsePickUps := h.randomGenerator.Intn(2) == 0 if shouldUsePickUps && len(pickUps[rarity[i]]) > 0 { characterIDs = pickUps[rarity[i]] } else { characterIDs = drops[rarity[i]] } index := h.randomGenerator.Intn(len(characterIDs)) results[i] = characterIDs[index] } return results, nil }

回答

A 以下は、GachaCharacterHandler.Roll()メソッドのテストコードの例です。このテストコードでは、正常な入力と異常な入力の両方をテストしています。

func TestGachaCharacterHandler_Roll(t *testing.T) {
    // create test data
    drops := GachaCharacterDrops{
        Rarity5: []uint32{1, 2, 3},
        Rarity4: []uint32{4, 5, 6},
        Rarity3: []uint32{7, 8, 9},
    }
    pickUps := GachaCharacterDrops{
        Rarity5: []uint32{1},
        Rarity4: []uint32{4},
        Rarity3: []uint32{7},
    }
    rarity := GachaCharacterRarity{Rarity5, Rarity4, Rarity3}

    // create handler
    handler := GachaCharacterHandler{
        randomGenerator: rand.New(rand.NewSource(time.Now().UnixNano())),
    }

    // test normal input
    results, err := handler.Roll(rarity, drops, pickUps)
    if err != nil {
        t.Errorf("unexpected error: %v", err)
    }
    if len(results) != len(rarity) {
        t.Errorf("unexpected result length: %d", len(results))
    }
    for i, r := range rarity {
        if !contains(drops[r], results[i]) && !contains(pickUps[r], results[i]) {
            t.Errorf("unexpected result: %d", results[i])
        }
    }

    // test nil rarity
    _, err = handler.Roll(nil, drops, pickUps)
    if err == nil {
        t.Errorf("expected error, but got nil")
    }

    // test invalid rarity
    invalidRarity := GachaCharacterRarity{Rarity5, Rarity4, Rarity3, Rarity3}
    _, err = handler.Roll(invalidRarity, drops, pickUps)
    if err == nil {
        t.Errorf("expected error, but got nil")
    }

    // test nil drops
    _, err = handler.Roll(rarity, nil, pickUps)
    if err == nil {
        t.Errorf("expected error, but got nil")
    }

    // test invalid drops
    invalidDrops := GachaCharacterDrops{
        Rarity5: []uint32{1, 2, 3},
        Rarity4: []uint32{4, 5},
        Rarity3: []uint32{7, 8, 9},
    }
    _, err = handler.Roll(rarity, invalidDrops, pickUps)
    if err == nil {
        t.Errorf("expected error, but got nil")
    }

    // test invalid pickUps
    invalidPickUps := GachaCharacterDrops{
        Rarity5: []uint32{1, 2, 3},
        Rarity4: []uint32{4, 5},
        Rarity3: []uint32{7},
    }
    _, err = handler.Roll(rarity, drops, invalidPickUps)
    if err == nil {
        t.Errorf("expected error, but got nil")
    }
}

func contains(s []uint32, e uint32) bool {
    for _, v := range s {
        if v == e {
            return true
        }
    }
    return false
}

上記のテストコードでは、正常な入力と異常な入力の両方をテストしています。正常な入力では、ランダムにキャラクターIDを選択し、期待される結果を返すことを確認しています。異常な入力では、Roll()メソッドがエラーを返すことを確認しています。

自分が知らない単語を訊く

書き方はわかるが何と呼ぶかわからないというものを質問することができます。自分の知らないことをAIに訊くと、ハルシネーション(幻覚)がかなり怖いですが、たまに当たることがあります。

Q. このファイルは 管理画面における一覧画面、および詳細画面での振る舞いを定義するものです。 このようにメソッドをチェーンすることで 簡単に開発ができる手法を なんと呼ぶか教えてください。

回答

このファイルは、管理画面における一覧画面および詳細画面での振る舞いを定義するものです。
このようにメソッドをチェーンすることで簡単に開発ができる手法は、
フルエンチェイン (Fluent Interface) と呼ばれます。

フルエンチェインは、オブジェクト指向プログラミングにおいて、
メソッドチェーンを使用してコードをより簡潔で読みやすくする手法です。
この手法を使用することで、複雑な処理を行うコードをより簡単に理解できるようになります。
また、フルエンチェインは、コードの可読性を向上させるだけでなく、
コードの再利用性も高めることができます。

記事を書いた感想

過去のCopilotChatとのやり取りをエクスポートするのが大変でした、過去のこのやり取り例が誰かのCopilotライフの充実につながることを祈ります。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0