計算機科学のブログ

LINQ and lambdas - Get control of your data - refactoring, lambda expressions, switch expressions, LINQ methods(OrderBy, GroupBy, Select, Join)

Head First C Sharp: A Learner’s Guide to Real-World Programming with C Sharp and .NET (Andrew Stellman(著)、Jennifer Greene(著)、O’Reilly Media)の Chapter 9(LINQ and lambdas - Get control of your data)、p.518(Sharpen your pencil)の解答を求めてみる。

コード

Program.cs

using ConsoleApp1;

var done = false;
while (!done)
{
    Console.WriteLine(
        "\nPress G to group comics by price, R to get reviews, any other key to quit\n");
    // switch (Console.ReadKey(true).KeyChar.ToString().ToUpper())
    // {
    //     case "G":
    //         done = GroupComicsByPrice();
    //         break;
    //     case "R":
    //         done = GetReviews();
    //         break;
    //     default:
    //         done = true;
    //         break;
    // }
    done = Console.ReadKey(true).KeyChar.ToString().ToUpper() switch
    {
        "G" => GroupComicsByPrice(),
        "R" => GetReviews(),
        _ => true,
    };
}
static bool GroupComicsByPrice()
{
    var groups = ComicAnalyzer.GroupComicsByPrice(Comic.Catalog, Comic.Prices);
    foreach (var group in groups)
    {
        Console.WriteLine($"{group.Key} comics");
        foreach (var comic in group)
        {
            Console.WriteLine($"#{comic.Issue} {comic.Name}: {Comic.Prices[comic.Issue]:c}");
        }
    }
    return false;
}
static bool GetReviews()
{
    var reviews = ComicAnalyzer.GetReviews(Comic.Catalog, Comic.Reviews);
    foreach (var review in reviews)
    {
        Console.WriteLine(review);
    }
    return false;
}

Comic.cs

using System;

namespace ConsoleApp1;

public class Comic
{
    public string Name { get; set; } = "";
    public int Issue { get; set; }
    public override string ToString()
    {
        return $"{Name} (Issue #{Issue})";
    }
    public static readonly IEnumerable<Comic> Catalog =
     new List<Comic>{
        new Comic(){Name="Johnny America vs. the Pinko", Issue=6},
        new Comic(){Name="Rock and Roll (limitededition)", Issue=19},
        new Comic(){Name="Woman's Work", Issue=36},
        new Comic(){Name="Hippie Madness (misprinted)", Issue=57},
        new Comic(){Name="Revenge of the New Wave Freak (damaged)", Issue=68},
        new Comic(){Name="Black Monday", Issue=74},
        new Comic(){Name="Tattoo Madness", Issue=83},
        new Comic(){Name="The Death of the Object", Issue=97},
    };

    public static readonly IEnumerable<Review> Reviews = new[]
    {
        new Review() { Issue = 36, Critic = Critics.MuddyCritic, Score = 37.6},
        new Review() { Issue = 74, Critic = Critics.RottenTornadoes, Score =22.8 },
        new Review() { Issue = 74, Critic = Critics.MuddyCritic, Score = 87.2 },
        new Review() { Issue = 83, Critic = Critics.RottenTornadoes, Score = 89.4 },
        new Review() { Issue = 97, Critic = Critics.MuddyCritic, Score = 98.1 },
    };
    public static readonly IReadOnlyDictionary<int, decimal> Prices =
        new Dictionary<int, decimal>{
            {6, 3600M},
            {19,500M},
            {36,650M},
            {57,13525M},
            {68,250M},
            {74,75M},
            {83,25.75M},
            {97,35.25M},
    };

}

ComicAnalyzer.cs

using System;

namespace ConsoleApp1;

public class ComicAnalyzer
{
    private static PriceRange CalculatePriceRange(Comic comic)
    {
        if (Comic.Prices[comic.Issue] < 100)
        {
            return PriceRange.Cheap;
        }
        return PriceRange.Expensive;
    }
    public static IEnumerable<IGrouping<PriceRange, Comic>> GroupComicsByPrice(IEnumerable<Comic> catalog, IReadOnlyDictionary<int, decimal> prices)
    {
        // return from comic in catalog
        //        orderby prices[comic.Issue]
        //        group comic by CalculatePriceRange(comic) into grouped
        //        select grouped;
        return catalog
          .OrderBy(comic => prices[comic.Issue])
          .GroupBy(CalculatePriceRange);
    }

    public static IEnumerable<object> GetReviews(IEnumerable<Comic> catalog, IEnumerable<Review> reviews)
    {
        // return from comic in catalog
        //        orderby comic.Issue
        //        join review in reviews
        //        on comic.Issue equals review.Issue
        //        select $"{review.Critic} rated #{comic.Issue} '{comic.Name}' {review.Score:0.00}";
        return catalog
         .OrderBy(comic => comic.Issue)
         .Join(reviews,
              (comic) => comic.Issue,
              (review) => review.Issue,
              (comic, review) => $"{review.Critic} rated #{comic.Issue} '{comic.Name}' {review.Score:0.00}");

    }
}

Critics.cs

namespace ConsoleApp1;

public enum Critics
{
    MuddyCritic,
    RottenTornadoes,
}

PriceRange.cs

namespace ConsoleApp1;

public enum PriceRange
{
    Cheap,
    Expensive,
}

Review.cs

using System;

namespace ConsoleApp1;

public class Review
{
    public int Issue { get; set; }
    public Critics Critic { get; set; }
    public double Score { get; set; }
    public Review()
    {
    }
}

入出力結果(Terminal, Zsh)

% dotnet run

Press G to group comics by price, R to get reviews, any other key to quit

Cheap comics
#83 Tattoo Madness: ¥26
#97 The Death of the Object: ¥35
#74 Black Monday: ¥75
Expensive comics
#68 Revenge of the New Wave Freak (damaged): ¥250
#19 Rock and Roll (limitededition): ¥500
#36 Woman's Work: ¥650
#6 Johnny America vs. the Pinko: ¥3,600
#57 Hippie Madness (misprinted): ¥13,525

Press G to group comics by price, R to get reviews, any other key to quit

MuddyCritic rated #36 'Woman's Work' 37.60
RottenTornadoes rated #74 'Black Monday' 22.80
MuddyCritic rated #74 'Black Monday' 87.20
RottenTornadoes rated #83 'Tattoo Madness' 89.40
MuddyCritic rated #97 'The Death of the Object' 98.10

Press G to group comics by price, R to get reviews, any other key to quit

%