生存報告:0レベル問題を本気で考えるということ

프로필

2025年12月31日

222 0

最近ブログを書いてなかった理由

最近、ブログの更新が止まっていた。
「最近」と言っても、気づいたら前回の投稿からほぼ半年が経っていた。

何をしていたのかと言われると、別に大層な理由があるわけではない。
- ドメインの有効期限が近づいていたので、新しいドメインに移行した
- 終わりの見えないブログのリファクタリングを進めている(というより、ほぼ作り直し)
- 事業をやっている友人のWebアプリを作りつつ、自分用の自動化ツールをいくつか書いた
- そして、自分の一番の弱点だと思っているコーディングテストとCSの勉強をしている

要するに、目に見える成果物よりも、思考のほうに時間を使っていたという話だ。


で、今回話したいコードは

これ。

def solution(num_list):
    return [even := sum(i % 2 == 0 for i in num_list), len(num_list) - even]

num_listの中に含まれる偶数と奇数の個数を配列で返す関数だ。

プログラマーズの0レベル問題
「偶数と奇数の個数」というやつ。

条件は以下の通り。
- リストの長さ:1 ≤ n ≤ 100
- 要素の範囲:0 ≤ 値 ≤ 1000

正直、難しい問題ではない。
一番オーソドックスな解き方はこうなる。

even, odd = 0, 0
for i in num_list:
    if i % 2 == 0:
        even += 1
    else:
        odd += 1
return [even, odd]

このコードが悪いと言うつもりはない。
ただし、コーディングテストという文脈に限っては、これは自分の選択肢ではない。

一応先に言っておくと、
実務コードとコーディングテスト用のコードは、はっきり分けて考えている。

実務では可読性、協業性、保守性を優先する。
一方でコーディングテストでは、できる限り思考を圧縮する方向に振り切る。

つまりこの記事は、
実務コードを誇るためのものではなく、考え方を残すための記録だ。


思考過程 #1

sum(1 for i in num_list if i % 2 == 0)

偶数なら1を返して、それをsumで足す。
これで偶数の個数は求められる。

じゃあ奇数も同じようにやれば?

[sum(1 for i in num_list if i % 2 == 0), sum(1 for i in num_list if i % 2 == 1)]

これで答えは出る。
ただし、num_listを2回走査して、ほぼ同じ条件を2回評価している。

とはいえ、この問題での最大要素数は100なので、性能的な問題は一切ない。

それでも、自分の感覚では美しくない。


思考過程 #2 : ウォルラス演算子

ウォルラス演算子とは何か。
Python 3.8で追加された構文だ。

Walrus Operator (:=) 式の中で値を計算しつつ代入できる

この問題でウォルラスがハマる理由はひとつ。

偶数か奇数、どちらか一方の個数が分かれば、もう片方は計算する必要がない。
もちろん、ウォルラスを使わなくても書ける。

even = sum(1 for i in num_list if i % 2 == 0)
return [even, len(num_list) - even]

これで無駄な計算は消えるし、多くの人はここで止まると思う。
ただ、ここでウォルラスを使うとこうなる。

[even := sum(1 for i in num_list if i % 2 == 0), len(num_list) - even]

可読性も協業性も保守性も正直どうでもいい。
コーディングテストという場で、思考をどこまで圧縮できるかを楽しみたい自分には、ちょうどいいコードだ。


思考過程 #3

まだ改善の余地はある。
Pythonでは、Trueは1、Falseは0として扱われる。
つまり、1 for iみたいな分岐を書かなくても、boolean演算をそのまま使える。

sum(i % 2 == 0 for i in num_list)

もちろん、この違いが体感できるほど効くわけではない。
要素数100の問題では、正直ほぼ意味はない。

それでも、条件分岐を減らし、Pythonの型仕様に素直に乗るという意味で、ここまで詰めた。
こうして最終的に残ったコードがこれだ。

def solution(num_list):
    return [even := sum(i % 2 == 0 for i in num_list), len(num_list) - even]

0レベル問題をここまでやる人は、たぶん多くない。
でも、冗談で書いたコードではない。


本当に効率的なのか?

可読性や協業性、保守性はいったん置いておく。
時間計算量と空間計算量の観点で見る。

時間計算量
- リストを1回走査 → O(n)
- それ以外はすべて O(1)

全要素を最低1回は見る必要がある以上、これ以上はどうやっても下がらない。

空間計算量
- ジェネレータ使用
- 不要な配列生成なし
- 保持する変数はevenひとつだけ

空間計算量はO(1)。

実務ならどうするかというと、

def solution(num_list):
    even = sum(i % 2 == 0 for i in num_list)
    return [even, len(num_list) - even]

あるいは素直にfor文を書く。
協業コードでのウォルラスは、ほとんどの場合ノイズになる。


結論

このコードを書けと言いたいわけではない。

簡単な問題でも、いろいろな解き方を試すことで
「何が使えるか」「そして何を使わないか」を意識的に選べるようになる。

知らなくて使わないのと、
知った上で使わないのは、まったく別の話だ。

この記事を書いた理由は、
今の自分のコード美学が、1年後や5年後も同じだとは思っていないからだ。

新しいことを学べば、
将来の自分がこのコードを見て「何やってんだ」と言うかもしれない。

それでも、ここに至るまでの思考は残しておきたい。
2025年の自分は、こう考えていたという記録として。

ちなみに、% 2 == 0すら無駄だと思う人向けに、
ビット演算を使ったやり方も一応ある。

return [len(num_list) - (odd := sum(i & 1 for i in num_list)), odd]

正直、自分の基準でもこれはやりすぎだ。
直感的じゃないし、人間向けのロジックでもない。

Pythonのインタプリタオーバーヘッドを考えれば、
前のコードと実質的な差もない。
ここが、自分の引くラインだ。

もし、ラムダ以外に別の解き方をした人がいたら、ぜひ見てみたい。
問題そのものより、考え方のほうが面白いコードには、学ぶ価値があると思っている。

#숏코딩 #힙스터 #왈러스 #코딩테스트

コメント 0件

コメントを投稿するにはログインが必要です