プログラムの事とか

お約束ですが「掲載内容は私個人の見解です」

GitHub Copilotのエージェントモードで遊んでみる

Visual Studio 2022でGitHub Copilotのエージェントモードで遊んでみます 一文字もコードを書かないことを目標に頑張ってみます

エージェントモードを使用する

learn.microsoft.com

以下では

私のお願い

Copilotの回答

という感じで表記します

プロジェクトを作る

チャットウィンドウでエージェントにお願いします

.NET最新でコンソールアプリのプロジェクトを作ってください。プロジェクト名はNabeで、フォルダは f:\temp の下に

.NET最新バージョンで「Nabe」という名前のコンソールアプリプロジェクトを f:\temp\Nabe に作成しました。ご希望の内容は完了しています。

できました

できたのですがVisual Studioはまだ何も表示していないので、表示してもらおうとしました

そのソリューションを開いてください

f:\temp\Nabe.sln というソリューションファイルを作成し、Nabe プロジェクトをソリューションに追加しました。ソリューションは開かれています。

プロジェクトはあったみたいですが、ソリューション(sln)が無かったようで作ってくれました。「ソリューションは開かれています」とありますが、Visual Studio上は変化ありません。たぶんターミナル上で開いているということでしょう。ということでVisual Studioで開いてもらいます

Visual Studioでソリューションを開いてください

Visual Studioでソリューションを開くには、エクスプローラーで「f:\temp\Nabe.sln」をダブルクリックするか、Visual Studioを起動して「ファイル」→「開く」→「プロジェクト/ソリューション」から「f:\temp\Nabe.sln」を選択してください。
AIから直接Visual Studioを起動することはできませんが、上記の手順で簡単に開くことができます。

Copilot様は開くことができないようなので、私が開くことにします。マウス操作だけなのでセーフ

コードを書いてもらう

ソリューションを開いてコードを見てみると

Console.WriteLine("Hello, World!");

となっています。テンプレートのものなのでまぁそうですよね

ここからCopilot様にプログラムを書いていってもらいましょう

1から20まで順番に出力してください

C# で 1 から 20 まで順番に出力するには、for ループを使って Console.WriteLine で出力します。Program.cs を次のように修正します。
for (int i = 1; i <= 20; i++)
{
    Console.WriteLine(i);
}

3の倍数と3のつく数字のときだけアホになってください

「3の倍数」と「3のつく数字」のときだけ「アホ」など特別な出力をし、それ以外は数字をそのまま出力するには、以下のように修正します。
for (int i = 1; i <= 20; i++)
{
    if (i % 3 == 0 || i.ToString().Contains("3"))
    {
        Console.WriteLine("アホ");
    }
    else
    {
        Console.WriteLine(i);
    }
}

なるほど

1を"いち"、2を"に"という感じにできますか?

はい、1~20の数字を日本語の読み(例: 1→"いち", 2→"に")で出力し、3の倍数または3のつく数字のときは「アホ」と出力するようにできます。
var readings = new string[] { "", "いち", "に", "さん", "よん", "ご", "ろく", "なな", "はち", "きゅう", "じゅう", "じゅういち", "じゅうに", "じゅうさん", "じゅうよん", "じゅうご", "じゅうろく", "じゅうなな", "じゅうはち", "じゅうきゅう", "にじゅう" };

for (int i = 1; i <= 20; i++)
{
    Console.WriteLine(i);
    if (i % 3 == 0 || i.ToString().Contains("3"))
    {
        Console.WriteLine("アホ");
    }
    else
    {
        Console.WriteLine(readings[i]);
    }
}

あとちょっと

アホになるところは"アホ"と出力するのではなく、例えば"さん"を"さ~~~ん"みたいな感じにしたいです

「アホ」になるときは、該当する日本語読みの母音を伸ばして(例: "さん"→"さ~~~ん")出力し、それ以外は通常の日本語読みを出力するように修正します。
• 3の倍数または3のつく数字のとき、該当する日本語読みの最初の母音を「~~~」で伸ばします。
using System;
using System.Linq;

class Program
{
    static void Main()
    {
        string StretchVowel(string word)
        {
            if (string.IsNullOrEmpty(word)) return word;
            // 母音リスト
            var vowels = new[] { 'あ', 'い', 'う', 'え', 'お' };
            for (int i = 0; i < word.Length; i++)
            {
                if (vowels.Contains(word[i]))
                {
                    // 最初の母音を伸ばす
                    return word.Substring(0, i + 1) + "~~~" + word.Substring(i + 1);
                }
            }
            return word;
        }

        var readings = new string[] { "", "いち", "に", "さん", "よん", "ご", "ろく", "なな", "はち", "きゅう", "じゅう", "じゅういち", "じゅうに", "じゅうさん", "じゅうよん", "じゅうご", "じゅうろく", "じゅうなな", "じゅうはち", "じゅうきゅう", "にじゅう" };

        for (int i = 1; i <= 20; i++)
        {
    Console.WriteLine(i);
            string reading = readings[i];
            if (i % 3 == 0 || i.ToString().Contains("3"))
            {
                Console.WriteLine(StretchVowel(reading));
            }
            else
            {
                Console.WriteLine(reading);
            }
        }
    }
}

おおぅ

ちなみに実行結果はこんな感じ

いち
に
さん
よん
ご
ろく
なな
はち
きゅう~~~
じゅう
じゅういち
じゅう~~~に
じゅう~~~さん
じゅうよん
じゅう~~~ご
じゅうろく
じゅうなな
じゅう~~~はち
じゅうきゅう
にじゅう

おしい!

iが一桁の時(3,6,9)がそのまま出てしまっています。この3つは母音が無いので特別な処理が必要そうです

ご指摘の通り、「さん」「ろく」「きゅう」など一桁の3の倍数は母音が最初に現れないため、現状の StretchVowel では伸ばせません。
これら(3, 6, 9)は特別に「さ~~~ん」「ろ~~~く」「きゅ~~~う」と出力するよう個別対応を追加します。
using System;
using System.Linq;

class Program
{
    static void Main()
    {
        string StretchVowel(string word, int i)
        {
            // 特別対応: 3, 6, 9
            if (i == 3) return "さ~~~ん";
            if (i == 6) return "ろ~~~く";
            if (i == 9) return "きゅ~~~う";
            if (string.IsNullOrEmpty(word)) return word;
            // 母音リスト
            var vowels = new[] { 'あ', 'い', 'う', 'え', 'お' };
            for (int j = 0; j < word.Length; j++)
            {
                if (vowels.Contains(word[j]))
                {
                    // 最初の母音を伸ばす
                    return word.Substring(0, j + 1) + "~~~" + word.Substring(j + 1);
                }
            }
            return word;
        }

        var readings = new string[] { "", "いち", "に", "さん", "よん", "ご", "ろく", "なな", "はち", "きゅう", "じゅう", "じゅういち", "じゅうに", "じゅうさん", "じゅうよん", "じゅうご", "じゅうろく", "じゅうなな", "じゅうはち", "じゅうきゅう", "にじゅう" };

        for (int i = 1; i <= 20; i++)
        {
            string reading = readings[i];
            if (i % 3 == 0 || i.ToString().Contains("3"))
            {
                Console.WriteLine(StretchVowel(reading, i));
            }
            else
            {
                Console.WriteLine(reading);
            }
        }
    }
}

実行結果

いち
に
さ~~~ん
よん
ご
ろ~~~く
なな
はち
きゅ~~~う
じゅう
じゅういち
じゅう~~~に
じゅう~~~さん
じゅうよん
じゅう~~~ご
じゅうろく
じゅうなな
じゅう~~~はち
じゅうきゅう
にじゅう

出力はこんな感じでOKとしましょう

出力をもう少しそれっぽくしてみる

上のコードでは実行すると一気に全部出力して終わってしまいます

1秒に1行ずつ出力するようにしてください

1秒ごとに1行ずつ出力するには、ループ内でThread.Sleep(1000);を使います。
System.Threading名前空間のusingも追加します。
for (int i = 1; i <= 20; i++)
        {
            string reading = readings[i];
            if (i % 3 == 0 || i.ToString().Contains("3"))
            {
                Console.WriteLine(StretchVowel(reading, i));
            }
            else
            {
                Console.WriteLine(reading);
            }
            Thread.Sleep(1000);
        }

Thread.Sleep(1000);追加しただけですね。それはそう

次は

2行目以降は1行目を消してそこに出力することはできますか?

はい、2行目以降で前の行を消して同じ位置に新しい出力を表示するには、コンソールのカーソル位置を制御する必要があります。
Console.SetCursorPosition を使い、1行目に常に上書きする形にできます。
以下のように修正します:
• 1行目はそのまま出力
• 2行目以降は Console.SetCursorPosition(0, Console.CursorTop - 1); で1行上に戻り、Console.Write(new string(' ', Console.WindowWidth)); で行を消してから新しい出力を上書き
この方法で、常に1行目に最新の出力が表示されるようにします。

録画したいので、開始時に5秒のカウントダウンをしてからこの処理を実行してください

5秒のカウントダウンを開始時に表示し、その後に既存の処理を実行するように修正します。
カウントダウンも1秒ごとに同じ行に上書き表示します。
using System;
using System.Linq;
using System.Threading;

class Program
{
    static void Main()
    {
        // 5秒カウントダウン
        for (int sec = 5; sec >= 1; sec--)
        {
            Console.Write($"録画開始まで: {sec}秒   ");
            Thread.Sleep(1000);
            Console.SetCursorPosition(0, Console.CursorTop);
        }
        // カウントダウン表示を消す
        Console.Write(new string(' ', Console.WindowWidth));
        Console.SetCursorPosition(0, Console.CursorTop);

        string StretchVowel(string word, int i)
        {
            // 特別対応: 3, 6, 9
            if (i == 3) return "さ~~~ん";
            if (i == 6) return "ろ~~~く";
            if (i == 9) return "きゅ~~~う";
            if (string.IsNullOrEmpty(word)) return word;
            // 母音リスト
            var vowels = new[] { 'あ', 'い', 'う', 'え', 'お' };
            for (int j = 0; j < word.Length; j++)
            {
                if (vowels.Contains(word[j]))
                {
                    // 最初の母音を伸ばす
                    return word.Substring(0, j + 1) + "~~~" + word.Substring(j + 1);
                }
            }
            return word;
        }

        var readings = new string[] { "", "いち", "に", "さん", "よん", "ご", "ろく", "なな", "はち", "きゅう", "じゅう", "じゅういち", "じゅうに", "じゅうさん", "じゅうよん", "じゅうご", "じゅうろく", "じゅうなな", "じゅうはち", "じゅうきゅう", "にじゅう" };

        for (int i = 1; i <= 20; i++)
        {
            string reading = readings[i];
            string output = (i % 3 == 0 || i.ToString().Contains("3")) ? StretchVowel(reading, i) : reading;
            if (i == 1)
            {
                Console.WriteLine(output);
            }
            else
            {
                Console.SetCursorPosition(0, Console.CursorTop - 1);
                Console.Write(new string(' ', Console.WindowWidth));
                Console.SetCursorPosition(0, Console.CursorTop - 0);
                Console.WriteLine(output);
            }
            Thread.Sleep(1000);
        }
    }
}

まとめ

あまりナベアツっぽくないですが、まぁこんな感じで完成としましょう。事前調査とか全くやらずに行き当たりばったりで試してみましたが、目標通りコードは一文字も書かずにできました。あのネタは30番台までいかないと、とかそこまでやるとこのコードだとツライかなとか、そういうのはまぁ無視で

実際に自分でコーディングするときはどのように記述するかなー、と考えながらCopilot様にお願いしていましたが結構面白かったです

おまけ

Googleで検索すると・・・

AIの世界では、1,2,アホ,…が正解なの・・・?

条件判定の話

久しぶりのネタは、私のようなプログラマー歴二桁年の人には当たり前なやつだと思うのですが、最近はあまり気にしないのかなという感じなやつです

複数の条件を判定する時のやつ

サンプルはC#で書きますが少なくともCでも同じ感じだったはずなので、たいていの言語で同じでしょう。たぶんきっと

class Test
{
    public bool CheckA(int val)
    {
        Console.WriteLine("CheckA");
        return val % 2 == 0;
    }
    public bool CheckB(int val)
    {
        Console.WriteLine("CheckB");
        return val % 3 == 0;
    }
}

こんなクラスを作って

var test = new Test();
for (var i = 1; i < 10; i++)
{
    Console.WriteLine(i);
    if (test.CheckA(i) && test.CheckB(i))
    {
        Console.WriteLine("Hit");
    }
    else
    {
        Console.WriteLine("Miss");
    }
}

このように呼んであげると、コンソールにはどう表示されるか?

抜粋ですが

4
CheckA
CheckB
Miss
5
CheckA
Miss
6
CheckA
CheckB
Hit

こんな感じになります

if (test.CheckA(i) && test.CheckB(i)) の左側の比較 (CheckA) だけで結果が確定する場合、CheckB は呼ぶ必要が無いので呼ばれません

呼ばれないので、例えば i が5の時に CheckB で例外が発生してしまう場合でも正しく動作します

public bool CheckB(int val)
{
    Console.WriteLine("CheckB");
    if(val == 5) throw new Exception("Error");  // この例外は飛ばない
    return val % 3 == 0;
}

if (test.CheckA(i) && test.CheckB(i)) の CheckA が false の場合は CheckB はどうなっても関係ない

if (test.CheckA(i) || test.CheckB(i)) の CheckA が true の場合も同様

っていうの、みんな普通に考えて書くよね?と思っていたんですが、最近はあまり気にしないんでしょうか

応用編

応用というほどの応用でもないですが・・・

比較回数

先ほどの Test クラスの比較処理が呼ばれた回数を数えてみます

class Test
{
    public int TotalCheckCount { get; set; }
    public bool CheckA(int val)
    {
        Console.WriteLine("CheckA");
        TotalCheckCount++;
        return val % 2 == 0;
    }
    public bool CheckB(int val)
    {
        Console.WriteLine("CheckB");
        TotalCheckCount++;
        return val % 3 == 0;
    }
}

こんな感じにして、ループが抜けた後 Console.WriteLine($"TotalCheckCount: {test.TotalCheckCount}"); でコンソールに出力すると

TotalCheckCount: 13

とでるので、合計で13回関数が呼ばれたことになります

続いて if (test.CheckA(i) && test.CheckB(i))if (test.CheckB(i) && test.CheckA(i)) にして試してみます (CheckA と CheckB を入れ替えただけ)

TotalCheckCount: 12

ということで比較処理が1回減りました

先ほど書きましたが一つ目の条件で分岐が確定する場合二つ目の条件は比較しないので、このサンプルの場合3の倍数の方が少ない分比較処理が減ったということですね

比較処理の負荷が同程度なら呼ばれる回数が減ったほうが速く動いてくれるわけですから、こっちの方がうれしいですね

負荷が同程度になることなんてめったにないですから、データの分布とか負荷とかそのあたりを考慮して自分で最適化してあげる必要はあると思います

null チェック

testがnullの可能性がある場合、比較処理を呼ぶ前にnullチェックが必要になるでしょう

Test? test = null;
for (var i = 1; i < 10; i++) { 
    if (test != null && test.CheckA(i) && test.CheckB(i))
・・・

上のように書けば CheckA/B を呼ぶ時にぬるぽは発生しませんし、コンパイラも警告は出さないでしょう

これは一番左のnullチェックでその後の式では非nullが確定しているからです

まぁC#であれば最近は

if ((test?.CheckA(i) ?? false) && test.CheckB(i))

って書いてしまったりするわけですが、やっていることは同じ

それが正義かというと・・・

効率だけを考えるなら一番負荷が減るように書けばいいでしょう

ですがプログラムは他人 (1年後の自分を含む) が見ることがあるので、解読が必要なものはあまり好まれません

例えば、「葛飾柴又で生まれて、葛飾柴又で育った、寅次郎という名前かどうか」を判定するように仕様書に描いてあった時

if ( 名前 is 寅次郎 && 育ち is 葛飾柴又 && 生まれ is 葛飾柴又)

と書くより

if (生まれ is 葛飾柴又 && 育ち is 葛飾柴又 && 名前 is 寅次郎)

と書いた方が分かりやすいでしょう。たとえ 寅次郎という名前の人が一人しかいないとしても

このあたりのコードの読みやすさと効率の良さのバランスのとり方がプログラマーの腕の見せ所かもしれませんね

おしまい

BingのAIチャット機能が予想以上にできる子だった件

AIに絵を描かせて遊ぶブームが来たとおもったら、AIとチャットで遊ぶ人が増え、気づいたらBingの検索がそんな感じになっていました

というのをぼーっと外から観測していたのですが、かなりできる子らしいので使ってみたら本当にできる子だったのでその勢いのままブログを書きます

以下はほぼ「あ…ありのまま 今 起こったことを話すぜ!」な話です

あるcss素人の苦悩

私は年に数回くらいしかcssを書かない、css素人どころかまだよちよち歩きの赤ちゃんレベルです

次にcssを書こうと思うと前回の知識がすべてリセットされており、同じことを何度も検索しなくてはなりません

赤ちゃんというより老人レベルなのかもしれません

そんなcss老人が<input type="text"/>の見た目を少し変えてみます

input[type=text]{
  width: 100%;
  height: 40px;
  background-color: #ffffff;
  border: 1px solid #d1d1d4;
  transition: all 0.2s;
}
input[type=text]:focus {
  border-color: #21bdd1;
}

私の脳内では枠線がグレーで、フォーカスを当てると水色に変わるテキストボックスがレンダリングされています

さっそく試してみます

黒?

なんか黒い枠が上に乗っている

枠線に注目するとフォーカスを外すときに黒い枠の下に水色の枠が見えます

borderの上に何かが乗っています。昔もこんなのに苦しめられた記憶がありますがcss老人級の私の脳内に解決策はありません

いつもならここでググるわけなんですが"よくわからない枠"をどういうキーワードで検索すればいいのか、という問題にいつも苦しめられます。borderではない枠の正しい固有名称がわかるくらいならcssに書いているYO!

ということでせっかく応募したんだし試しにBing様に聞いてみました。自然言語で検索できるらしいですし、変な回答が返ってきたらそれはそれで面白いし

Bing様!

<input>タグでフォーカスを当てた時に出る黒い枠を表示しないようにしたい

outline!そういえばそうだった!!

ってことで一発で答え教えてくれました。「フォーカスを当てる」とか業界の方言?みたいな表現をしてみましたが無問題ですね

css老人の挑戦

無事outline: none;を付けることができたので、今度は<td/>の中に<input/>を入れてみました

以下は必要なところだけ

<td><input type="text"/></td>
td{
  background-color:white;
  padding:12px;
}
input[type=text]{
  width: 100%;
  height: 40px;
}

私の脳内には<td>の中12pxの隙間をいれて横幅いっぱいに配置されるテキストボックスがレンダリングされています

試してみます

はみだした

はみ出しているYO!

これも過去に何度も経験している現象なんですが、残念ながら私の脳内には経験した記憶しかありません

助けてBingえもん!

迷いはありません。css老人の私はAIに介護してもらうことにすでに何の抵抗もありません

<td>タグの中に<input>タグをMarginつけて横いっぱいに配置したいです

これは質問が悪かった気がします。なんか頑張ってstackoverflowに英語で質問したけどちゃんとした質問になっていなくて残念な回答をもらったときを思い出します

回答の2番目は私のcssと一緒ですしそれではダメだったので、もっと具体的に聞いてしまいます

<td>タグにpaddingを指定して中の<input>をwidth:100%にしたら<input>がはみ出してしまいました

すでに質問ではなく現象しか書いていませんが、どうやらBing様はチャットを文脈で理解してくれている風もあるので完璧な回答を返してくれました

box-sizing:border-boxですね。しかも原因なども細かく教えてくれました(Bingの検索で出てきたページの人が教えてくれているんですが)

まとめ

ということでこれからわからないことがあったらとりあえずBing神に聞いてみる生活が続きそうです

っていうかもうBing神がプログラム作ってくれないかな