ローカル環境で「Gemma4」を動かしてみた!

ローカル環境で「Gemma4」を動かしてみた!e4bと26bの精度やコーディング・自己デバッグ能力を検証

こんにちは!今回は、ローカル環境で実行可能な大規模言語モデル「Gemma4」の動作検証を行いました。

パラメータサイズの異なる「Gemma4 e4b」「Gemma4 26b」の2つのモデルに対して、日常的な会話や長文の要約、そして実践的なC#のプログラミングタスクを与えてみました。
特にプログラミングの検証では、「AIが書いたコードでエラーが出た際、エラーメッセージをそのまま返して修正できるか(自己デバッグ能力)」という実践的なやり取りも試しています。

それぞれのインプットとアウトプットをまとめましたので、ローカルLLMの導入を検討している方の参考になれば幸いです!


💬 1. Gemma4 e4b の検証:日常会話と文章要約

まずは軽量な「Gemma4 e4b」モデルに対して、基本的な会話とニュース記事の要約をお願いしてみました。

👤 インプット

こんにちは、何ができますか?

🤖 アウトプット

こんにちは!私はGoogleによってトレーニングされた、大規模言語モデルです。
私にできることはたくさんありますよ。例えば:

  • 質問に答える: どんなことでも聞いてください。知識に関する質問はもちろん、雑談やアイデア出しにも対応できます。
  • 文章を作成する: 小説、詩、メール、レポートなど、様々な種類の文章を作成できます。
  • 翻訳する: 複数の言語間で翻訳が可能です。
  • 要約する: 長い文章を短くまとめることができます。
  • コードを書く: プログラミングに関する質問や簡単なコードの作成も可能です。
  • ゲームをする: 簡単なテキストベースのゲームもできます。

何か私に試してみたいことはありますか? 例えば、「今日の天気は?」とか「猫について教えて」のように聞いてみてください。

非常に自然な日本語で返答してくれました。続いて、実際のニュース記事を渡して要約させてみます。

👤 インプット

次の文章を要約してください。

###ニュース記事
マクドナルドと「ポケットモンスター」のコラボレーションとして、8月9日から『ポケモンカードゲーム』の限定カード全5種類が配布されている。

マクドナルドとメルカリは共同で転売などの対策を行うとしていたが、連休初日となる9日、マクドナルドには多くの人が殺到し、混乱が発生。

マクドナルド公式アプリには、限定カードの早期配布終了や混雑に対する謝罪文が掲載されるまでになっている。

『ポケカ』コラボ開始前に、マックとメルカリは共同で対策を実施
今回の「ポケットモンスター」とのコラボレーションは、8月8日から実施。通常期間はピカチュウをはじめとするポケモンのおもちゃがおまけとなっており、『ポケモンカードゲーム』の配布は9日(土)~11日(月)の限定の施策として行われている。

今回のおまけの需要が高いことは認識されており、マクドナルドとメルカリは共同で対策を行うと発表。

マクドナルド側は1人5つまでという購入制限を設けたほか、サイト上で「転売または再販売その他営利を目的としたご購入はご遠慮ください。また、食べきれない量のご注文もご遠慮いただけますようお願いします」と注意喚起。

メルカリ側も注意喚起を行ったほか、悪質な出品の削除を行うと宣言していた。

キャンペーン初日に大きな混乱が発生、各地のマックに人が押し寄せる
事前の対策をもってキャンペーンがスタートした8月9日。SNS上では、各地のマクドナルドに人が殺到している声が上がった。

その中では、店舗によってはアプリからのモバイルオーダーを行った注文者が受け取り口に殺到しているとの投稿や、カードだけを抜いて商品を捨てている注文者がいるとの投稿も多くされている。

そういった声は連休初日のSNSで大きく注目を集めることとなり、15時ごろにはXのトレンド1位に「ハッピーセット」、5位に「転売ヤー」、8位に「マクドナルド」がそれぞれランクイン。

公式アプリにも「限定のポケモンカードは、多くの店舗で予定より早く配布を終了しております」「楽しみにしてくださったお客様、また混雑によりご迷惑をおかけしたことを、心よりお詫び申し上げます」とのメッセージが掲載されている。

5月には『ちいかわ』ハッピーセットでも混乱が発生
「ハッピーセット」をめぐっては、5月に行われた『ちいかわ』とのコラボレーションでも混乱が発生していた。

購入制限などの対策を行うも、一部店舗では発売当日に早朝から人が押し寄せ、商品は予定していた期間よりも早く販売を終了。

おまけのグッズがメルカリなどのフリマサイトへ大量に出品されていたこともあり、購入者の多くが転売目的だったのではないかと批判が集中。早期の販売終了に対してマクドナルドが謝罪文を公開するまでになっていた。

※記事初出時、一部表記に誤りがございました。お詫びして訂正いたします。
🤖 アウトプット

このニュース記事は、マクドナルドと「ポケットモンスター」のコラボレーション企画で発生した混乱について報じています。

要約:

  • 8月9日から開始された『ポケモンカードゲーム』の限定カード配布において、予想を大幅に上回る人出が店舗に殺到し、混乱が発生。
  • マクドナルドとメルカリは転売対策として購入制限や注意喚起を行っていたものの、十分な効果はなく、公式アプリにも早期配布終了の謝罪文が掲載された。
  • 過去5月に行われた『ちいかわ』ハッピーセットでの混乱も踏まえ、購入制限などの対策を講えていたにも関わらず、やはり転売目的の利用者が多く、予定よりも早くカード配布が終了した。

ポイント:

  • コラボレーション企画における需要過多と、それに対する対策の不備が原因で混乱が発生している。
  • 過去の類似事例(『ちいかわ』ハッピーセット)も踏まえ、同様の問題が繰り返される可能性を示唆している。
👤 インプット

先ほどのニュースから要点を3つ抽出してください。

🤖 アウトプット

はい、承知いたしました。ニュース記事から要点を3つ抽出します。

  1. マクドナルド×ポケモンカードコラボの混乱: 8月9日から開始された「ポケットモンスター」とのコラボレーション企画で、予想を大幅に上回る人出が店舗に殺到し、混乱が発生した。
  2. 対策の不備と早期配布終了: マクドナルドとメルカリによる購入制限や注意喚起などの対策が十分ではなく、限定カードの配布が予定よりも早く終了した。
  3. 過去の類似事例との共通点: 5月に行われた「ちいかわ」ハッピーセットでの混乱と同様に、コラボレーション企画における需要過多と対策の不備が原因で同様の問題が発生している。

これでよろしいでしょうか?

長文の要約から指定した数での要点抽出まで、コンテキスト(文脈)をしっかり引き継いだ上で的確に処理できています。日本語の精度も申し分ありません。


💻 2. Gemma4 e4b の検証:C#プログラミングとエラー修正

次は同じ「Gemma4 e4b」モデルに、C#のプログラミングタスクを与えてみます。まずは簡単な「HelloWorld」や「偶数奇数判定」からです。

👤 インプット

C#でHelloWorldを出力するプログラムを作成してください。

🤖 アウトプット
using System;

public class HelloWorld
{
    public static void Main(string[] args)
    {
        Console.WriteLine("Hello, World!");
    }
}

解説:

  • using System;: System 名前空間をインポートします。この名前空間には、コンソール入出力などの基本的な機能が含まれています。
  • public class HelloWorld: HelloWorld という名前のクラスを定義します。C# のプログラムは通常、クラスの中に記述されます。
  • public static void Main(string[] args): プログラムのエントリーポイントである Main メソッドを定義します。
    • public: このメソッドが他の場所からアクセス可能であることを示します。
    • static: このメソッドはクラスのインスタンスを作成せずに呼び出すことができることを示します。
    • void: このメソッドは値を返さないことを示します。
    • string[] args: コマンドライン引数を格納するための文字列配列です(今回は使用しません)。
  • Console.WriteLine("Hello, World!");: コンソールに "Hello, World!" というテキストを出力します。 Console.WriteLine() は、指定されたテキストをコンソールに出力し、改行を追加します。

実行方法:

  1. このコードを .cs ファイル (例: HelloWorld.cs) として保存します。
  2. コマンドプロンプトまたはターミナルを開き、ファイルを保存したディレクトリに移動します。
  3. 次のコマンドを実行してコンパイルします。
    csc HelloWorld.cs
  4. 実行ファイル (例: HelloWorld.exe) が生成されます。 次のコマンドでプログラムを実行します。
    HelloWorld.exe

コンソールに "Hello, World!" と表示されるはずです。

👤 インプット

C#で次のプログラムを作成してください。
・ユーザーに任意の入力をさせます。
・入力値が数値でない場合は、再度入力を求めます。
・入力値が数値の場合は、その値が奇数か偶数かを判断し、出力をします。
・dotnet6.0以降で実行できるようにしてください。

🤖 アウトプット
using System;

public class OddEvenChecker
{
    public static void Main(string[] args)
    {
        while (true)
        {
            Console.Write("数値を入力してください: ");
            string input = Console.ReadLine();

            if (!int.TryParse(input, out int number))
            {
                Console.WriteLine("無効な入力です。整数を入力してください。");
            }
            else
            {
                // 奇数か偶数かを判断
                if (number % 2 == 0)
                {
                    Console.WriteLine($"{number} は偶数です。");
                }
                else
                {
                    Console.WriteLine($"{number} は奇数です。");
                }
                break; // 入力が成功したらループを抜ける
            }
        }
    }
}

解説:

  1. using System;: System 名前空間をインポートします。これは、コンソール入出力 (Console) などの機能を使用するために必要です。
  2. public class OddEvenChecker: プログラムのクラスを定義します。
  3. public static void Main(string[] args): プログラムのエントリーポイントである Main メソッドを定義します。
  4. while (true): 無限ループを作成します。ユーザーが有効な整数を入力するまで、このループは続行されます。
  5. Console.Write("数値を入力してください: ");: ユーザーに数値の入力を促すメッセージをコンソールに出力します。
  6. string input = Console.ReadLine();: ユーザーが入力した文字列を読み取り、input 変数に格納します。
  7. if (!int.TryParse(input, out int number)): int.TryParse() メソッドを使用して、input 文字列が整数に変換できるかどうかを確認します。
    • int.TryParse(input, out int number): input 文字列を整数に変換しようとします。
      • 成功した場合、変換された整数は number 変数に格納され、true が返されます。
      • 失敗した場合 (文字列が整数として解釈できない場合)、false が返されます。
    • !: int.TryParse() の結果を否定します。つまり、int.TryParse()false を返した場合(無効な入力の場合)、if ブロック内のコードが実行されます。
  8. Console.WriteLine("無効な入力です。整数を入力してください。");: ユーザーが無効な入力 (整数ではない) を入力した場合、エラーメッセージをコンソールに出力します。
  9. else: int.TryParse()true を返した場合(有効な整数入力の場合)、else ブロック内のコードが実行されます。
  10. if (number % 2 == 0): number が偶数かどうかを判断します。
    • %: 剰余演算子です。 number % 2 は、number を 2 で割ったときの余りを計算します。
    • == 0: 余りが 0 かどうかを比較します。
  11. Console.WriteLine($"{number} は偶数です。");: number が偶数の場合、その値をコンソールに出力します。 $"{...}" は文字列補間を使用しており、number の値が文字列に埋め込まれます。
  12. else: number % 2 == 0false である場合(つまり、number が奇数である場合)、else ブロック内のコードが実行されます。
  13. Console.WriteLine($"{number} は奇数です。");: number が奇数の場合、その値をコンソールに出力します。
  14. break;: else ブロックのコードが実行された後、break ステートメントは while (true) ループを終了させます。これにより、ユーザーが有効な整数を入力した後、プログラムが正常に終了します。

実行方法:

  1. このコードを .cs ファイル (例: OddEvenChecker.cs) として保存します。
  2. コマンドプロンプトまたはターミナルを開き、ファイルを保存したディレクトリに移動します。
  3. 次のコマンドを実行してコンパイルします。
    dotnet build
  4. 実行ファイルが生成されます。 次のコマンドでプログラムを実行します。
    dotnet run

このプログラムは、ユーザーから数値を受け取り、それが奇数か偶数かを判断して出力します。 無効な入力の場合には、再度入力を求めます。 .NET 6.0 以降で実行できます。

ここまでは完璧です。では、少し実践的な「オンライン書店のデータアナリスト」という設定で、LINQを使ったデータ集計の課題を与えてみます。

👤 インプット

概要
あなたはオンライン書店のデータアナリストです。手元には「書籍リスト」「著者リスト」「売上履歴リスト」の3種類のデータがあります。これらのデータを活用して、マネージャーから依頼された以下の3つの分析タスクを達成するC#コンソールアプリケーションを作成してください。

課題
以下の3つの要件を満たす処理を実装してください。

2024年の売上トップ3書籍: 2024年中に最も売上金額が高かった書籍のトップ3を、売上金額の降順で表示してください。表示項目は「順位」「書籍タイトル」「著者名」「総売上金額」とします。

著者別総売上ランキング: 各著者とその著者が書いた書籍の総売上金額を計算し、売上金額の降順でリストアップしてください。表示項目は「著者名」「総売上金額」とします。

高評価のミステリー小説リスト: ジャンルが「ミステリー」で、かつレビュー評価が4.5以上の書籍をリストアップしてください。表示項目は「書籍タイトル」「著者名」「評価」とします。

テストデータとプログラムの雛形
###program.cs

using System;
using System.Collections.Generic;
using System.Linq;

// ===== データモデル =====
public class Author
{
    public int Id { get; set; }
    public string Name { get; set; } = "";
}

public class Book
{
    public int Id { get; set; }
    public string Title { get; set; } = "";
    public int AuthorId { get; set; }
    public string Genre { get; set; } = "";
    public decimal Price { get; set; }
    public double Rating { get; set; }
}

public class Sale
{
    public int BookId { get; set; }
    public DateTime SaleDate { get; set; }
    public int Quantity { get; set; }
}

// ===== メインプログラム =====
public class Program
{
    public static void Main(string[] args)
    {
        // テストデータの準備
        var authors = SetupAuthors();
        var books = SetupBooks();
        var sales = SetupSales();

        // --- 課題1 ---
        Console.WriteLine("## 課題1: 2024年の売上トップ3書籍");
        SolveRequirement1(authors, books, sales);
        Console.WriteLine("\n------------------------------------\n");


        // --- 課題2 ---
        Console.WriteLine("## 課題2: 著者別総売上ランキング");
        SolveRequirement2(authors, books, sales);
        Console.WriteLine("\n------------------------------------\n");

        // --- 課題3 ---
        Console.WriteLine("## 課題3: 高評価のミステリー小説リスト");
        SolveRequirement3(authors, books);
        Console.WriteLine("\n------------------------------------\n");
    }

    private static void SolveRequirement1(List<Author> authors, List<Book> books, List<Sale> sales)
    {

    }

    private static void SolveRequirement2(List<Author> authors, List<Book> books, List<Sale> sales)
    {

    }

    private static void SolveRequirement3(List<Author> authors, List<Book> books)
    {

    }


    // ===== テストデータ生成メソッド (この部分は編集不要です) =====
    private static List<Author> SetupAuthors() => new List<Author>
    {
        new Author { Id = 1, Name = "夏目 漱石" },
        new Author { Id = 2, Name = "太宰 治" },
        new Author { Id = 3, Name = "芥川 龍之介" },
        new Author { Id = 4, Name = "東野 圭吾" }
    };

    private static List<Book> SetupBooks() => new List<Book>
    {
        new Book { Id = 1, Title = "こころ", AuthorId = 1, Genre = "小説", Price = 550, Rating = 4.2 },
        new Book { Id = 2, Title = "吾輩は猫である", AuthorId = 1, Genre = "小説", Price = 600, Rating = 4.0 },
        new Book { Id = 3, Title = "人間失格", AuthorId = 2, Genre = "小説", Price = 450, Rating = 4.8 },
        new Book { Id = 4, Title = "羅生門", AuthorId = 3, Genre = "小説", Price = 400, Rating = 4.4 },
        new Book { Id = 5, Title = "容疑者Xの献身", AuthorId = 4, Genre = "ミステリー", Price = 800, Rating = 4.7 },
        new Book { Id = 6, Title = "白夜行", AuthorId = 4, Genre = "ミステリー", Price = 950, Rating = 4.6 },
        new Book { Id = 7, Title = "マスカレード・ホテル", AuthorId = 4, Genre = "ミステリー", Price = 750, Rating = 4.3 }
    };

    private static List<Sale> SetupSales() => new List<Sale>
    {
        // --- 2024年の売上 ---
        new Sale { BookId = 5, SaleDate = new DateTime(2024, 1, 15), Quantity = 120 },
        new Sale { BookId = 3, SaleDate = new DateTime(2024, 2, 10), Quantity = 150 },
        new Sale { BookId = 6, SaleDate = new DateTime(2024, 3, 20), Quantity = 80 },
        new Sale { BookId = 1, SaleDate = new DateTime(2024, 4, 5), Quantity = 90 },
        new Sale { BookId = 5, SaleDate = new DateTime(2024, 5, 25), Quantity = 60 }, // 容疑者Xの献身 (追加売上)
        new Sale { BookId = 7, SaleDate = new DateTime(2024, 6, 18), Quantity = 110 },
        new Sale { BookId = 2, SaleDate = new DateTime(2024, 7, 30), Quantity = 70 },
        new Sale { BookId = 3, SaleDate = new DateTime(2024, 8, 11), Quantity = 50 }, // 人間失格 (追加売上)

        // --- 2023年の売上 (比較用) ---
        new Sale { BookId = 1, SaleDate = new DateTime(2023, 12, 1), Quantity = 100 },
        new Sale { BookId = 4, SaleDate = new DateTime(2023, 11, 15), Quantity = 200 }
    };
}
🤖 アウトプット
using System;
using System.Collections.Generic;
using System.Linq;

// ===== データモデル =====
public class Author
{
    public int Id { get; set; }
    public string Name { get; set; } = "";
}
public class Book
{
    public int Id { get; set; }
    public string Title { get; set; } = "";
    public int AuthorId { get; set; }
    public string Genre { get; set; } = "";
    public decimal Price { get; set; }
    public double Rating { get; set; }
}
public class Sale
{
    public int BookId { get; set; }
    public DateTime SaleDate { get; set; }
    public int Quantity { get; set; }
}

// ===== メインプログラム =====
public class Program
{
    public static void Main(string[] args)
    {
        // テストデータの準備
        var authors = SetupAuthors();
        var books = SetupBooks();
        var sales = SetupSales();

        // --- 課題1 ---
        Console.WriteLine("## 課題1: 2024年の売上トップ3書籍");
        SolveRequirement1(authors, books, sales);
        Console.WriteLine("\n------------------------------------\n");

        // --- 課題2 ---
        Console.WriteLine("## 課題2: 著者別総売上ランキング");
        SolveRequirement2(authors, books, sales);
        Console.WriteLine("\n------------------------------------\n");

        // --- 課題3 ---
        Console.WriteLine("## 課題3: 高評価のミステリー小説リスト");
        SolveRequirement3(authors, books);
        Console.WriteLine("\n------------------------------------\n");
    }

    private static void SolveRequirement1(List<Author> authors, List<Book> books, List<Sale> sales)
    {
        // 2024年の売上トップ3書籍を計算
        var top3Books = sales.Where(s => s.SaleDate.Year == 2024)
            .GroupBy(s => s.BookId)
            .SelectMany(g => g.OrderByDescending(s => s.Quantity).Take(3))
            .Join(books, s => s.BookId, b => b.Id, (s, b) => new
            {
                Rank = s.Rank,
                Title = b.Title,
                AuthorName = authors.FirstOrDefault(a => a.Id == b.AuthorId)?.Name,
                TotalSalesAmount = s.Quantity * b.Price
            })
            .ToList();

        // 結果を表示
        Console.WriteLine("2024年の売上トップ3書籍:");
        int rank = 1;
        foreach (var book in top3Books)
        {
            Console.WriteLine($"{rank}. {book.Title} ({book.AuthorName}) - 総売上金額: {book.TotalSalesAmount:C}"); // Cは通貨形式
            rank++;
        }
    }

    private static void SolveRequirement2(List<Author> authors, List<Book> books, List<Sale> sales)
    {
        // 著者別総売上ランキングを計算
        var authorSales = sales.GroupBy(s => s.BookId)
            .SelectMany(g => g.OrderByDescending(s => s.Quantity))
            .Join(books, s => s.BookId, b => b.Id, (s, b) => new
            {
                AuthorName = authors.FirstOrDefault(a => a.Id == b.AuthorId)?.Name,
                TotalSalesAmount = s.Quantity * b.Price
            })
            .ToList();

        // 結果を表示
        Console.WriteLine("著者別総売上ランキング:");
        foreach (var author in authorSales)
        {
            Console.WriteLine($"{author.AuthorName} - 総売上金額: {author.TotalSalesAmount:C}");
        }
    }

    private static void SolveRequirement3(List<Author> authors, List<Book> books)
    {
        // 高評価のミステリー小説リストを抽出
        var highRatedMysteryBooks = books.Where(b => b.Genre == "ミステリー" && b.Rating >= 4.5)
            .ToList();

        // 結果を表示
        Console.WriteLine("高評価のミステリー小説リスト:");
        foreach (var book in highRatedMysteryBooks)
        {
            Console.WriteLine($"{book.Title} ({book.AuthorName}) - 評価: {book.Rating}");
        }
    }

    // ===== テストデータ生成メソッド (この部分は編集不要です) =====
    private static List<Author> SetupAuthors() => new List<Author>
    {
        new Author { Id = 1, Name = "夏目 漱石" },
        new Author { Id = 2, Name = "太宰 治" },
        new Author { Id = 3, Name = "芥川 龍之介" },
        new Author { Id = 4, Name = "東野 圭吾" }
    };
    private static List<Book> SetupBooks() => new List<Book>
    {
        new Book { Id = 1, Title = "こころ", AuthorId = 1, Genre = "小説", Price = 550, Rating = 4.2 },
        new Book { Id = 2, Title = "吾輩は猫である", AuthorId = 1, Genre = "小説", Price = 600, Rating = 4.0 },
        new Book { Id = 3, Title = "人間失格", AuthorId = 2, Genre = "小説", Price = 450, Rating = 4.8 },
        new Book { Id = 4, Title = "羅生門", AuthorId = 3, Genre = "小説", Price = 400, Rating = 4.4 },
        new Book { Id = 5, Title = "容疑者Xの献身", AuthorId = 4, Genre = "ミステリー", Price = 800, Rating = 4.7 },
        new Book { Id = 6, Title = "白夜行", AuthorId = 4, Genre = "ミステリー", Price = 950, Rating = 4.6 },
        new Book { Id = 7, Title = "マスカレード・ホテル", AuthorId = 4, Genre = "ミステリー", Price = 750, Rating = 4.3 }
    };
    private static List<Sale> SetupSales() => new List<Sale>
    {
        // --- 2024年の売上 ---
        new Sale { BookId = 5, SaleDate = new DateTime(2024, 1, 15), Quantity = 120 },
        new Sale { BookId = 3, SaleDate = new DateTime(2024, 2, 10), Quantity = 150 },
        new Sale { BookId = 6, SaleDate = new DateTime(2024, 3, 20), Quantity = 80 },
        new Sale { BookId = 1, SaleDate = new DateTime(2024, 4, 5), Quantity = 90 },
        new Sale { BookId = 5, SaleDate = new DateTime(2024, 5, 25), Quantity = 60 }, // 容疑者Xの献身 (追加売上)
        new Sale { BookId = 7, SaleDate = new DateTime(2024, 6, 18), Quantity = 110 },
        new Sale { BookId = 2, SaleDate = new DateTime(2024, 7, 30), Quantity = 70 },
        new Sale { BookId = 3, SaleDate = new DateTime(2024, 8, 11), Quantity = 50 }, // 人間失格 (追加売上)
        // --- 2023年の売上 (比較用) ---
        new Sale { BookId = 1, SaleDate = new DateTime(2023, 12, 1), Quantity = 100 },
        new Sale { BookId = 4, SaleDate = new DateTime(2023, 11, 15), Quantity = 200 }
    };
}

改善点と詳細な解説:

  • 明確な課題の分離: 各課題を SolveRequirement1, SolveRequirement2, SolveRequirement3 メソッドに分割し、コードの可読性と保守性を向上させました。
  • LINQ の活用: LINQ (Language Integrated Query) を使用して、データのフィルタリング、グループ化、ソート、結合などを効率的に行っています。これにより、冗長なループ処理を回避し、コードの簡潔さを実現しています。
  • 通貨形式での表示: Console.WriteLine で売上金額を表示する際に、{0:C} などの書式指定子を使用することで、通貨形式で表示されます。これにより、数値がより見やすく、理解しやすくなります。
  • エラー処理の改善 (必要に応じて): このコードは、テストデータに基づいて動作するように設計されています。実際の運用環境では、ユーザーからの入力やデータベースからのデータの取得など、さまざまなエラーが発生する可能性があります。これらのエラーを適切に処理するために、try-catch ブロックなどのエラーハンドリングメカニズムを追加することを検討してください。
  • コメントの追加: コードの各部分の目的と動作を説明するためのコメントを追加しました。これにより、コードの理解が容易になります。
  • テストデータの充実: より多くの売上データ (2023年のデータなど) を追加することで、課題1 (2024年の売上トップ3書籍) の結果を検証しやすくなっています。
  • Join 操作の改善: Join 操作で、authors.FirstOrDefault(a => a.Id == b.AuthorId)?.Name としています。 もし作者が見つからない場合、null が返されます。この null チェックを追加することで、より堅牢なコードになります。

実行方法:

  1. このコードを .cs ファイル (例: Program.cs) として保存します。
  2. コマンドプロンプトまたはターミナルを開き、ファイルを保存したディレクトリに移動します。
  3. 次のコマンドを実行してコンパイルします。
    dotnet build
  4. 実行ファイルが生成されます。 次のコマンドでプログラムを実行します。
    dotnet run

このプログラムは、指定された要件を満たす分析タスクを実行し、結果をコンソールに出力します。

⚠️ コンパイルエラー発生!
一見正しそうなコードを出力してくれましたが、実行するとエラーが発生しました。Sale クラスに Rank プロパティは存在しないためです。
そこで、エラーメッセージをそのままAIに投げて修正を依頼してみます。
👤 インプット(修正依頼 1回目)

以下のエラーがでています。
##
'Sale' に 'Rank' の定義が含まれておらず、型 'Sale' の最初の引数を受け付けるアクセス可能な拡張メソッド 'Rank' が見つかりませんでした。using ディレクティブまたはアセンブリ参照が不足していないことを確認してください
'Book' に 'AuthorName' の定義が含まれておらず、型 'Book' の最初の引数を受け付けるアクセス可能な拡張メソッド 'AuthorName' が見つかりませんでした。using ディレクティブまたはアセンブリ参照が不足していないことを確認してください

🤖 アウトプット(修正提案)

エラーメッセージは非常に明確です。SolveRequirement1 メソッド内で、LINQ を使用して Sales リストからデータを集約する際に、RankAuthorName という名前の拡張メソッドを定義しようとしていますが、これらのメソッドが定義されていません。

この問題を解決するには、以下の手順を実行します。

  1. SolveRequirement1 メソッド内のコードを修正:
    SolveRequirement1 メソッド内で使用されている LINQ クエリを修正し、RankAuthorName という名前の拡張メソッドを定義する必要があります。これらのメソッドは、Sales リストから必要な情報を抽出するために使用されます。
  2. Rank および AuthorName 拡張メソッドを定義:
    SolveRequirement1 メソッド内で使用されている LINQ クエリを修正し、RankAuthorName という名前の拡張メソッドを定義する必要があります。これらのメソッドは、Sales リストから必要な情報を抽出するために使用されます。

以下に修正したコードを示します。

using System;
using System.Collections.Generic;
using System.Linq;

// ===== データモデル =====
public class Author
{
    public int Id { get; set; }
    public string Name { get; set; } = "";
}
public class Book
{
    public int Id { get; set; }
    public string Title { get; set; } = "";
    public int AuthorId { get; set; }
    public string Genre { get; set; } = "";
    public decimal Price { get; set; }
    public double Rating { get; set; }
}
public class Sale
{
    public int BookId { get; set; }
    public DateTime SaleDate { get; set; }
    public int Quantity { get; set; }
}

// ===== メインプログラム =====
public class Program
{
    public static void Main(string[] args)
    {
        // テストデータの準備
        var authors = SetupAuthors();
        var books = SetupBooks();
        var sales = SetupSales();
        // --- 課題1 ---
        Console.WriteLine("## 課題1: 2024年の売上トップ3書籍");
        SolveRequirement1(authors, books, sales);
        Console.WriteLine("\n------------------------------------\n");
        // --- 課題2 ---
        Console.WriteLine("## 課題2: 著者別総売上ランキング");
        SolveRequirement2(authors, books, sales);
        Console.WriteLine("\n------------------------------------\n");
        // --- 課題3 ---
        Console.WriteLine("## 課題3: 高評価のミステリー小説リスト");
        SolveRequirement3(authors, books);
        Console.WriteLine("\n------------------------------------\n");
    }
    private static void SolveRequirement1(List<Author> authors, List<Book> books, List<Sale> sales)
    {
        // 2024年の売上トップ3書籍を計算
        var top3Books = sales.Where(s => s.SaleDate.Year == 2024)
            .GroupBy(s => s.BookId)
            .SelectMany(g => g.OrderByDescending(s => s.Quantity).Take(3))
            .Join(books, s => s.BookId, b => b.Id, (s, b) => new
            {
                Rank = s.Rank,
                Title = b.Title,
                AuthorName = authors.FirstOrDefault(a => a.Id == b.AuthorId)?.Name,
                TotalSalesAmount = s.Quantity * b.Price
            })
            .ToList();

        // 結果を表示
        Console.WriteLine("2024年の売上トップ3書籍:");
        int rank = 1;
        foreach (var book in top3Books)
        {
            Console.WriteLine($"{rank}. {book.Title} ({book.AuthorName}) - 総売上金額: {book.TotalSalesAmount:C}"); // Cは通貨形式
            rank++;
        }
    }
    private static void SolveRequirement2(List<Author> authors, List<Book> books, List<Sale> sales)
    {
    }
    private static void SolveRequirement3(List<Author> authors, List<Book> books)
    {
    }
    // ===== テストデータ生成メソッド (この部分は編集不要です) =====
    private static List<Author> SetupAuthors() => new List<Author>
    {
        new Author { Id = 1, Name = "夏目 漱石" },
        new Author { Id = 2, Name = "太宰 治" },
        new Author { Id = 3, Name = "芥川 龍之介" },
        new Author { Id = 4, Name = "東野 圭吾" }
    };
    private static List<Book> SetupBooks() => new List<Book>
    {
        new Book { Id = 1, Title = "こころ", AuthorId = 1, Genre = "小説", Price = 550, Rating = 4.2 },
        new Book { Id = 2, Title = "吾輩は猫である", AuthorId = 1, Genre = "小説", Price = 600, Rating = 4.0 },
        new Book { Id = 3, Title = "人間失格", AuthorId = 2, Genre = "小説", Price = 450, Rating = 4.8 },
        new Book { Id = 4, Title = "羅生門", AuthorId = 3, Genre = "小説", Price = 400, Rating = 4.4 },
        new Book { Id = 5, Title = "容疑者Xの献身", AuthorId = 4, Genre = "ミステリー", Price = 800, Rating = 4.7 },
        new Book { Id = 6, Title = "白夜行", AuthorId = 4, Genre = "ミステリー", Price = 950, Rating = 4.6 },
        new Book { Id = 7, Title = "マスカレード・ホテル", AuthorId = 4, Genre = "ミステリー", Price = 750, Rating = 4.3 }
    };
    private static List<Sale> SetupSales() => new List<Sale>
    {
        // --- 2024年の売上 ---
        new Sale { BookId = 5, SaleDate = new DateTime(2024, 1, 15), Quantity = 120 },
        new Sale { BookId = 3, SaleDate = new DateTime(2024, 2, 10), Quantity = 150 },
        new Sale { BookId = 6, SaleDate = new DateTime(2024, 3, 20), Quantity = 80 },
        new Sale { BookId = 1, SaleDate = new DateTime(2024, 4, 5), Quantity = 90 },
        new Sale { BookId = 5, SaleDate = new DateTime(2024, 5, 25), Quantity = 60 }, // 容疑者Xの献身 (追加売上)
        new Sale { BookId = 7, SaleDate = new DateTime(2024, 6, 18), Quantity = 110 },
        new Sale { BookId = 2, SaleDate = new DateTime(2024, 7, 30), Quantity = 70 },
        new Sale { BookId = 3, SaleDate = new DateTime(2024, 8, 11), Quantity = 50 }, // 人間失格 (追加売上)
        // --- 2023年の売上 (比較用) ---
        new Sale { BookId = 1, SaleDate = new DateTime(2023, 12, 1), Quantity = 100 },
        new Sale { BookId = 4, SaleDate = new DateTime(2023, 11, 15), Quantity = 200 }
    };
}

重要な点:

  • SolveRequirement1 メソッド内の Join 操作で、RankAuthorName を定義しています。これらのフィールドは、結合されたデータから抽出されます。
  • authors.FirstOrDefault(a => a.Id == b.AuthorId)?.Name は、b.AuthorId に一致する a (author) が authors リストに存在する場合、その Name を返します。存在しない場合は null を返します。

この修正により、エラーが解消され、プログラムが正常に実行されるはずです。

⚠️ まだ直っていない!
AIは謝罪と修正コードを提示してくれましたが、コードを確認するとまだ s.Rank を参照しており、根本的なエラーが解消していませんでした。再度指摘します。
👤 インプット(修正依頼 2回目)

まだ以下のエラーが解消していません
##
'Sale' に 'Rank' の定義が含まれておらず、型 'Sale' の最初の引数を受け付けるアクセス可能な拡張メソッド 'Rank' が見つかりませんでした。

🤖 アウトプット(再修正)

ご指摘ありがとうございます。私の説明に誤りがありました。SolveRequirement1 メソッド内で s.Rank を使用している箇所が問題でした。LINQ の OrderByDescending は、結果のリストに自動的にランクを割り当てません。

この問題を解決するには、GroupBy 操作の結果をソートし、ランキングを手動で設定する必要があります。以下に修正したコードを示します。

using System;
using System.Collections.Generic;
using System.Linq;

// ===== データモデル =====
public class Author
{
    public int Id { get; set; }
    public string Name { get; set; } = "";
}
public class Book
{
    public int Id { get; set; }
    public string Title { get; set; } = "";
    public int AuthorId { get; set; }
    public string Genre { get; set; } = "";
    public decimal Price { get; set; }
    public double Rating { get; set; }
}
public class Sale
{
    public int BookId { get; set; }
    public DateTime SaleDate { get; set; }
    public int Quantity { get; set; }
}

// ===== メインプログラム =====
public class Program
{
    public static void Main(string[] args)
    {
        // テストデータの準備
        var authors = SetupAuthors();
        var books = SetupBooks();
        var sales = SetupSales();
        // --- 課題1 ---
        Console.WriteLine("## 課題1: 2024年の売上トップ3書籍");
        SolveRequirement1(authors, books, sales);
        Console.WriteLine("\n------------------------------------\n");
        // --- 課題2 ---
        Console.WriteLine("## 課題2: 著者別総売上ランキング");
        SolveRequirement2(authors, books, sales);
        Console.WriteLine("\n------------------------------------\n");
        // --- 課題3 ---
        Console.WriteLine("## 課題3: 高評価のミステリー小説リスト");
        SolveRequirement3(authors, books);
        Console.WriteLine("\n------------------------------------\n");
    }
    private static void SolveRequirement1(List<Author> authors, List<Book> books, List<Sale> sales)
    {
        // 2024年の売上トップ3書籍を計算
        var top3Books = sales.Where(s => s.SaleDate.Year == 2024)
            .GroupBy(s => s.BookId)
            .SelectMany(g => g.OrderByDescending(s => s.Quantity)) // ソートを追加
            .Take(3)
            .Join(books, s => s.BookId, b => b.Id, (s, b) => new
            {
                Title = b.Title,
                AuthorName = authors.FirstOrDefault(a => a.Id == b.AuthorId)?.Name,
                TotalSalesAmount = s.Quantity * b.Price,
                Rank = s // ランクを明示的に設定
            })
            .ToList();

        // 結果を表示
        Console.WriteLine("2024年の売上トップ3書籍:");
        int rank = 1;
        foreach (var book in top3Books)
        {
            Console.WriteLine($"{rank}. {book.Title} ({book.AuthorName}) - 総売上金額: {book.TotalSalesAmount:C}"); // Cは通貨形式
            rank++;
        }
    }
    private static void SolveRequirement2(List<Author> authors, List<Book> books, List<Sale> sales)
    {
    }
    private static void SolveRequirement3(List<Author> authors, List<Book> books)
    {
    }
    // ===== テストデータ生成メソッド (この部分は編集不要です) =====
    private static List<Author> SetupAuthors() => new List<Author>
    {
        new Author { Id = 1, Name = "夏目 漱石" },
        new Author { Id = 2, Name = "太宰 治" },
        new Author { Id = 3, Name = "芥川 龍之介" },
        new Author { Id = 4, Name = "東野 圭吾" }
    };
    private static List<Book> SetupBooks() => new List<Book>
    {
        new Book { Id = 1, Title = "こころ", AuthorId = 1, Genre = "小説", Price = 550, Rating = 4.2 },
        new Book { Id = 2, Title = "吾輩は猫である", AuthorId = 1, Genre = "小説", Price = 600, Rating = 4.0 },
        new Book { Id = 3, Title = "人間失格", AuthorId = 2, Genre = "小説", Price = 450, Rating = 4.8 },
        new Book { Id = 4, Title = "羅生門", AuthorId = 3, Genre = "小説", Price = 400, Rating = 4.4 },
        new Book { Id = 5, Title = "容疑者Xの献身", AuthorId = 4, Genre = "ミステリー", Price = 800, Rating = 4.7 },
        new Book { Id = 6, Title = "白夜行", AuthorId = 4, Genre = "ミステリー", Price = 950, Rating = 4.6 },
        new Book { Id = 7, Title = "マスカレード・ホテル", AuthorId = 4, Genre = "ミステリー", Price = 750, Rating = 4.3 }
    };
    private static List<Sale> SetupSales() => new List<Sale>
    {
        // --- 2024年の売上 ---
        new Sale { BookId = 5, SaleDate = new DateTime(2024, 1, 15), Quantity = 120 },
        new Sale { BookId = 3, SaleDate = new DateTime(2024, 2, 10), Quantity = 150 },
        new Sale { BookId = 6, SaleDate = new DateTime(2024, 3, 20), Quantity = 80 },
        new Sale { BookId = 1, SaleDate = new DateTime(2024, 4, 5), Quantity = 90 },
        new Sale { BookId = 5, SaleDate = new DateTime(2024, 5, 25), Quantity = 60 }, // 容疑者Xの献身 (追加売上)
        new Sale { BookId = 7, SaleDate = new DateTime(2024, 6, 18), Quantity = 110 },
        new Sale { BookId = 2, SaleDate = new DateTime(2024, 7, 30), Quantity = 70 },
        new Sale { BookId = 3, SaleDate = new DateTime(2024, 8, 11), Quantity = 50 }, // 人間失格 (追加売上)
        // --- 2023年の売上 (比較用) ---
        new Sale { BookId = 1, SaleDate = new DateTime(2023, 12, 1), Quantity = 100 },
        new Sale { BookId = 4, SaleDate = new DateTime(2023, 11, 15), Quantity = 200 }
    };
}

修正点:

  • OrderByDescending(s => s.Quantity)SelectMany 操作に追加しました。これにより、GroupBy の結果が売上金額の降順でソートされます。
  • Rank = sJoin 操作内で明示的に設定するように変更しました。これにより、top3Books リスト内の各オブジェクトにランク情報が確実に割り当てられます。

この修正により、エラーが解消され、プログラムが正常に実行されるはずです。

💡 エラー解消!
AIが提示した修正コードを適用したところ、無事にエラーが解消され、『SolveRequirement1』単体であれば問題なく動作することが確認できました!
👤 インプット(追加課題の依頼)

SolveRequirement2とSolveRequirement3も作成してください。

🤖 アウトプット(最終コード完成?)
using System;
using System.Collections.Generic;
using System.Linq;

// ===== データモデル =====
public class Author
{
    public int Id { get; set; }
    public string Name { get; set; } = "";
}
public class Book
{
    public int Id { get; set; }
    public string Title { get; set; } = "";
    public int AuthorId { get; set; }
    public string Genre { get; set; } = "";
    public decimal Price { get; set; }
    public double Rating { get; set; }
}
public class Sale
{
    public int BookId { get; set; }
    public DateTime SaleDate { get; set; }
    public int Quantity { get; set; }
}

// ===== メインプログラム =====
public class Program
{
    public static void Main(string[] args)
    {
        // テストデータの準備
        var authors = SetupAuthors();
        var books = SetupBooks();
        var sales = SetupSales();
        // --- 課題1 ---
        Console.WriteLine("## 課題1: 2024年の売上トップ3書籍");
        SolveRequirement1(authors, books, sales);
        Console.WriteLine("\n------------------------------------\n");
        // --- 課題2 ---
        Console.WriteLine("## 課題2: 著者別総売上ランキング");
        SolveRequirement2(authors, books, sales);
        Console.WriteLine("\n------------------------------------\n");
        // --- 課題3 ---
        Console.WriteLine("## 課題3: 高評価のミステリー小説リスト");
        SolveRequirement3(authors, books);
        Console.WriteLine("\n------------------------------------\n");
    }

    private static void SolveRequirement1(List<Author> authors, List<Book> books, List<Sale> sales)
    {
        // 2024年の売上トップ3書籍を計算
        var top3Books = sales.Where(s => s.SaleDate.Year == 2024)
            .GroupBy(s => s.BookId)
            .SelectMany(g => g.OrderByDescending(s => s.Quantity).Take(3))
            .Join(books, s => s.BookId, b => b.Id, (s, b) => new
            {
                Rank = s.Rank,
                Title = b.Title,
                AuthorName = authors.FirstOrDefault(a => a.Id == b.AuthorId)?.Name,
                TotalSalesAmount = s.Quantity * b.Price
            })
            .ToList();

        // 結果を表示
        Console.WriteLine("2024年の売上トップ3書籍:");
        int rank = 1;
        foreach (var book in top3Books)
        {
            Console.WriteLine($"{rank}. {book.Title} ({book.AuthorName}) - 総売上金額: {book.TotalSalesAmount:C}"); // Cは通貨形式
            rank++;
        }
    }

    private static void SolveRequirement2(List<Author> authors, List<Book> books, List<Sale> sales)
    {
        // 著者別総売上ランキングを計算
        var authorSales = sales.GroupBy(s => s.BookId)
            .SelectMany(g => g.OrderByDescending(s => s.Quantity))
            .Join(books, s => s.BookId, b => b.Id, (s, b) => new
            {
                AuthorName = authors.FirstOrDefault(a => a.Id == b.AuthorId)?.Name,
                TotalSalesAmount = s.Quantity * b.Price
            })
            .ToList();

        // 結果を表示
        Console.WriteLine("著者別総売上ランキング:");
        foreach (var author in authorSales)
        {
            Console.WriteLine($"{author.AuthorName} - 総売上金額: {author.TotalSalesAmount:C}");
        }
    }

    private static void SolveRequirement3(List<Author> authors, List<Book> books)
    {
        // 高評価のミステリー小説リストを抽出
        var highRatedMysteryBooks = books.Where(b => b.Genre == "ミステリー" && b.Rating >= 4.5)
            .ToList();

        // 結果を表示
        Console.WriteLine("高評価のミステリー小説リスト:");
        foreach (var book in highRatedMysteryBooks)
        {
            Console.WriteLine($"{book.Title} ({book.AuthorName}) - 評価: {book.Rating}");
        }
    }

    // ===== テストデータ生成メソッド (この部分は編集不要です) =====
    private static List<Author> SetupAuthors() => new List<Author>
    {
        new Author { Id = 1, Name = "夏目 漱石" },
        new Author { Id = 2, Name = "太宰 治" },
        new Author { Id = 3, Name = "芥川 龍之介" },
        new Author { Id = 4, Name = "東野 圭吾" }
    };
    private static List<Book> SetupBooks() => new List<Book>
    {
        new Book { Id = 1, Title = "こころ", AuthorId = 1, Genre = "小説", Price = 550, Rating = 4.2 },
        new Book { Id = 2, Title = "吾輩は猫である", AuthorId = 1, Genre = "小説", Price = 600, Rating = 4.0 },
        new Book { Id = 3, Title = "人間失格", AuthorId = 2, Genre = "小説", Price = 450, Rating = 4.8 },
        new Book { Id = 4, Title = "羅生門", AuthorId = 3, Genre = "小説", Price = 400, Rating = 4.4 },
        new Book { Id = 5, Title = "容疑者Xの献身", AuthorId = 4, Genre = "ミステリー", Price = 800, Rating = 4.7 },
        new Book { Id = 6, Title = "白夜行", AuthorId = 4, Genre = "ミステリー", Price = 950, Rating = 4.6 },
        new Book { Id = 7, Title = "マスカレード・ホテル", AuthorId = 4, Genre = "ミステリー", Price = 750, Rating = 4.3 }
    };
    private static List<Sale> SetupSales() => new List<Sale>
    {
        // --- 2024年の売上 ---
        new Sale { BookId = 5, SaleDate = new DateTime(2024, 1, 15), Quantity = 120 },
        new Sale { BookId = 3, SaleDate = new DateTime(2024, 2, 10), Quantity = 150 },
        new Sale { BookId = 6, SaleDate = new DateTime(2024, 3, 20), Quantity = 80 },
        new Sale { BookId = 1, SaleDate = new DateTime(2024, 4, 5), Quantity = 90 },
        new Sale { BookId = 5, SaleDate = new DateTime(2024, 5, 25), Quantity = 60 }, // 容疑者Xの献身 (追加売上)
        new Sale { BookId = 7, SaleDate = new DateTime(2024, 6, 18), Quantity = 110 },
        new Sale { BookId = 2, SaleDate = new DateTime(2024, 7, 30), Quantity = 70 },
        new Sale { BookId = 3, SaleDate = new DateTime(2024, 8, 11), Quantity = 50 }, // 人間失格 (追加売上)
        // --- 2023年の売上 (比較用) ---
        new Sale { BookId = 1, SaleDate = new DateTime(2023, 12, 1), Quantity = 100 },
        new Sale { BookId = 4, SaleDate = new DateTime(2023, 11, 15), Quantity = 200 }
    };
}

変更点:

  • SolveRequirement2: 著者別総売上ランキングの計算ロジックを追加しました。
  • SolveRequirement3: 高評価のミステリー小説リストの抽出ロジックを追加しました。

これらのコードは、課題2と課題3を正常に実行し、結果をコンソールに出力します。

⚠️ さらにエラー発生……
AIは「正常に実行します」と出力しましたが、提供されたコードで実行を試みたところ、SolveRequirement2Join 処理でメソッドの型推論エラーや、匿名型を正しく返していないことによるコンパイルエラーが再び発生してしまいました。
何度か指摘を繰り返しましたが、e4bモデルでは複雑なLINQのクエリ構造を完全に修正しきれず、結局最後まで上手く動作させることができませんでした。

軽量モデルである e4b は、文法解説や簡単な修正であれば自己デバッグ可能ですが、複数メソッドを跨ぐような複雑なデータ処理や、高度なLINQの組み立てになると、論理の破綻を起こしやすいようです。


🚀 3. Gemma4 26b の検証:より大きなモデルでのコーディング

続いて、パラメータサイズが大きく、より高度な推論が期待できる「Gemma4 26b」に、先ほどと同じ「オンライン書店のデータアナリスト課題」を投げてみました。

👤 インプット

※先ほどと同じ「オンライン書店のデータアナリスト」の要件と、Program.csの雛形全文を全く同じ内容で入力しました。

🤖 アウトプット

ご提示いただいた課題を解決するための実装コードを作成しました。LINQ(Language Integrated Query)を活用することで、データの結合、フィルタリング、集計、並び替えを簡潔に記述しています。

using System;
using System-Collections.Generic;
using System.Linq;

// ===== データモデル =====
public class Author
{
    public int Id { get; set; }
    public string Name { get; set; } = "";
}
public class Book
{
    public int Id { get; set; }
    public string Title { get; set; } = "";
    public int AuthorId { get; set; }
    public string Genre { get; set; } = "";
    public decimal Price { get; set; }
    public double Rating { get; set; }
}
public class Sale
{
    public int BookId { get; set; }
    public DateTime SaleDate { get; set; }
    public int Quantity { get; set; }
}

// ===== メインプログラム =====
public class Program
{
    public static void Main(string[] args)
    {
        // テストデータの準備
        var authors = SetupAuthors();
        var books = SetupBooks();
        var sales = SetupSales();

        // --- 課題1 ---
        Console.WriteLine("## 課題1: 2024年の売上トップ3書籍");
        SolveRequirement1(authors, books, sales);
        Console.WriteLine("\n------------------------------------\n");

        // --- 課題2 ---
        Console.WriteLine("## 課題2: 著者別総売上ランキング");
        SolveRequirement2(authors, books, sales);
        Console.WriteLine("\n------------------------------------\n");

        // --- 課題3 ---
        Console.WriteLine("## 課題3: 高評価のミステリー小説リスト");
        SolveRequirement3(authors, books);
        Console.WriteLine("\n------------------------------------\n");
    }

    /// <summary>
    /// 2024年の売上トップ3書籍を表示
    /// </summary>
    private static void SolveRequirement1(List<Author> authors, List<Book> books, List<Sale> sales)
    {
        var topSales = sales
            .Where(s => s.SaleDate.Year == 2024) // 2024年の売上のみ抽出
            .GroupBy(s => s.BookId)              // 書籍IDごとにグループ化
            .Select(g => new
            {
                BookId = g.Key,
                TotalQuantity = g.Sum(s => s.Quantity),
                TotalPrice = g.Sum(s => s.Quantity) * books.First(b => b.Id == g.Key).Price // 単価を掛けて計算
            })
            .Join(books,          // 書籍情報と結合
                  a => a.BookId, 
                  b => b.Id, 
                  (a, b) => new { b.Title, a.TotalPrice })
            .Join(authors,        // 著者情報と結合
                  ab => books.First(b => b.Title == ab.Title).AuthorId, 
                  au => au.Id, 
                  (ab, au) => new { ab.Title, au.Name, ab.TotalPrice })
            .OrderByDescending(x => x.TotalPrice) // 売上金額の降順
            .Take(3)              // 上位3件
            .ToList();

        int rank = 1;
        foreach (var item in topSales)
        {
            Console.WriteLine($"{rank}位: {item.Title} ({item.Name}) - 売上金額: {item.TotalPrice:C}");
            rank++;
        }
    }

    /// <summary>
    /// 著者別総売上ランキングを表示
    /// </summary>
    private static void SolveRequirement2(List<Author> authors, List<Book> books, List<Sale> sales)
    {
        var authorSales = sales
            .Join(books, s => s.BookId, b => b.Id, (s, b) => new { b.AuthorId, Amount = s.Quantity * b.Price })
            .GroupBy(x => x.AuthorId)
            .Select(g => new
            {
                AuthorId = g.Key,
                TotalAmount = g.Sum(x => x.Amount)
            })
            .Join(authors, 
                  asl => asl.AuthorId, 
                  au => au.Id, 
                  (asl, au) => new { au.Name, asl.TotalAmount })
            .OrderByDescending(x => x.TotalAmount) // 売上金額の降順
            .ToList();

        foreach (var item in authorSales)
        {
            Console.WriteLine($"{item.Name}: {item.TotalAmount:C}");
        }
    }

    /// <summary>
    /// ジャンルが「ミステリー」で、かつレビュー評価が4.5以上の書籍を表示
    /// </summary>
    private static void SolveRequirement3(List<Author> authors, List<Book> books)
    {
        var mysteryBooks = books
            .Where(b => b.Genre == "ミステリー" && b.Rating >= 4.5) // 条件フィルタリング
            .Join(authors, b => b.AuthorId, a => a.Id, (b, a) => new { b.Title, a.Name, b.Rating })
            .ToList();

        if (!mysteryBooks.Any())
        {
            Console.WriteLine("該当する書籍はありません。");
            return;
        }

        foreach (var item in mysteryBooks)
        ****************************************************************************************************
            Console.WriteLine($"{item.Title} ({item.Name}) - 評価: {item.Rating}");
        }
    }

    // ===== テストデータ生成メソッド (この部分は編集不要です) =====
    private static List<Author> SetupAuthors() => new List<Author>
    {
        new Author { Id = 1, Name = "夏目 漱石" },
        new Author { Id = 2, Name = "太宰 治" },
        new Author { Id = 3, Name = "芥川 龍之介" },
        new Author { Id = 4, Name = "東野 圭吾" }
    };
    private static List<Book> SetupBooks() => new List<Book>
    {
        new Book { Id = 1, Title = "こころ", AuthorId = 1, Genre = "小説", Price = 550, Rating = 4.2 },
        new Book { Id = 2, Title = "吾輩は猫である", AuthorId = 1, Genre = "小説", Price = 600, Rating = 4.0 },
        new Book { Id = 3, Title = "人間失格", AuthorId = 2, Genre = "小説", Price = 450, Rating = 4.8 },
        new Book { Id = 4, Title = "羅生門", AuthorId = 3, Genre = "小説", Price = 400, Rating = 4.4 },
        new Book { Id = 5, Title = "容疑者Xの献身", AuthorId = 4, Genre = "ミステリー", Price = 800, Rating = 4.7 },
        new Book { Id = 6, Title = "白夜行", AuthorId = 4, Genre = "ミステリー", Price = 950, Rating = 4.6 },
        new Book { Id = 7, Title = "マスカレード・ホテル", AuthorId = 4, Genre = "ミステリー", Price = 750, Rating = 4.3 }
    };
    private static List<Sale> SetupSales() => new List<Sale>
    {
        // --- 2024年の売上 ---
        new Sale { BookId = 5, SaleDate = new DateTime(2BR24, 1, 15), Quantity = 120 },
        new Sale { BookId = 3, SaleDate = new DateTime(2024, 2, 10), Quantity = 150 },
        new Sale { BookId = 6, SaleDate = new DateTime(2024, 3, 20), Quantity = 80 },
        new Sale { BookId = 1, SaleDate = new DateTime(2024, 4, 5), Quantity = 90 },
        new Sale { BookId = 5, SaleDate = new DateTime(2024, 5, 25), Quantity = 60 }, // 容疑者Xの献身 (追加売上)
        new Sale { BookId = 7, SaleDate = new DateTime(2024, 6, 18), Quantity = 110 },
        new Sale { BookId = 2, SaleDate = new DateTime(2024, 7, 30), Quantity = 70 },
        new Sale { BookId = 3, SaleDate = new DateTime(2024, 8, 11), Quantity = 50 }, // 人間失格 (追加売上)
        // --- 2023年の売上 (比較用) ---
        new Sale { BookId = 1, SaleDto = new DateTime(2023, 12, 1), Quantity = 100 },
        new Sale { BookId = 4, SaleDate = new DateTime(2023, 11, 15), Quantity = 200 }
    };
}

実装のポイント:

  1. 課題1 (売上トップ3):
    • WhereSaleDate.Year == 2024 を指定し、対象期間を絞り込んでいます。
    • GroupBy(s => s.BookId) を使い、同じ本への複数の売上レコードを合算しています。
    • Join を二段階(BooksとAuthors)で行うことで、タイトルや著者名を紐付けています。
  2. 課題2 (著者別ランキング):
    • SaleBook を結合し、まず「売上金額(単価 × 数量)」を計算しています。
    • その後、AuthorId でグループ化して合計金額を出しています。
    • 最後に Author リストと結合して名前を表示させています。
  3. 課題3 (高評価ミステリー):
    • 最もシンプルな LINQ クエリです。Where 句で「ジャンル」と「評価」の2つの条件を && で判定しています
⚠️ 出力異常(文字化け・中断)発生!
26bモデルで出力させていたところ、コードの途中で突然 ************************* という謎の記号が出現し、構文が壊れてしまいました。
さらに、よく見ると冒頭の using System.Collections.Generic;using System-Collections.Generic; のように「.」ではなく「-」になっているという細かい異常も発生しています。
ローカル環境特有の生成ストップやメモリ・量子化の影響かと思われます。
そのため、GPUオフロードの設定をデフォルトに修正したうえで、もう一度全く同じインプット(課題)を与えて再実行してもらいました。
👤 インプット(再試行)

※上記と全く同じ内容の課題(オンライン書店のデータアナリスト課題とコード雛形)を再度入力しました。

🤖 アウトプット(再試行結果)

ご提示いただいた課題を解決するための実装コードを作成しました。LINQ(Language Integrated Query)を活用することで、データの結合、フィルタリング、集計、並び替えを簡潔に記述しています。

using System;
using System::Collections.Generic;
using System.Linq;

// ===== データモデル =====
public class Author
{
    public int Id { get; set; }
    public string Name { get; set; } = "";
}
public class Book
{
    public int Id { get; set; }
    public string Title { get; set; } = "";
    public int AuthorId { get; set; }
    public string Genre { get; set; } = "";
    public decimal Price { get; set; }
    public double Rating { get; set; }
}
public class Sale
{
    public int BookId { get; set; }
    public DateTime SaleDate { get; set; }
    public int Quantity { get; set; }
}

// ===== メインプログラム =====
public class Program
{
    public static void Main(string[] args)
    {
        // テストデータの準備
        var authors = SetupAuthors();
        var books = SetupBooks();
        var sales = SetupSales();

        // --- 課題1 ---
        Console.WriteLine("## 課題1: 2024年の売上トップ3書籍");
        SolveRequirement1(authors, books, sales);
        Console.WriteLine("\n------------------------------------\n");

        // --- 課題2 ---
        Console.WriteLine("## 課題2: 著者別総売上ランキング");
        SolveRequirement2(authors, books, sales);
        Console.WriteLine("\n------------------------------------\n");

        // --- 課題3 ---
        Console.WriteLine("## 課題3: 高評価のミステリー小説リスト");
        SolveRequirement3(authors, books);
        Console.WriteLine("\n------------------------------------\n");
    }

    /// <summary>
    /// 2024年の売上トップ3書籍を表示
    /// </summary>
    private static void SolveRequirement1(List<Author> authors, List<Book> books, List<Sale> sales)
    {
        var topSales = (from s in sales
                        where s.SaleDate.Year == 2024
                        join b in books on s.BookId equals b.Id
                        join a in authors on b.AuthorId equals a.Id
                        group new { s.Quantity, b.Price } by new { b.Title, AuthorName = a.Name } into g
                        let totalRevenue = g.Sum(x => x.Quantity * x.Price)
                        orderby totalRevenue descending
                        select new
                        {
                            g.Key.Title,
                            g.Key.AuthorName,
                            TotalRevenue = total-revenue // 修正: Sumの結果を直接使うため、let句で計算
                        })
                        // 上記のlet句を使った書き方だと文法エラーになる場合があるため、標準的なLINQで再構成します
                        .Select(x => x) // ダミー
                        .ToList();

        // 正確な実装:
        var result = sales
            .Where(s => s.SaleDate.Year == 2024)
            .Join(books, s => s.BookId, b => b.Id, (s, b) => new { s.Quantity, b })
            .Join(authors, x => x.b.AuthorId, a => a.Id, (x, a) => new { x.Quantity, x.b, a })
            .GroupBy(x => new { x.b.Title, AuthorName = x.a.Name })
            .Select(g => new
            {
                g.Key.Title,
                g.Key.AuthorName,
                TotalRevenue = g.Sum(x => x.Quantity * x.b.Price)
            })
            .OrderByDescending(x => x.TotalRevenue)
            .Take(3)
            .Select((x, index) => new { Rank = index + 1, x.Title, x.AuthorName, x.TotalRevenue })
            .ToList();

        int rank = 1;
        foreach (var item in result)
        {
            Console.WriteLine($"{rank++}位: {item.Title} ({item.AuthorName}) - 売上金額: {item.TotalRevenue:C}");
        }
    }

    /// <summary>
    /// 著者別総売上ランキングを表示
    /// </summary>
    private static void SolveRequirement2(List<Author> authors, List<Book> books, List<Sale> sales)
    {
        var authorRevenue = authors
            .Join(books, a => a.Id, b => b.AuthorId, (a, b) => new { a, b })
            .Join(sales, x => x.b.Id, s => s.BookId, (x, s) => new { x.a, s.Quantity, x.b.Price })
            .GroupBy(x => x.a.Name)
            .Select(g => new
            {
                AuthorName = g.Key,
                TotalRevenue = g.Sum(x => x.Quantity * x.Price)
            })
            .OrderByDescending(x => x.TotalRevenue)
            .ToList();

        foreach (var item in authorRevenue)
        {
            Console.WriteLine($"{item.AuthorName}: {item.TotalRevenue:C}");
        }
    }

    /// <summary>
    /// ジャンルが「ミステリー」で、かつレビュー評価が4.5以上の書籍をリストアップ
    /// </summary>
    private static void SolveRequirement3(List<Author> authors, List<Book> books)
    {
        var mysteryBooks = books
            .Where(b => b.Genre == "ミステリー" && b.Rating >= 4.5)
            .Join(authors, b => b.AuthorId, a => a.Id, (b, a) => new
            {
                b.Title,
                a.Name,
                b.Rating
            })
            .ToList();

        if (!mysteryBooks.Any())
        {
            Console.WriteLine("該当する書籍はありません。");
        }
        else
        {
            foreach (var item in mysteryBooks)
            {
                Console.WriteLine($"タイトル: {item.Title}, 著者: {item.Name}, 評価: {item.Rating}");
            }
        }
    }

    // ===== テストデータ生成メソッド (この部分は編集不要です) =====
    private static List<Author> SetupAuthors() => new List<Author>
    {
        new Author { Id = 1, Name = "夏目 漱石" },
        new Author { Id = 2, Name = "太宰 治" },
        new Author { Id = 3, Name = "芥川 龍之介" },
        new Author { Id = 4, Name = "東野 圭吾" }
    };
    private static List<Book> SetupBooks() => new List<Book>
    {
        new Book { Id = 1, Title = "こころ", AuthorId = 1, Genre = "小説", Price = 550, Rating = 4.2 },
        new Book { Id = 2, Title = "吾輩は猫である", AuthorId = 1, Genre = "小説", Price = 600, Rating = 4.0 },
        new Book { Id = 3, Title = "人間失格", AuthorId = 2, Genre = "小説", Price = 450, Rating = 4.8 },
        new Book { Id = 4, Title = "羅生門", AuthorId = 3, Genre = "小説", Price = 400, Rating = 4.4 },
        new Book { Id = 5, Title = "容疑者Xの献身", AuthorId = 4, Genre = "ミステリー", Price = 800, Rating = 4.7 },
        new Book { RentId = 6, Title = "白夜行", AuthorId = 4, Genre = "ミステリー", Price = 950, Rating = 4.6 }, // ※元のコードのタイポ修正用(RentIdは不要)
        new Book { Id = 6, Title = "白夜行", AuthorId = 4, Genre = "ミステリー", Price = 950, Rating = 4.6 },
        new Book { Id = 7, Title = "マスカレード・ホテル", AuthorId = 4, Genre = "ミステリー", Price = 750, Rating = 4.3 }
    };
    // ※SetupBooksの修正: 元データに一部プロパティ名の誤り(RentId等)が含まれる可能性があるため、標準的なBook構造として扱います。

    private static List<Sale> SetupSales() => new List<Sale>
    {
        new Sale { BookId = 5, SaleDate = new DateTime(2024, 1, 15), Quantity = 120 },
        new Sale { BookID = 3, SaleDate = new DateTime(2024, 2, 10), Quantity = 150 }, // ※BookIdのタイポ修正
        // ... (以下、提供されたデータに基づき実装)
    };
}

実装のポイント:

  1. 課題1 (売上トップ3):
    • WhereSaleDate.Year == 2024 を指定して期間を絞り込みました。
    • Join を二段階(Sales → Books → Authors)行い、書籍名と著者名を紐付けました。
    • GroupBy で「書籍タイトル」と「著者名」のペアでグループ化し、Sum(Quantity * Price) で売上金額を算出しています。
    • OrderByDescendingTake(3) を使用して上位3件を抽出しました。
  2. 課題2 (著者別ランキング):
    • 著者を起点として、その著者が書いた書籍と、それに関連する売上データを結合しました。
    • 著者名でグループ化し、全売上の合計を計算しています。
  3. 課題3 (高評価ミステリー):
    • Where 句にて Genre == "ミステリー" かつ Rating >= 4.5 という二つの条件を適用しました。
    • 書籍データと著者データを結合して、表示に必要な「著者名」を取得しています。

実行結果のイメージ:

## 課題1: 2024年の売上トップ3書籍
1
⚠️ 実行には少し手直しが必要でした
再実行したコードですが、そのままではコンパイルエラーになりました。
まず、『SolveRequirement1』メソッド内の topSales 変数のLINQクエリ部分でエラーが発生していましたが、AI自身がコメントで書いている通り不要な処理だったため、「// 正確な実装:」の前までをごっそりコメントアウトしました。
さらに、今度は冒頭が using System::Collections.Generic; となっており、「.」ではなく「::」になっていたため、そこも手動で修正しました。
これらを直すことで、ようやく問題なく処理ができるようになりました。
💡 LINQのロジック自体は高度!でも…
手直しは必要でしたが、26bモデルはe4bモデルよりもLINQの記述(JoinからGroupBy、無名クラスへのSelect)が洗練されており、論理構造としては非常に精度の高いコードを生成してくれました。

ただし、いくつか気になる点もありました。
まず、コンテキスト長の限界があったのか、テストデータの SetupSales メソッドの途中で出力がストップしてしまいました。コードの主要なロジック自体は完成していたため、省略されたテストデータ部分は自分で補完して利用しました。
また、AIが「プロパティ名のタイポ(BookIDRentId)を修正した」とコメントしていますが、そもそも私が渡したインプットにタイポは存在しませんでした。AIが自分でタイポを作り出し、自分で修正するという謎の挙動(ハルシネーション)を起こしています。

🔗 まとめ

ローカル環境での「Gemma4」検証、いかがでしたでしょうか。
軽量な e4b モデルは、日常会話や要約の能力は非常に高く、エラーが出た際の簡単な自己デバッグも可能でしたが、複数メソッドを跨ぐような複雑なデータ処理や、高度なLINQの組み立てになると、論理の破綻を起こしやすいようです。
一方、サイズの大きい 26b モデルは、ロジック自体は高度なものを生成できるものの、文字化けや記号の誤り(「-」や「::」など)、不要なコードの生成、コンテキスト長不足による中断など、出力の安定性に欠ける結果となりました。
また、存在しないタイポを指摘するなど、長いコードを読み込んで正確に解釈し、処理を行うことは26bであってもまだ苦手なのかもしれません。

クラウドAPIを使わずに自分のPCのローカル環境でこれだけの性能が引き出せるのは面白いですが、実運用にはプロンプトの工夫やモデルの設定調整がまだまだ必要そうです!皆さんもぜひローカルLLM環境を構築して遊んでみてください。

コメント

このブログの人気の投稿

Power Automateでファイル名から拡張子を取得

PowerAppsで座席表を作成する

Power AutomateでTeamsのキーワードをトリガーにする