今回は paiza のクエリで「Vtuber」の問題に挑戦!
少し前に、宝鐘マリンさんがチャンネル登録者数が400万人突破してて、すごい人気ですよね!
問題概要
あなたはVtuberとして活動中で、配信のたびに視聴者から「superchat」と「メンバーシップ加入」の履歴がある。
-
superchat
視聴者が配信者に送った「応援金」の合計額を表す。
1人のアカウントが複数回送ることもあるので、合計額を管理する必要がある。
-
membership
視聴者がメンバーシップ制度に加入したことを示す。
加入者リストを管理する。
読み上げる順番は以下の通り:
- superchatを送ったアカウントを「合計金額の高い順」に読み上げる。
- 同じ合計金額の場合は「名前を辞書順降順(Z→A)」に並べる。
- その後、メンバーシップ加入者を「名前の辞書順昇順(A→Z)」で読み上げる。
入力として、イベント数とイベント内容(superchat送金またはメンバーシップ加入)が与えられる。
出力は読み上げ順にアカウント名を一行ずつ表示する。
入力例:
5
aiueo give 2489 !
kk join membership!
coffee_addiction join membership!
so_cute give 837 !
yoyo give 9284 !
出力例:
yoyo
aiueo
so_cute
coffee_addiction
kk
❌ NG例:
const rl = require('readline').createInterface({input:process.stdin});
const lines = [];
rl.on('line', (input) => lines.push(input));
rl.on('close', () => {
const N = Number(lines[0]);
const superChat = new Map();
const memberShip = [];
lines.slice(1).forEach(line => {
const tokens = line.split(' ');
const name = tokens[0];
const event = tokens[1];
if (event === 'give'){
const money = Number(tokens[2]);
superChat.set(name, superChat.get(name) || 0 + money);
}
else if (event === 'join'){
memberShip.push(name);
}
});
const sorted = Array.from(superChat.keys()).sort((a, b) => {
const A = superChat.get(a);
const B = superChat.get(b);
return A !== B ? B - A : a - b;
});
sorted.forEach(s => console.log(s));
console.log(memberShip.sort().join('\n'));
});
- ❌ superChat.get(name) || 0 + money
- → ✅ (superChat.get(name) || 0) + money
- 同じ金額だった場合の辞書順ソートが正しくできていない
(a と b は文字列なので a – b は NaN になる)
✅ OK例:
const rl = require('readline').createInterface({ input: process.stdin });
const lines = [];
rl.on('line', (input) => lines.push(input));
rl.on('close', () => {
const N = Number(lines[0]); // イベントの数
const superChat = new Map(); // superchat 金額をアカウント名で管理
const memberShip = []; // メンバーシップ加入者のリスト
// 2行目以降のイベントを処理
lines.slice(1).forEach(line => {
const tokens = line.split(' ');
const name = tokens[0];
const event = tokens[1];
if (event === 'give') {
const money = Number(tokens[2]);
// すでに superchat 済みなら加算、なければ 0 から加算
superChat.set(name, (superChat.get(name) || 0) + money);
}
else if (event === 'join') {
memberShip.push(name);
}
});
// superchat 金額が高い順 → 同額は名前を辞書順降順
const sorted = Array.from(superChat.keys()).sort((a, b) => {
const A = superChat.get(a);
const B = superChat.get(b);
return A !== B ? B - A : b.localeCompare(a);
});
// superchat 順に出力
sorted.forEach(s => console.log(s));
// メンバーシップは辞書順昇順に出力
memberShip.sort().forEach(m => console.log(m));
});
-
Mapで superchat 金額を効率管理。(名前がキー、総額が値) - superchat のソートは「金額の降順」→「金額同じなら名前の辞書順降順」
A !== B ? B - A : b.localeCompare(a)
-
memberShip.sort()→ デフォルトで辞書順昇順。
💡localeCompare() メソッド
localeCompare() メソッドは、参照文字列がソート順で指定された文字列の前か後か、または同じかを示す数値を返す。
🔍基本構文
"a".localeCompare("b")
これは "a" と "b" を 辞書順で比較するメソッド。
戻り値は:
・負の数 → 参照文字列(左の “a”)の方が 前。
・0 → 同じ。
・正の数 → 参照文字列(左の “a”)の方が 後。
👉 つまり:
"apple".localeCompare("banana") // 負の数(appleが前)
"banana".localeCompare("apple") // 正の数(bananaが後)
🔍Array.prototype.sort() のコールバックでの順番
array.sort((a, b) => {
return a.localeCompare(b);
});
sort の比較関数は a と b の 2 つの要素を受け取り:
・戻り値が負の数 → a が b より前に来る
・戻り値が 0 → 順序を変えない
・戻り値が正の数 → a が b より後に来る
👉つまり、localeCompare を sort で使うと
array.sort((a, b) => a.localeCompare(b));
→ 辞書順 昇順(A → Z)
例えば:
["banana", "apple"].sort((a, b) => a.localeCompare(b))
// → ["apple", "banana"]
🔍今回の問題の場合
superchat の金額が同じ場合、同じ金額の中で辞書順降順でアカウント名を読むことにしました。
降順にしたいときは順番を逆にすれば OK!
array.sort((a, b) => b.localeCompare(a));
👉 b.localeCompare(a) にすると:
b が a より前 → 正の数 → a が後ろ → 「降順」。
🗒️ 学習メモ・まとめ
-
Mapが便利- キーにアカウント名、値に合計金額を格納。
-
Map.get(name) || 0で初期値を扱う。
-
sortの比較関数の理解が重要- 数値の降順は
b - a - 文字列の辞書順昇順は
a.localeCompare(b) - 文字列の辞書順降順は
b.localeCompare(a)
- 数値の降順は
-
localeCompare()メソッドで辞書順ソートができる- 言語やロケールに対応した正確な文字列比較が可能。
-
a.localeCompare(b)はaがbの前なら負の値、後なら正の値を返す。
- 条件によって複数のソート基準を使い分ける
- 今回は「superchat金額の降順」→「金額同じなら名前の辞書降順」
- 「メンバーシップ加入者は名前の辞書昇順」
- 文字列に対して - 演算子は使えない
-
a - bはNaNになるため、文字列比較は必ずlocaleCompareを使う。
-
- 入力の解析とデータ構造の選択
- 入力を分解し必要な情報を効率よく格納・集計すること。
- 配列・
Mapを状況に応じて使い分ける。
- 出力は問題の指示に忠実に
- 指定された順序・形式で出力すること。