{ }codelper

学ぶ › C#

さらに先へ · 25

レコードのリストと列挙型

レコードをリストに集め、決まったいくつかの値には名前を付けます。

いくつかの値をまとめたレコードをリストに順に入れると、一枚の表になります。
そして「低い・中くらい・高い」のように決まったいくつかのうちの一つである値には、名前を付けておきます(列挙型、enum)。
下の例は、生徒の名前と点数を読んで等級を付けたレコードをリストに集め、1行ずつ出力したあと、等級が HIGH の生徒の名前だけを出力します。

using System;using System.Collections.Generic; enum Level { LOW, MID, HIGH }   // 정해진 세 가지 중 하나 record Student(string Name, int Score, Level Level); class Program{    static Level LevelOf(int score)    {        if (score >= 80) return Level.HIGH;        if (score >= 60) return Level.MID;        return Level.LOW;    }     static void Main()    {        int n = int.Parse(Console.ReadLine());        var students = new List<Student>();   // 묶음 자료(이름, 점수, 등급)의 목록        for (int i = 0; i < n; i++)        {            string[] parts = Console.ReadLine().Split();            int score = int.Parse(parts[1]);            students.Add(new Student(parts[0], score, LevelOf(score)));        }         foreach (Student s in students)        {            Console.WriteLine(s.Name + " " + s.Score + " " + s.Level);        }         var high = new List<string>();        foreach (Student s in students)        {            if (s.Level == Level.HIGH) high.Add(s.Name);        }        Console.WriteLine("HIGH: " + string.Join(" ", high));    }}
stdin
4
mina 92
jun 58
sora 75
theo 81
stdout
mina 92 HIGH
jun 58 LOW
sora 75 MID
theo 81 HIGH
HIGH: mina theo

ポイント

  • レコード一つは、一人(一つの物)についての値のまとまりです。
    これをリストに入れると、一つの場所がレコード一つである表になります。
    students から取り出した s の名前・点数・等級を一緒に扱います。
  • レコードはループの中で、毎回新しく作ってリストに入れます。
    名前のリストと点数のリストを別々に持つより、一人分の値がばらばらにならなくて済みます。
  • 列挙型(enum)は、決まったいくつかの値に名前を付けた型です。
    文字列 "HIGH" や数 2 を直接書く代わりに Level.HIGH と書きます。
    列挙型のある言語では、Level.HIHG のように存在しない名前を書くとエラーで知らせてくれます(Python は実行中に、ほかはコンパイル時に)。
  • 列挙型の中身は言語ごとに違います。
    C、C++、TypeScript、C# は中身が 0、1、2 のような数です。
    名前を出力するとき、Java と C# はそのまま出力すればよく、Python は .name、TypeScript は Level[値]、C と C++ は名前を書いておいた配列から取り出します。
  • 列挙型のない JavaScript と Craykon は、名前を付けた定数で同じことをします。
    コードのあちこちに 2 と書く代わりに、HIGH という名前を一度決めて使います。
  • 点数を等級に変える仕事は、関数一つ(levelOf)にまとめます。
    基準が変わっても、直すのは一か所だけです。

言語ごとの書き方

Pythonレコードは辞書 {"name": name, "score": score} で作り、students.append(...) で入れて s["name"] で取り出します。
@dataclass を付けたクラスで作れば s.name で取り出せます。
列挙型は from enum import Enum のあと、class Level(Enum): の中に HIGH = 2 のように書きます。
名前は level.name、中の値は level.value、名前の文字列から探すときは Level["HIGH"]
JavaScriptレコードはオブジェクト { name: name, score: score } で作り、students.push(...) で入れて s.name で取り出します。
変数名とキーが同じなら { name, score } と短く書けます。
enum がないので、const Level = Object.freeze({ LOW: "LOW", MID: "MID", HIGH: "HIGH" }); のように凍結したオブジェクトを使います。
凍結したオブジェクトは Level.HIGH = "x" としても変わりません。
このサイトの採点のように普通のスクリプトとして動かすとエラーなしで無視され、厳格モード("use strict" や ES モジュール)では TypeError で止まります。
存在しない名前 Level.HIHG はエラーではなく undefined です
TypeScriptレコードの形は type Student = { name: string; score: number; level: Level }; で決め、リストは Student[]。
列挙型は enum Level { LOW, MID, HIGH } で、中身は 0、1、2 です。
名前は Level[s.level] で得ます。
いくつかの文字列のうちの一つと決める type Level = "LOW" | "MID" | "HIGH"; もよく使います
Javaレコードは record Student(String name, int score, Level level) {} で作り、new Student(...)、取り出すときは s.name()。
リストは List<Student>。
列挙型は enum Level { LOW, MID, HIGH } で、出力すると名前がそのまま出ます。
順番の番号は level.ordinal()、名前の文字列から探すときは Level.valueOf("HIGH")
C#レコードは record Student(string Name, int Score, Level Level); で作り、new Student(...)、取り出すときは s.Name。
リストは List<Student>。
列挙型は enum Level { LOW, MID, HIGH } で、出力すると名前が出ます。
中の数は (int)level、名前の文字列から探すときは Enum.Parse<Level>("HIGH")
C++レコードは struct Student { std::string name; int score; Level level; };、リストは std::vector<Student> に push_back({name, score, levelOf(score)})。
列挙型は enum class Level { LOW, MID, HIGH }; で、Level::HIGH と書きます。
enum class は std::cout に直接入れられず、int にひとりでに変わることもないので、名前の配列から LEVEL_NAMES[static_cast<int>(s.level)] で取り出します
Cレコードは typedef struct { char name[32]; int score; Level level; } Student;、リストは配列 Student students[100]; で students[i].score のように使います。
列挙型は typedef enum { LOW, MID, HIGH } Level; で、中身は整数 0、1、2 です。
名前を出力する機能がないので、LEVEL_NAMES[students[i].level] のように名前の配列から取り出します
Craykon構造体がないので、レコードは辞書で作ります。
毎回 s = 辞書(); で新しく作り、s["name"] = ... で埋めてから students.追加(s)。
enum もないので、いちばん上に LOW = 0; MID = 1; HIGH = 2; のように名前を付けた定数を置き、名前はリスト LEVEL_NAMES[s["level"]] から取り出します。
定数を変えられないようにする機能はないので、変えないと約束します

よくある間違い

列挙型の値を文字列や数と比べたPython

from enum import Enum
class Level(Enum):
    LOW = 0
    MID = 1
    HIGH = 2
level = Level.HIGH
print(level == "HIGH", level == 2)

→ False False — Level.HIGH は文字列 "HIGH" でも数 2 でもない、別の値です。エラーなしで偽になるので見つけにくいです

level == Level.HIGH のように列挙型どうしで比べます。
入力で受け取った文字列は、先に Level["HIGH"] で列挙型に変えます。

列挙型を出力したら名前ではなく数が出たTypeScript · C · C++

enum Level { LOW, MID, HIGH }
console.log(Level.HIGH);

→ 2 — 中に入っている数がそのまま出ます。C の printf("%d\n", level); も、C++ で enum Level を std::cout に入れても 2 です。C++ の enum class を std::cout に入れるとコンパイルエラーです

TypeScript は Level[Level.HIGH] で名前を得ます。
C と C++ は {"LOW", "MID", "HIGH"} のような名前の配列を作って、そこから取り出します。

一つのレコードを書き換えながら何度も入れたJavaScript · TypeScript · Python · Craykon

const s = {};
const students = [];
for (const name of ["mina", "jun"]) {
  s.name = name;
  students.push(s);
}
console.log(students);

→ [ { name: 'jun' }, { name: 'jun' } ] — リストの二つの場所が同じ一つのレコードを指しているので、最後に書いた値だけが残ります

レコードをループの中で作ります(const s = { name: name };)。
そうすれば毎回新しいレコードができます。

言語ごとの違い
列挙型だからといって、決まった値しか入らないわけではありません。
C では Level x = 7; がそのままコンパイルでき、7 が出力されます。
C# で (Level)7 を出力しても 7 です。
C++ の enum class は Level x = 7; をコンパイルエラーで止めてくれます。
外から来た数を列挙型に変えるときは、先に範囲を確かめます。

練習問題