小学校プログラミング授業用の組み合わせ問題を解くコードをGitHub Copilotと一緒に書いてみた

# はじめに

GitHub Copilotくらい経験しとかないといけない気がしたので、やってみた「はじめてのCopilot体験レポート」です。

題材としてK3Tunnelというサイトで公開されている小学校プログラミング授業用のミッションのひとつである「[家電買いかえ大作戦](https://k3tunnel.com/mission/101/)」というミッションを使ってみました。

なお「新しいBing」に聞いてみたエントリーはこちらです。

https://qiita.com/cocoakamen/items/8c7b7d936da75c482176

「新しいBing」が教えてくれたコードの一部をスタートにGitHubのCopilotと一緒にコードを書いてみました。

本エントリ執筆時点、[Copilot X](https://github.blog/jp/2023-03-23-github-copilot-x-the-ai-powered-developer-experience/)が発表されていますが、本エントリで試しているのは[Copilot](https://github.com/features/copilot)です。

# 問題と期待する答え

## 「新しいBing」へのインプット
以下をBingチャットで聞いてみました。ミッションの中では満足ポイントという言葉を使っていますが、満足度のほうが一般的かなと思って置き換えています。

***
たかしくんは、おとうさん、おかあさんと一緒に暮らす3人家族です。

たかしくん家族は、掃除機、クッション、ホームベーカリーを買い替えることにしました。予算は10万円です。

それぞれの候補は以下の通りです。名前、値段とカッコ内の数字は、順番にそれを買った時のたかしくん、おとうさん、おかあさんの満足度を表しています。

掃除機の候補
１ ロボット型そうじ機 80000円 (100 100 70)
２ 高性能そうじ機 60000円 (100 80 90)
３ 軽くてかっこいいそうじ機 30000円 (50 60 90)
４ 旧型そうじ機 20000円 (30 50 50)

クッションの候補
１ マッサージ機能付クッション 50000円 (50 100 60)
２ 大きいくまさんクッション 30000円 (100 50 80)
３ マイクロビーズクッション 10000円 (70 70 80)
４ 普通のシンプルクッション 5000円 (50 70 50)

ホームベーカリーの候補
１ 本格派具材自動投入HB 40000円 (100 70 50)
２ お手軽派HB 30000円 (70 70 80)
３ ご飯でパンが焼けるHB 20000円 (60 50 70)
４ 食パン焼くだけHB 5000円 (50 100 60)

満足度の最大値が等しい場合には、たかしくんの満足度を優先します。

すべての組み合わせで、合計金額と満足度合計値を計算し、商品の名前と合計金額、満足度合計値を表示するJavaScriptのプログラムを作ってください。
***

## 最適な組み合わせとして出てくることを期待しているもの

予算内で満足度合計最大になる組み合わせは、二つあり、そのうちたかしくんの満足度が高い組み合わせはこちらになります。

+ 高性能そうじ機
+ 大きいくまさんクッション
+ 食パン焼くだけHB
+ 満足度合計	710
+ 価格の合計	95000
+ たかしくん満足度合計 250


# 書いていく様子

## スタート

データをつくるところは、Bingが作ってくれたコードをそのまま使うことにします。このあたり、意外とめんどくさいので助かります。コメントだけ追加しました。

```javascript
// 掃除機の候補
const candidates = [
  {
    name: "ロボット型そうじ機",
    price: 80000,
    satisfaction: [100, 100, 70],
  },
  {
    name: "高性能そうじ機",
    price: 60000,
    satisfaction: [100, 80, 90],
  },
  {
    name: "軽くてかっこいいそうじ機",
    price: 30000,
    satisfaction: [50, 60, 90],
  },
  {
    name: "旧型そうじ機",
    price: 20000,
    satisfaction: [30, 50, 50],
  },
];

// クッションの候補
const cushions = [
  {
    name: "マッサージ機能付クッション",
    price: 50000,
    satisfaction: [50, 100, 60],
  },
  {
    name: "大きいくまさんクッション",
    price: 30000,
    satisfaction: [100, 50, 80],
  },
  {
    name: "マイクロビーズクッション",
    price: 10000,
    satisfaction: [70, 70, 80],
  },
  {
    name: "普通のシンプルクッション",
    price: 5000,
    satisfaction: [50, 70, 50],
  },
];

// ホームベーカリーの候補
const homeBakeries = [
   {
      name:"本格派具材自動投入HB",
      price :40000,
      satisfaction:[100,70,50]
   },
   {
      name:"お手軽派HB",
      price :30000,
      satisfaction:[70,70,80]
   },
   {
      name:"ご飯でパンが焼けるHB",
      price :20000,
      satisfaction:[60,50,70]
   },
   {
      name:"食パン焼くだけHB",
      price :5000,
      satisfaction:[50,100,60]
   }
];
```

## 全部の組み合わせを作る

Bingの作ってくれたコードは、For文で全組み合わせをつくって比較するというものでした。その方針に従うことにします。

コメント書いてみます
```javascript
// 掃除機、クッション、ホームベーカリーの組み合わせを作る
```

候補が提案されました。配列につっこむのでしょう。違和感ないので確定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308852/17a5cf02-e33d-9673-7445-14b3dda63656.png)

For文が出てきました。違和感ないので確定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308852/38740360-76e6-a0e7-6cbc-2c4cfd490854.png)

三重ループが出てきました。違和感ないので確定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308852/7ad64a3c-2209-1910-b7f0-06ec0a18c2fb.png)

全員の満足度合計とたかしくんの満足度があったほうがよさそうです。

とりあえず「全員の満足度合計」をコメントに足してみましたが、これは、たかしくん満足度合計です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308852/2caec20b-1dd0-86e4-abf2-0dc34f1d02fd.png)

配列の要素を全部足す関数あった方がいいかなと思って、途中までコメント書いたら候補が現れたので確定します。（グレーアウトしているところが候補表示です）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308852/2fd3770e-40d4-292b-800a-705b51767531.png)

sumという名前の関数ってどきどきしますが、確定してしまいます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308852/887acd45-cc95-c2d7-a19b-e1f2c41f97ef.png)

そのままただひたすら確定していった結果
```javascript
// 配列の要素を全部足し合わせる関数
const sum = (array) => {
  let result = 0;
  for (let i = 0; i < array.length; i++) {
    result += array[i];
  }
  return result;
};
```

ふたたび満足度合計に戻ってくるsum関数を使ってくれそうですが、最後が[で終わっているのが嫌な予感。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308852/af2e5e1d-24ee-f8b7-857d-3ecd9eaef7e9.png)

[を消してエンターを押した結果。改行はおかしいものの、あっていそうです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308852/096587cd-11b0-b013-9bda-a4e3a64a3bf0.png)

）を追加してみたら＋が出てきました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308852/40545a21-f0b6-fb42-c1a2-1a9e3ca42131.png)

ふむふむ。スペースとタブ（確定）のみでコードは作られていき、改行だけ直した結果こうなりました。
```javascript
for (let i = 0; i < candidates.length; i++) {
  for (let j = 0; j < cushions.length; j++) {
    for (let k = 0; k < homeBakeries.length; k++) {
      const combination = {
        vacuumCleaner: candidates[i],
        cushion: cushions[j],
        homeBakery: homeBakeries[k],
        // 全員の満足度合計
        totalSatisfaction: sum(candidates[i].satisfaction) + sum( cushions[j].satisfaction) + sum(homeBakeries[k].satisfaction),
      };
      combinations.push(combination);
    }
  }
}
```
つづいて「たかしくんの満足度合計」もコメント追加したところ、こちらはよさそうなので確定
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308852/2140c162-ca5c-3d97-ee0d-6aa4c436ace6.png)

予算も条件にいれないといけないので価格合計も追加。こちらもコメント追加したらよさそうなのがでてきたのでそのまま確定。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308852/0eb11496-7355-f124-f037-3bc45a8cfd5d.png)

## 候補を絞り込んでいく

若干の試行錯誤をしつつ、コメント部分だけを書き、コードはすべて出てきたものを確定していって作っていきました。

最終的にこんな感じに。mapとmaxとかfilterとか雰囲気だけで確定しました。
```javascript
const under10 = combinations.filter((combination) => {
  return combination.vacuumCleaner.price + combination.cushion.price + combination.homeBakery.price <= 100000;
});

// 全員の満足度合計が最大の組み合わせを抽出
const maxTotalSatisfaction = Math.max(...under10.map((combination) => combination.totalSatisfaction));
const bestCombination = under10.filter((combination) => combination.totalSatisfaction === maxTotalSatisfaction);

// 結果を表示
console.log("全員の満足度合計が最大の組み合わせは以下の通りです");
console.log(bestCombination);

// bestCombinationの中からたかしくんの満足度合計が最大の組み合わせを抽出
const maxTakashiSatisfaction = Math.max(...bestCombination.map((combination) => combination.takashiSatisfaction));
const bestCombinationForTakashi = bestCombination.filter((combination) => combination.takashiSatisfaction === maxTakashiSatisfaction);

// 結果を表示
console.log("たかしくんの満足度合計が最大の組み合わせは以下の通りです");
console.log(bestCombinationForTakashi);
```

## 実行結果

Arrayになってしまっているのが若干残念ですが、まあいいのではないでしょうか。
```
全員の満足度合計が最大の組み合わせは以下の通りです
[
  {
    vacuumCleaner: { name: '高性能そうじ機', price: 60000, satisfaction: [Array] },    
    cushion: { name: '大きいくまさんクッション', price: 30000, satisfaction: [Array] },
    homeBakery: { name: '食パン焼くだけHB', price: 5000, satisfaction: [Array] },      
    totalSatisfaction: 710,
    takashiSatisfaction: 250
  },
  {
    vacuumCleaner: { name: '高性能そうじ機', price: 60000, satisfaction: [Array] },
    cushion: { name: 'マイクロビーズクッション', price: 10000, satisfaction: [Array] },
    homeBakery: { name: 'お手軽派HB', price: 30000, satisfaction: [Array] },
    totalSatisfaction: 710,
    takashiSatisfaction: 240
  }
]
たかしくんの満足度合計が最大の組み合わせは以下の通りです
[
  {
    vacuumCleaner: { name: '高性能そうじ機', price: 60000, satisfaction: [Array] },
    cushion: { name: '大きいくまさんクッション', price: 30000, satisfaction: [Array] },
    homeBakery: { name: '食パン焼くだけHB', price: 5000, satisfaction: [Array] },
    totalSatisfaction: 710,
    takashiSatisfaction: 250
  }
]
```

## 自分でコードを書いたのはどこだったか

全員の満足度合計を求めるところが、そのままではうまくいかなかったので、`[`を削除し、確定していく途中で`)`を足しました。それ以外は、日本語のコメントを書き、エンターとタブ（確定）を押しただけです。

## コード全量

<details><summary>コード全量はこちらです。</summary>

```javascript
// 掃除機の候補
const candidates = [
  {
    name: "ロボット型そうじ機",
    price: 80000,
    satisfaction: [100, 100, 70],
  },
  {
    name: "高性能そうじ機",
    price: 60000,
    satisfaction: [100, 80, 90],
  },
  {
    name: "軽くてかっこいいそうじ機",
    price: 30000,
    satisfaction: [50, 60, 90],
  },
  {
    name: "旧型そうじ機",
    price: 20000,
    satisfaction: [30, 50, 50],
  },
];

// クッションの候補
const cushions = [
  {
    name: "マッサージ機能付クッション",
    price: 50000,
    satisfaction: [50, 100, 60],
  },
  {
    name: "大きいくまさんクッション",
    price: 30000,
    satisfaction: [100, 50, 80],
  },
  {
    name: "マイクロビーズクッション",
    price: 10000,
    satisfaction: [70, 70, 80],
  },
  {
    name: "普通のシンプルクッション",
    price: 5000,
    satisfaction: [50, 70, 50],
  },
];

// ホームベーカリーの候補
const homeBakeries = [
   {
      name:"本格派具材自動投入HB",
      price :40000,
      satisfaction:[100,70,50]
   },
   {
      name:"お手軽派HB",
      price :30000,
      satisfaction:[70,70,80]
   },
   {
      name:"ご飯でパンが焼けるHB",
      price :20000,
      satisfaction:[60,50,70]
   },
   {
      name:"食パン焼くだけHB",
      price :5000,
      satisfaction:[50,100,60]
   }
];

// 配列の要素を全部足し合わせる関数
const sum = (array) => {
  let result = 0;
  for (let i = 0; i < array.length; i++) {
    result += array[i];
  }
  return result;
};


// 掃除機、クッション、ホームベーカリーの組み合わせを作る
const combinations = [];
for (let i = 0; i < candidates.length; i++) {
  for (let j = 0; j < cushions.length; j++) {
    for (let k = 0; k < homeBakeries.length; k++) {
      const combination = {
        vacuumCleaner: candidates[i],
        cushion: cushions[j],
        homeBakery: homeBakeries[k],
        // 全員の満足度合計
        totalSatisfaction: sum(candidates[i].satisfaction) + sum( cushions[j].satisfaction) + sum(homeBakeries[k].satisfaction),
        // たかしくんの満足度合計
        takashiSatisfaction: candidates[i].satisfaction[0] + cushions[j].satisfaction[0] + homeBakeries[k].satisfaction[0],
        // 価格合計
        totalPrice: candidates[i].price + cushions[j].price + homeBakeries[k].price,
      };
      combinations.push(combination);
    }
  }
}

// 10万円以下の組み合わせを抽出
const under10 = combinations.filter((combination) => {
  return combination.vacuumCleaner.price + combination.cushion.price + combination.homeBakery.price <= 100000;
});

// 全員の満足度合計が最大の組み合わせを抽出
const maxTotalSatisfaction = Math.max(...under10.map((combination) => combination.totalSatisfaction));
const bestCombination = under10.filter((combination) => combination.totalSatisfaction === maxTotalSatisfaction);

// 結果を表示
console.log("全員の満足度合計が最大の組み合わせは以下の通りです");
console.log(bestCombination);

// bestCombinationの中からたかしくんの満足度合計が最大の組み合わせを抽出
const maxTakashiSatisfaction = Math.max(...bestCombination.map((combination) => combination.takashiSatisfaction));
const bestCombinationForTakashi = bestCombination.filter((combination) => combination.takashiSatisfaction === maxTakashiSatisfaction);

// 結果を表示
console.log("たかしくんの満足度合計が最大の組み合わせは以下の通りです");
console.log(bestCombinationForTakashi);
```
***
</details>

# まとめ

Copilotがすごいという噂は聞いていましたが、すごかったです。
[Copilot X](https://github.blog/jp/2023-03-23-github-copilot-x-the-ai-powered-developer-experience/)になったら、もっとすごくなるのでしょう。

Bingに聞いてみたのとあわせて考えると、今のところ「問題を定義し、やりたいことを的確に言語化する」ところは、人間ががんばる領域として残るのかなという印象です。

現時点、本当の初心者が、最初からうまくAIを使いこなしたプログラミングができるかというと、そんなことはないだろうなと思います。小学校授業でのプログラミング体験を、どこを重視した体験とするべきなのか。あらためて考えるよいきっかけになるなと思いました。