进阶 · 25
记录列表与枚举
把记录收进列表,给固定的几种取值起名字。
把由几个值组成的记录一条条放进列表,就成了一张表。
而像“低、中、高”这样只能是固定几种之一的值,要给它们起名字(枚举,enum)。
下面的示例读入学生的姓名和分数,把带有等级的记录收进列表,逐行输出,然后只输出等级为 HIGH 的学生姓名。
from enum import Enum class Level(Enum): # 정해진 세 가지 중 하나 LOW = 0 MID = 1 HIGH = 2 def level_of(score): if score >= 80: return Level.HIGH if score >= 60: return Level.MID return Level.LOW n = int(input())students = [] # 묶음 자료(이름, 점수, 등급)의 목록for _ in range(n): name, score = input().split() score = int(score) students.append({"name": name, "score": score, "level": level_of(score)}) for s in students: print(s["name"], s["score"], s["level"].name) high = []for s in students: if s["level"] == Level.HIGH: high.append(s["name"])print("HIGH:", *high)// enum이 없어서, 바꿀 수 없게 얼린 객체에 이름을 모아 둬요const Level = Object.freeze({ LOW: "LOW", MID: "MID", HIGH: "HIGH" }); function levelOf(score) { if (score >= 80) return Level.HIGH; if (score >= 60) return Level.MID; return Level.LOW;} const lines = require("fs").readFileSync(0, "utf8").trim().split("\n");const n = Number(lines[0]);const students = []; // 묶음 자료(이름, 점수, 등급)의 목록for (let i = 1; i <= n; i++) { const [name, scoreText] = lines[i].trim().split(" "); const score = Number(scoreText); students.push({ name: name, score: score, level: levelOf(score) });} for (const s of students) { console.log(s.name + " " + s.score + " " + s.level);} const high = [];for (const s of students) { if (s.level === Level.HIGH) high.push(s.name);}console.log("HIGH: " + high.join(" "));import { readFileSync } from "node:fs"; enum Level { LOW, MID, HIGH } // 정해진 세 가지 중 하나(속은 0, 1, 2) type Student = { name: string; score: number; level: Level }; function levelOf(score: number): Level { if (score >= 80) return Level.HIGH; if (score >= 60) return Level.MID; return Level.LOW;} const lines: string[] = readFileSync(0, "utf8").trim().split("\n");const n: number = Number(lines[0]);const students: Student[] = []; // 묶음 자료(이름, 점수, 등급)의 목록for (let i = 1; i <= n; i++) { const [name, scoreText] = lines[i].trim().split(" "); const score = Number(scoreText); students.push({ name: name, score: score, level: levelOf(score) });} for (const s of students) { console.log(s.name + " " + s.score + " " + Level[s.level]); // Level[2]는 이름 "HIGH"} const high: string[] = [];for (const s of students) { if (s.level === Level.HIGH) high.push(s.name);}console.log("HIGH: " + high.join(" "));import java.util.*; public class Main { enum Level { LOW, MID, HIGH } // 정해진 세 가지 중 하나 record Student(String name, int score, Level level) {} static Level levelOf(int score) { if (score >= 80) return Level.HIGH; if (score >= 60) return Level.MID; return Level.LOW; } public static void main(String[] args) { Scanner in = new Scanner(System.in); int n = in.nextInt(); List<Student> students = new ArrayList<>(); // 묶음 자료(이름, 점수, 등급)의 목록 for (int i = 0; i < n; i++) { String name = in.next(); int score = in.nextInt(); students.add(new Student(name, score, levelOf(score))); } for (Student s : students) { System.out.println(s.name() + " " + s.score() + " " + s.level()); } List<String> high = new ArrayList<>(); for (Student s : students) { if (s.level() == Level.HIGH) high.add(s.name()); } System.out.println("HIGH: " + String.join(" ", 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)); }}#include <iostream>#include <string>#include <vector> enum class Level { LOW, MID, HIGH }; // 정해진 세 가지 중 하나const char* LEVEL_NAMES[] = {"LOW", "MID", "HIGH"}; // 이름은 따로 적어 둬요 struct Student { std::string name; int score; Level level;}; Level levelOf(int score) { if (score >= 80) return Level::HIGH; if (score >= 60) return Level::MID; return Level::LOW;} int main() { int n; std::cin >> n; std::vector<Student> students; // 묶음 자료(이름, 점수, 등급)의 목록 for (int i = 0; i < n; i++) { std::string name; int score; std::cin >> name >> score; students.push_back({name, score, levelOf(score)}); } for (const Student& s : students) { std::cout << s.name << ' ' << s.score << ' ' << LEVEL_NAMES[static_cast<int>(s.level)] << '\n'; } std::cout << "HIGH:"; for (const Student& s : students) { if (s.level == Level::HIGH) std::cout << ' ' << s.name; } std::cout << '\n';}#include <stdio.h> typedef enum { LOW, MID, HIGH } Level; // 정해진 세 가지 중 하나(속은 0, 1, 2)const char *LEVEL_NAMES[] = {"LOW", "MID", "HIGH"}; // 이름은 따로 적어 둬요 typedef struct { char name[32]; int score; Level level;} Student; Level level_of(int score) { if (score >= 80) return HIGH; if (score >= 60) return MID; return LOW;} int main(void) { int n; scanf("%d", &n); Student students[100]; // 묶음 자료(이름, 점수, 등급)의 배열 for (int i = 0; i < n; i++) { scanf("%31s %d", students[i].name, &students[i].score); students[i].level = level_of(students[i].score); } for (int i = 0; i < n; i++) { printf("%s %d %s\n", students[i].name, students[i].score, LEVEL_NAMES[students[i].level]); } printf("HIGH:"); for (int i = 0; i < n; i++) { if (students[i].level == HIGH) printf(" %s", students[i].name); } printf("\n"); return 0;}// enum이 없어서, 이름 붙인 상수로 정해요(바꾸지 않기로 약속한 변수)LOW = 0;MID = 1;HIGH = 2;LEVEL_NAMES = ["LOW", "MID", "HIGH"]; levelOf(score){ 如果 (score >= 80) { 返回 HIGH; } 如果 (score >= 60) { 返回 MID; } 返回 LOW;} n = 控制台.读行().转数字();students = []; // 묶음 자료(이름, 점수, 등급)의 목록循环 (i = 0; i < n; i++){ parts = 控制台.读行().分割(); s = 字典(); // 바퀴마다 새 묶음 자료를 만들어요 s["name"] = parts[0]; s["score"] = parts[1].转数字(); s["level"] = levelOf(s["score"]); students.添加(s);} 遍历 (s 在 students){ 控制台.输出(s["name"] + " " + s["score"] + " " + LEVEL_NAMES[s["level"]]);} high = [];遍历 (s 在 students){ 如果 (s["level"] == HIGH) { high.添加(s["name"]); }}控制台.输出("HIGH: " + high.连接(" "));4 mina 92 jun 58 sora 75 theo 81
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 里,
在 C# 里输出
C++ 的
把外面来的数转成枚举时,先检查范围。
是枚举,并不代表只有规定的值才能放进去。
在 C 里,
Level x = 7; 照样能编译,并输出 7。在 C# 里输出
(Level)7 也是 7。C++ 的
enum class 会用编译错误拦住 Level x = 7;。把外面来的数转成枚举时,先检查范围。