こんにちは、いーさんです。
今回は、paizaで同じ間違いを2回も繰り返してしまった経験から学んだ「中間配列アンチパターン」について書きます。
プログラミング初心者が陥りやすい罠で、私もまさにハマってしまいました。特にPython経験者がJavaScriptを学ぶときに起こりやすいパターンなので、同じような背景を持つ方の参考になれば嬉しいです。
何度も繰り返した同じ間違い
paizaの「文字列の最初の出現位置検索」という問題を解いていたとき、テストケースの一部は通るのに、大量データになるとエラーが出る現象に遭遇しました。
そして、Python の正解コードと自分のJavaScriptコードを見比べて、やっと問題に気づきました。
それが「不要な中間配列」でした。
間違ったコード
まず、私が書いてしまった問題のあるコードがこちらです。
reader.on('close', () => {
const [N, Q] = lines[0].split(" ").map(Number);
let s = {};
let t = []; // ← 問題:不要な中間配列
// S配列をオブジェクトに格納
for(let i = 0; i < N; i++){
let S = lines[i+1];
if (!(S in s)){
s[S] = i + 1;
}
}
// ❌ 一旦配列に格納
for(let j = 0; j < Q; j++){
t.push(lines[N + 1 + j]);
}
// ❌ 別のループで処理
for(let x of t){
if(x in s){
console.log(s[x]);
} else {
console.log(-1);
}
}
});
一見問題なさそうですが、このコードには重大な無駄があります。
何が問題だったのか
問題点1:不要な中間配列 t
let t = []; // これ、本当に必要?
for(let j = 0; j < Q; j++){
t.push(lines[N + 1 + j]);
}
lines 配列にすでにデータが全部入っているのに、わざわざ別の配列 t に移し替えています。
これが完全に無駄でした。
問題点2:ループが2回に分かれている
// 1回目のループ:配列に格納
for(let j = 0; j < Q; j++){
t.push(lines[N + 1 + j]);
}
// 2回目のループ:処理
for(let x of t){
// 処理
}
ループを2回回す必要がありません。1回のループで処理できます。
問題点3:余分なメモリ使用
中間配列 t を作ることで:
- 時間計算量: O(Q) + O(Q) = O(Q)(変わらない)
- 空間計算量: O(Q)(余分なメモリ使用)
小さいデータなら問題になりませんが、大量データになるとメモリを圧迫します。
正しいコード
修正後のコードがこちらです。
reader.on('close', () => {
const [N, Q] = lines[0].split(" ").map(Number);
const s = {};
// S配列をオブジェクトに格納
for(let i = 0; i < N; i++){
const S = lines[i + 1];
if (!(S in s)){
s[S] = i + 1;
}
}
// ✅ クエリを直接処理
for(let j = 0; j < Q; j++){
const t = lines[N + 1 + j];
if(t in s){
console.log(s[t]);
} else {
console.log(-1);
}
}
});
変更点:
- 中間配列
tを削除 - ループを1つに統合
linesから直接データを取り出して処理
シンプルで分かりやすくなりました。
なぜこの間違いをしてしまったのか
振り返ってみると、3つの心理的要因がありました。
1. 「一旦まとめて取得したい」衝動
データを処理する前に、「まずデータを全部集めよう」と考えてしまいました。
これは一見論理的に思えますが、実は不要なステップです。
2. Pythonの input() との違いに引きずられた
Pythonでは input() で1行ずつ読み込みますが、JavaScriptの paiza環境では lines 配列に全データが最初から入っています。
この違いを理解していなかったため、「データを取り出して別の場所に保存する」という発想になってしまいました。
3. 処理を段階的に分けたい気持ち
「データ取得」と「データ処理」を分けた方が分かりやすい気がしていました。
しかし実際は、分ける必要がない場面で分けることで、かえってコードが複雑になっていました。
Python vs JavaScript の処理の違い
Python の正解コードと比較してみます。
Python(参考)
N, Q = map(int, input().split())
S = {}
for i in range(N):
s = input()
if s not in S:
S[s] = i + 1
# ✅ 直接処理
for _ in range(Q):
t = input()
if t in S:
print(S[t])
else:
print(-1)
Pythonでは input() でその都度読み込んで、すぐに処理しています。
JavaScript(間違いパターン)
// ❌ 中間配列を作ってしまう
for(let j = 0; j < Q; j++){
t.push(lines[N + 1 + j]); // 一旦格納
}
for(let x of t){ // 別ループで処理
console.log(...);
}
JavaScript(正解パターン)
// ✅ Pythonと同じように直接処理
for(let j = 0; j < Q; j++){
const t = lines[N + 1 + j];
console.log(...);
}
学び: JavaScriptでも、Pythonと同じように「取得→即処理」のパターンで書くべきでした。
中間配列アンチパターンとは
この失敗から、「中間配列アンチパターン」という概念を学びました。
❌ アンチパターン(避けるべき)
// パターン1: 不要な中間配列
const temp = [];
for(let i = 0; i < N; i++){
temp.push(lines[i]);
}
for(let x of temp){
process(x);
}
// パターン2: 配列に格納してから検索
const list = [];
for(let i = 0; i < N; i++){
list.push(lines[i]);
}
for(let item of list){
console.log(search(item));
}
✅ 推奨パターン
// パターン1: 直接処理
for(let i = 0; i < N; i++){
const x = lines[i];
process(x);
}
// パターン2: その場で検索
for(let i = 0; i < N; i++){
const item = lines[i];
console.log(search(item));
}
いつ中間配列が必要なのか
ただし、中間配列が本当に必要な場面もあります。
中間配列が必要なケース
1. ソートが必要な場合
const data = [];
for(let i = 0; i < N; i++){
data.push(lines[i]);
}
data.sort((a, b) => a - b); // ソート必須
2. フィルタリングが必要な場合
const filtered = [];
for(let i = 0; i < N; i++){
if(条件){
filtered.push(lines[i]);
}
}
3. データを変換してから使う場合
const transformed = lines.map(Number); // 文字列→数値変換
中間配列が不要なケース
1. 単純な順次処理
// ❌ 不要
const temp = [];
for(let i = 0; i < N; i++){
temp.push(lines[i]);
}
for(let x of temp){
console.log(x);
}
// ✅ 直接処理
for(let i = 0; i < N; i++){
console.log(lines[i]);
}
2. 検索・照合処理
// ❌ 不要
const queries = [];
for(let i = 0; i < Q; i++){
queries.push(lines[i]);
}
for(let q of queries){
console.log(map[q]);
}
// ✅ 直接処理
for(let i = 0; i < Q; i++){
const q = lines[i];
console.log(map[q]);
}
チェックリスト:中間配列を作る前に確認
問題を解く前に、以下を自問自答するようにしました。
- [ ] 中間配列を作ろうとしてない?
- [ ] ループを2回に分けてない?
- [ ] 「一旦格納してから」と考えてない?
- [ ] Pythonならどう書く?それに近づけているか?
コードを書いた後:
- [ ] 以下のパターンがないかチェック
let array = [];
for(...){
array.push(...);
}
for(... of array){ // ← これが見つかったら要注意
...
}
学んだこと
1. 「一旦配列に格納」は要注意フレーズ
「一旦」「とりあえず」という言葉が出てきたら、本当に必要か立ち止まって考える。
2. 同じ間違いを繰り返すことでパターン認識
1回目:指摘されて直した
2回目:また同じ間違い
→ これでやっとパターンとして認識できた
3. Python経験が逆に足を引っ張ることもある
異なる言語の特性を理解せず、思考パターンをそのまま移植すると失敗する。
4. コードレビューの重要性
Python解答と比較することで、初めて違いに気づけた。 他の人のコードを見ることは学びになる。
まとめ
今回学んだポイント:
✅ 中間配列アンチパターンを認識
✅ 「一旦配列に格納」は要注意
✅ Python と JavaScript の違いを理解
✅ 本当に必要な配列かを考える癖をつける
同じ間違いを2回繰り返したのは悔しいですが、これでパターンとして完全に理解できました。
次に同じ状況になったら、絶対に中間配列は作りません!
プログラミング学習では、こうした失敗から学ぶことが一番身につくと実感しています。
今回のアンチパターン:
// ❌ 避ける
let temp = [];
for(...){ temp.push(...); }
for(...){ process(temp); }
// ✅ 推奨
for(...){
const x = data[i];
process(x);
}
次回もpaizaでの学びをアウトプットしていきます!
関連記事:
この記事は paizaラーニング の学習記録です。


コメント