{ }codelper
switch 文剰余P0401

楽譜を k 半音ずらして書く

楽譜に書かれた音をすべて k 半音ずつずらして書きたい。
音は音名の後ろにオクターブの数字を付けて C4、F#3、Bb5 のように書く。
音名は大文字の C、D、E、F、G、A、B のどれか一文字で、そのすぐ後ろにシャープ # かフラット b(小文字)が一つ付くことがある。
シャープとフラットは七つの文字のどれにも付くことがあり、E#、Fb、B#、Cb も入力に現れる。
文字ごとに決まった半音の数は C 0、D 2、E 4、F 5、G 7、A 9、B 11 である。
音の高さは(オクターブの数字)× 12 +(文字の半音の数)で、シャープが付けばこれに 1 を足し、フラットが付けば 1 を引く。
だから B#4 と C5 は高さがどちらも 60 で、Cb4 と B3 は高さがどちらも 47 である。
音ごとに高さに k を足した新しい高さを求め、その高さの音を次の規則で書いて出力せよ。
新しい高さを 12 で割った余り 0、1、2、…、11 に、順に音名 C、C#、D、D#、E、F、F#、G、G#、A、A#、B を使う。
その後ろに、新しい高さを 12 で割った商をオクターブの数字として付ける。
出力にフラットは使わない。

入力
一行目に音の個数 n とずらす半音の数 k が空白一つで区切って与えられる。
二行目に n 個の音が空白一つで区切って与えられる。
1 ≤ n ≤ 1000、-12 ≤ k ≤ 12 で、k が負なら高さが下がる。
入力のオクターブの数字は 2 以上 6 以下である。
したがって新しい高さは常に 11 以上 96 以下である。
出力
ずらした n 個の音を入力の順に一行に空白一つで区切って出力する。
例 1 · stdin
5 3
C4 B4 F#3 Bb5 E2
stdout
D#4 D5 A3 C#6 G2
例 2 · stdin
3 0
B#4 Cb4 Fb3
stdout
C5 B3 E3
main.py
入力(stdin)

実行すると、これらの行が順に input()・標準入力に渡されます(実行中に入力を求めません)。

$ 準備完了

コードを入力して解くにはパソコンで開いてください。モバイルでは問題と解答を見られます。

ほかの言語では?言語別の解答を見る · 8 言語の解答

一文字を数に変える七つの分岐が、この問題の switch です。
C・C++ の switch は整数と文字しか受け取らないので case 'C': のように書き、文字列を入れるとコンパイルエラーです。
JS・TS は case "C": で文字列も比べられます。
Java は矢印形式 case 'C' -> 0;、C# は switch 式 'C' => 0,、Python は 3.10 からある match/case "C":、Craykon は 選択 (letter) { 場合 "C": … 抜ける; } と書きます。
**落とし穴その一:break を書き忘れる。
** C・C++・Java(コロン形式)・JS・TS の switch は break がないと次の case へ流れ落ちます。
case 'E': s = 4; の後ろの break を抜くと E が F の値 5 を受け取り、例 1 の E2 が G2 ではなく G#2 になります。
ここに載せた C の解答は分岐ごとに break を書き、C++・JS・TS の解答は分岐ごとに return ですぐ抜けます。
C# は中身のある case が次へ流れ落ちるとコンパイルエラー(CS0163)で、Java の矢印形式、C# の switch 式、Python の match、Craykon の 選択 は流れ落ちません。
**落とし穴その二:オクターブを別にして音名の位置だけを計算する。
** 文字の半音の数にシャープ・フラットと k を足した値 s で NAMES[s % 12] と オクターブ + s / 12 を求めると、例 2 の Cb4 で s が -1 になります。
C・C++・Java・C#・JS・TS・Craykon では -1 % 12 は -1 なので、リストの外を指します。
JS では NAMES[-1] が undefined で、これに数を足すので NaN が出ます。
Python だけは -1 % 12 が 11、-1 // 12 が -1 なので、同じ式でも B3 になります。
ここに載せた解答はどれも、オクターブ × 12 まで合わせた高さ一つで計算します。
高さは常に 11 以上なので、どの言語でも余りと商が負になりません。
商は C・C++・Java・C# では整数どうしの /、Python は //、JS・TS は Math.floor(p / 12)、Craykon は 数学.商(p, 12) です。
**落とし穴その三:シャープ・フラットがオクターブをまたぐことを忘れる。
** 音名はシャープまで入れて選ぶのに、オクターブは文字と k だけで決めると、例 2 の B#4 が C5 ではなく C4 に、Cb4 が B3 ではなく B4 になります。

Pythonmain.py · 31 行
NAMES = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]  def semitone(letter):    # 음이름 글자가 C에서 반음 몇 개 위인지 (match는 다음 case로 이어 내려가지 않는다)    match letter:        case "C":            return 0        case "D":            return 2        case "E":            return 4        case "F":            return 5        case "G":            return 7        case "A":            return 9        case _:            return 11  # B  n, k = map(int, input().split())out = []for note in input().split():    p = int(note[-1]) * 12 + semitone(note[0])  # 음 번호: 옥타브와 음이름을 한 수로    if len(note) == 3:        p += 1 if note[1] == "#" else -1    p += k    out.append(NAMES[p % 12] + str(p // 12))print(" ".join(out))
JavaScriptmain.js · 25 行
const NAMES = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]; // 음이름 글자가 C에서 반음 몇 개 위인지function semitone(letter) {  switch (letter) {    case "C": return 0;    case "D": return 2;    case "E": return 4;    case "F": return 5;    case "G": return 7;    case "A": return 9;    default: return 11; // B  }} const lines = require("fs").readFileSync(0, "utf8").split("\n");const k = Number(lines[0].trim().split(/\s+/)[1]);const out = lines[1].trim().split(/\s+/).map((note) => {  // 음 번호: 옥타브와 음이름을 한 수로 합친다  let p = Number(note[note.length - 1]) * 12 + semitone(note[0]);  if (note.length === 3) p += note[1] === "#" ? 1 : -1;  p += k;  return NAMES[p % 12] + Math.floor(p / 12);});console.log(out.join(" "));
TypeScriptmain.ts · 27 行
import { readFileSync } from "node:fs"; const NAMES: string[] = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]; // 음이름 글자가 C에서 반음 몇 개 위인지function semitone(letter: string): number {  switch (letter) {    case "C": return 0;    case "D": return 2;    case "E": return 4;    case "F": return 5;    case "G": return 7;    case "A": return 9;    default: return 11; // B  }} const lines: string[] = readFileSync(0, "utf8").split("\n");const k: number = Number(lines[0].trim().split(/\s+/)[1]);const out: string[] = lines[1].trim().split(/\s+/).map((note: string): string => {  // 음 번호: 옥타브와 음이름을 한 수로 합친다  let p: number = Number(note[note.length - 1]) * 12 + semitone(note[0]);  if (note.length === 3) p += note[1] === "#" ? 1 : -1;  p += k;  return NAMES[p % 12] + Math.floor(p / 12);});console.log(out.join(" "));
JavaMain.java · 35 行
import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader; public class Main {    static final String[] NAMES = {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"};     // 음이름 글자가 C에서 반음 몇 개 위인지 (화살표 switch는 이어 내려가지 않는다)    static int semitone(char letter) {        return switch (letter) {            case 'C' -> 0;            case 'D' -> 2;            case 'E' -> 4;            case 'F' -> 5;            case 'G' -> 7;            case 'A' -> 9;            default -> 11; // B        };    }     public static void main(String[] args) throws IOException {        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));        int k = Integer.parseInt(in.readLine().trim().split("\\s+")[1]);        StringBuilder out = new StringBuilder();        for (String note : in.readLine().trim().split("\\s+")) {            // 음 번호: 옥타브와 음이름을 한 수로 합친다            int p = (note.charAt(note.length() - 1) - '0') * 12 + semitone(note.charAt(0));            if (note.length() == 3) p += note.charAt(1) == '#' ? 1 : -1;            p += k;            if (out.length() > 0) out.append(' ');            out.append(NAMES[p % 12]).append(p / 12);        }        System.out.println(out);    }}
C#Main.cs · 34 行
using System;using System.Collections.Generic; class Program{    static readonly string[] Names = { "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" };     // 음이름 글자가 C에서 반음 몇 개 위인지    static int Semitone(char letter) => letter switch    {        'C' => 0,        'D' => 2,        'E' => 4,        'F' => 5,        'G' => 7,        'A' => 9,        _ => 11, // B    };     static void Main()    {        int k = int.Parse(Console.ReadLine().Split(' ', StringSplitOptions.RemoveEmptyEntries)[1]);        var result = new List<string>();        foreach (string note in Console.ReadLine().Split(' ', StringSplitOptions.RemoveEmptyEntries))        {            // 음 번호: 옥타브와 음이름을 한 수로 합친다            int p = (note[note.Length - 1] - '0') * 12 + Semitone(note[0]);            if (note.Length == 3) p += note[1] == '#' ? 1 : -1;            p += k;            result.Add(Names[p % 12] + (p / 12));        }        Console.WriteLine(string.Join(" ", result));    }}
C++main.cpp · 35 行
#include <iostream>#include <string>using namespace std; const string NAMES[12] = {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"}; // 음이름 글자가 C에서 반음 몇 개 위인지int semitone(char letter) {    switch (letter) {        case 'C': return 0;        case 'D': return 2;        case 'E': return 4;        case 'F': return 5;        case 'G': return 7;        case 'A': return 9;        default:  return 11; // B    }} int main() {    int n, k;    cin >> n >> k;    for (int i = 0; i < n; i++) {        string note;        cin >> note;        // 음 번호: 옥타브와 음이름을 한 수로 합친다        int p = (note.back() - '0') * 12 + semitone(note[0]);        if (note.size() == 3) p += note[1] == '#' ? 1 : -1;        p += k;        if (i) cout << ' ';        cout << NAMES[p % 12] << p / 12;    }    cout << '\n';    return 0;}
Cmain.c · 36 行
#include <stdio.h>#include <string.h> static const char *NAMES[12] = {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"}; /* 음이름 글자가 C에서 반음 몇 개 위인지 */static int semitone(char letter) {    int s;    switch (letter) {        case 'C': s = 0; break;        case 'D': s = 2; break;        case 'E': s = 4; break;        case 'F': s = 5; break;        case 'G': s = 7; break;        case 'A': s = 9; break;        default:  s = 11; break; /* B */    }    return s;} int main(void) {    int n, k;    char note[8];    scanf("%d %d", &n, &k);    for (int i = 0; i < n; i++) {        scanf("%7s", note);        size_t len = strlen(note);        /* 음 번호: 옥타브와 음이름을 한 수로 합친다 */        int p = (note[len - 1] - '0') * 12 + semitone(note[0]);        if (len == 3) p += note[1] == '#' ? 1 : -1;        p += k;        printf("%s%s%d", i ? " " : "", NAMES[p % 12], p / 12);    }    printf("\n");    return 0;}
Craykonmain.cray · 53 行
names = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]; // 음이름 글자가 C에서 반음 몇 개 위인지반음(letter){    s = 11; // B    選択 (letter)    {        場合 "C":            s = 0;            抜ける;        場合 "D":            s = 2;            抜ける;        場合 "E":            s = 4;            抜ける;        場合 "F":            s = 5;            抜ける;        場合 "G":            s = 7;            抜ける;        場合 "A":            s = 9;            抜ける;    }    返す s;} first = コンソール.数読み();k = first[1];notes = コンソール.行読み().空白除去().分ける(" ");out = [];それぞれ (note の中 notes){    // 음 번호: 옥타브와 음이름을 한 수로 합친다    p = note[note.長さ - 1].数に() * 12 + 반음(note[0]);    もし (note.長さ == 3)    {        もし (note[1] == "#")        {            p = p + 1;        }        でなければ        {            p = p - 1;        }    }    p = p + k;    out.追加(names[p % 12] + 数学.商(p, 12));}コンソール.書く(out.つなぐ(" "));