2026.08.02(日) / 15:13

最近出会った具体的なプログラミングの知識

ID
40352
Published
2026-08-02 15:13
Modified
2026-08-02 15:13
Author
khiro
Categories
デフォルト

ここ数日、paizaの問題に取り組む中で、Pythonのさまざまな概念に改めて出会った。

どれもPythonの高度な機能というより、基本的な知識に分類されるものだと思う。しかし、知識として名前を知っていることと、問題を解くための道具として使えることには大きな差がある。

実際に問題の中で必要になったことで、それぞれの機能が何のために存在するのかが、少しずつ見えるようになってきた。

zipで、二つの文字列を同じ位置ごとに比較する

特に印象に残ったのが、zipを使った文字列の比較だった。

二つの文字列について、同じ位置にある文字が何個異なっているかを調べる問題では、次のように書くことができる。

wrong_num = 0

for correct_char, answer_char in zip(correct, answer):
    if correct_char != answer_char:
        wrong_num += 1

zipを使うと、二つの文字列やリストから、同じ位置にある要素を一組ずつ取り出せる。

たとえば、"cat""cut"を比較する場合は、cとcaとutとtという組み合わせで順番に確認できる。

説明を受ければ仕組みは理解できる。しかし、問題文を読んだ瞬間に「同じ位置を比較するのだからzipを使おう」と発想するのは簡単ではなかった。

この「問題の特徴と機能を結びつける力」こそ、今後身につけていく必要がある。

スライスで、必要な範囲だけを取り出す

商品の価格を並べ替え、一定数の商品を無料にする問題では、リストのスライスが重要だった。

最初はpopを使って一つずつ要素を削除しようとした。しかし、要素を削除するとリストのインデックスがずれるため、意図した商品とは別の商品を消してしまう可能性がある。

一方で、スライスを使えば、並べ替えた後の必要な範囲をそのまま取得できる。

prices.sort()
paid_prices = prices[free_num:]
print(sum(paid_prices))

prices[free_num:]は、先頭からfree_num個を除き、それ以降の要素を取り出すという意味になる。

一つずつ削除するのではなく、「必要な部分だけを残す」と考える。この視点の切り替えによって、コードは短くなり、インデックスのずれによるミスも防ぎやすくなる。

popとremoveで、使った要素を消す

二つの数を組み合わせて、合計が一定の値になるペアを数える問題では、popremoveを使った。

count = 0

while grades:
    x = grades.pop(0)
    pair = 11 - x

    if pair in grades:
        count += 1
        grades.remove(pair)

popは、リストから要素を取り出すと同時に削除する。removeは、指定した値をリストから削除する。

一度ペアとして使用した数字は、次のペアには使用できない。そのルールを、「使った要素をリストから消す」という処理で表現している。

ここでは、単に合計が11になる組み合わせを探すだけではない。「一度使った数字は消滅する」という問題の状態変化を、リストの状態変化として表す必要があった。

問題文に書かれた出来事を、プログラム上の操作に翻訳することの大切さを実感した。

for-elseで、最後まで見つからなかった場合を表す

複数人の休暇希望日を確認し、全員が休める日が存在するかを調べる問題では、for-elseを使った。

for value in day_list[0]:
    count = 0

    for row in day_list:
        if value in row:
            count += 1

    if count == len(day_list):
        print("OK")
        break
else:
    print("NG")

このelseは、ifではなくforに対応している。

条件を満たす日が見つかれば、breakによってループを終了する。一度もbreakされず、候補を最後まで調べ終わった場合にだけ、elseの処理が実行される。

つまり、「見つかった場合」と「最後まで探しても見つからなかった場合」を、自然に書き分けるための構文だ。

最初はforelseが付くこと自体に違和感があった。しかし、「breakされなかった場合」と捉えると役割が分かりやすくなった。

joinで、リストを一行の文字列に変換する

クロスワードパズルや、複数の値を空白区切りで出力する問題では、joinを使用した。

print(" ".join(map(str, numbers)))

joinは、複数の文字列を指定した区切り文字で連結する。

この例では、数値のリストをmap(str, numbers)によって文字列へ変換し、それらを半角スペースでつないでいる。

また、クロスワードの一行が文字のリストとして保存されている場合は、次のように空文字で連結する。

print("".join(row))

printは最後に改行を加えるため、各行を一回ずつ出力すれば、二次元の盤面として表示できる。

リストの中身を一つずつ出力するのではなく、まず一つの文字列に組み立ててから出力する。この考え方も、今後頻繁に使うことになるだろう。

combinationsで、重複のない組み合わせを作る

複数の商品やキャンディーセットの中から、決められた個数を選ぶ問題では、itertools.combinationsに触れた。

from itertools import combinations

pairs = combinations(candy_num_by_set, purchase_num)

for pair in pairs:
    total = sum(pair)

    if total % customer_num == 0:
        count += 1

combinationsを使うと、順番を区別しない組み合わせを列挙できる。

たとえば、1と2を選ぶことと、2と1を選ぶことを同じ組み合わせとして扱える。

自力ですべての組み合わせを作ろうとすると、ループの数が増えたり、同じ組み合わせを重複して数えたりしやすい。標準ライブラリを使うことで、組み合わせを作る処理を安全かつ明確に表現できる。

一方で、単に使い方を覚えるだけでは足りない。

「この問題は並び順が重要なのか」「同じ要素を複数回選べるのか」「選ぶ個数はいくつなのか」を整理し、combinationsが適切なのかを判断する必要がある。

reversedで、後ろから処理する

連分数を計算する問題では、入力された数値を後ろから処理する必要があった。

numerator = v_list[-1]
denominator = 1

for value in reversed(v_list[:-1]):
    new_numerator = value * numerator + denominator
    new_denominator = numerator

    numerator = new_numerator
    denominator = new_denominator

通常、リストは先頭から処理することが多い。しかし、計算の構造によっては、最後の値を出発点にして、前の値へ戻っていく方が自然な場合がある。

reversedは、リストの要素を逆順に取り出すための機能だ。

この問題では、「なぜ最後から始めるのか」「現在の分子と分母が、次の計算でどのように使われるのか」を理解することが核心だった。

単に式を覚えるのではなく、具体的な小さな入力を使い、分数が一段ずつ組み上がっていく様子を追うことで、ようやく処理の意味が見えてきた。

二次元リストでは、行と列を意識する

地図、クロスワード、ビンゴなどの問題では、二次元リストの扱いが繰り返し登場した。

board = [
    [".", ".", "."],
    [".", ".", "."],
    [".", ".", "."]
]

二次元リストでは、board[row][column]のように、最初のインデックスで行を選び、次のインデックスで列を選ぶ。

しかし、問題文では座標が1から始まり、Pythonのインデックスは0から始まることが多い。そのため、入力された位置から1を引く必要がある。

row = input_row - 1
column = input_column - 1

さらに、縦方向へ進む場合は行番号を増やし、横方向へ進む場合は列番号を増やす。

この行と列の関係を頭の中だけで処理しようとすると混乱しやすい。小さな盤面を紙に書き、インデックスを併記することが有効だった。

enumerateで、値とインデックスを同時に取得する

商品や命令の一覧を処理するときには、値だけではなく、「何番目の要素なのか」も必要になることがある。

そのような場合に使えるのがenumerateだ。

for item_index, item in enumerate(items):
    print(item_index, item)

enumerateを使うと、インデックスと値を同時に取り出せる。

range(len(items))を使ってインデックスを作ることもできるが、enumerateの方が、「リストの要素を順番に処理しながら、その位置も使う」という意図が伝わりやすい。

ただし、インデックスが本当に必要なのかを考えることも重要だ。値だけで処理できる場合は、無理にインデックスを使わない方がコードは単純になる。

map objectと、必要なタイミングでの型変換

mapについても、改めて理解する機会があった。

numbers = map(int, input().split())

mapは、複数の要素に対して同じ関数を適用する。

この例では、入力された文字列の各要素にintを適用している。

ただし、mapの結果はリストそのものではなく、必要になったときに要素を順番に取り出すmap objectだ。

そのため、後から何度も使ったり、インデックスで参照したりしたい場合は、listtupleへ変換する。

numbers = list(map(int, input().split()))

一方で、変数へそのまま分解するだけなら、リストへ変換する必要はない。

height, width = map(int, input().split())

機械的にすべてlistへ変換するのではなく、その後どのように使うかによって適切な形を選ぶ必要がある。

小さな知識を、問題解決の道具に変えていく

zip、スライス、pop、remove、for-else、join、combinations、reversed、二次元リスト、enumerate、map。

一つひとつを見れば、どれもPythonの基本的な知識かもしれない。

しかし、実際の問題では、それらを単独で使うのではなく、条件分岐やループと組み合わせながら、一つの処理を完成させる必要がある。

重要なのは、機能の名前を暗記することではない。

「二つの列を同時に見たいならzip」「必要な範囲だけ取りたいならスライス」「使った要素を消したいならpopやremove」「見つからなかった場合を表したいならfor-else」というように、問題の状況と道具を結びつけることだ。

今はまだ、解説を聞いて初めて分かることも多い。

それでも、実際に手を動かし、間違え、修正し、自分の言葉で処理を説明することで、知識は少しずつ自分の中へ定着していく。

この小さな理解の積み重ねが、やがて問題文を読んだ瞬間に、使うべき道具が自然に浮かぶ力へ変わっていくはずだ。