🔙
🔝

【paiza問題集 解説】
Cランク・スキルチェック過去問題セット

検索履歴

検索履歴

FINAL問題 検索履歴

検索履歴

    N = int(input())
    
    search_history = []
    for _ in range(N):
        W = input()
        if W in search_history:
            search_history.remove(W)
            search_history.insert(0, W)
        else:
            search_history.insert(0, W)
    
    print(*search_history, sep='\n')
    

    検索履歴への追加・削除をするだけの問題です。
    初めは検索履歴は空の状態です。ここから入力ワードを追加・削除していきます。

    「追加」とあるのですが、先頭に追加するので .insert() メソッドを使います。
    削除は「検索ワード」を削除、言い方を変えると取り除くので .remove() メソッドを使います。
    insert は「挿入そうにゅう」、removeは「除去じょきょ」です。

    .insert() メソッドの使い方は「2章 リスト型データを使いこなす機能一覧」の こちら をご覧ください。
    .remove() メソッドの使い方は「2章 リスト型データを使いこなす機能一覧」の こちら をご覧ください。

    それぞれのメソッドの使い方がわかれば、あとはその通りに使うだけですが、1点だけ注意。
    .remove() は除去対象の要素を先頭から探索していきます。ですので、 .insert(0, W) を実行してから .remove(W) を実行してしまうと、.insert(0, W) したばかりのものがそのまま .remove(W) で除去されてしまいます。それじゃ意味なし!😹 .remove() から実行しましょう。

単語のカウント

単語のカウント

STEP: 1 単語のカウント(力試し編)

単語のカウント(力試し編)

    lst = input().split()
    
    words_cnt = {}
    for word in lst:
        if word not in words_cnt:
            words_cnt[word] = 1
        else:
            words_cnt[word] += 1
    
    for word, cnt in words_cnt.items():
        print(word, cnt)
    

    100点が取れなかった場合でも、そのプログラムを修正せずにそのまま次のステップに進みましょう。

STEP: 3 「単語のカウント」を解くために:part2

「単語のカウント」を解くために:part2

  • 例1

  • lst = input().split()
    target_word = 'red'
    if target_word in lst:
        print('Yes')
    else:
        print('No')
    
  • 例2

  • lst = input().split()
    target_word = 'red'
    print('Yes' if target_word in lst else 'No')
    

STEP: 4 「単語のカウント」を解くために:part3

「単語のカウント」を解くために:part3

    lst = input().split()
    
    already_been = set()
    for word in lst:
        if word in already_been:
            print('already_been')
        else:
            print(word)
            already_been.add(word)
    

    すでに登場したワードを格納しておく already_been は、リストでもセットでも構いません。どちらでもよいならば、セットを優先して使いましょう。

STEP: 5 「単語のカウント」を解くために:part4

「単語のカウント」を解くために:part4

    lst = input().split()
    
    already_been = set()
    for word in lst:
        if word not in already_been:
            print(word)
            already_been.add(word)
    

STEP: 6 「単語のカウント」を解くために:part5

「単語のカウント」を解くために:part5

    lst = input().split()
    
    already_been = {}
    for word in lst:
        if word not in already_been.keys():
            print(word)
            already_been[word] = 1
    
    for _, value in already_been.items():
        print(value)
    

    出現回数のカウントはリストでもできますが、ここはより簡単にデータを扱える辞書を使っていきます。
    まずは初めて登場したワードは、そのワードを画面に出力してから辞書に値を 1 にして登録します。既に登場したワードの場合は何もしません。

    最後に辞書の .items()メソッドを使って各ワードの値のみを画面に出力していけば完了です。

    また、値が 1 に固定されているので、この様にも書けます。

    lst = input().split()
    
    already_been = {}
    for word in lst:
        print(word)
        already_been[word] = 1
    
    for _, value in already_been.items():
        print(value)
    

    キーが辞書にすでにある場合は、要素まるごと新しい要素に書き換えられます。
    本題を解くには最初の書き方が求められますので、こちらのプログラムは参考までに。🐱

    辞書のメソッドについては「2章 辞書を使いこなす機能一覧」で学習できます。

STEP: 7 「単語のカウント」を解くために:part6

「単語のカウント」を解くために:part6

  • 例1

  • lst = input().split()
    word = input()
    
    print(lst.index(word))
    
  • 例2

  • words_cnt = {word: None for word in input().split()}
    word = input()
    
    print(list(words_cnt.keys()).index(word))
    

    例1でじゅうぶんなのですが、辞書を使っているので例2も書いてみました。
    値は扱っていないので None にしてあります。

    words_cnt.keys() でワードのみを抽出し、list() 関数でリスト型に変換します。そうすると .index() メソッドが使えるようになりますので、該当するワードの要素番号を画面に出力して完了です。

    辞書の並び順は保証されているので、リストに変換してもその並びは保持されます。アンシンアンコールワット

STEP: 8 「単語のカウント」を解くために:part7

「単語のカウント」を解くために:part7

    lst = input().split()
    
    word_cnt = {}
    for word in lst:
        if word not in word_cnt:
            word_cnt[word] = 1
            print(word)
        else:
            word_cnt[word] += 1
    
    for _, cnt in word_cnt.items():
        print(cnt)
    

    STEP 6 のプログラムにカウントを加えたものです。(else文)
    代入文では上書きされましたが、代入演算子(+=)にするとカウントアップします。

FINAL問題 単語のカウント

単語のカウント

  • 例1

  • lst = input().split()
    
    words_cnt = {}
    for word in lst:
        if word not in words_cnt:
            words_cnt[word] = 1
        else:
            words_cnt[word] += 1
    
    for word, cnt in words_cnt.items():
        print(word, cnt)
    
  • 例2

  • lst = input().split()
    
    words_cnt = {}
    for word in lst:
        words_cnt[word] = words_cnt.get(word, 0) + 1
    
    for word, cnt in words_cnt.items():
        print(word, cnt)
    
  • 例3

  • from collections import Counter
    
    lst = input().split()
    
    words_cnt = Counter(lst)
    
    for word, cnt in words_cnt.items():
        print(word, cnt)
    

    例1は今までの流れの完成形です。例2は、文をひとまとめに書いた方法です。

    例3は、予め Python3 に標準で付いているモジュールと言われる外部プログラム(懐かしいなこの言い方😊)を利用したやり方です。
    一行目の召喚魔法で collections の中に入っている Counter という機能を使役できるようにしています。そして Counter(lst) と書くだけで、lst の中の出現したワードの出現回数をまとめて辞書化してくれます。つまり、この一行 words_cnt = Counter(lst) で、例1・例2の words_cnt = {} から文すべてのことを全部やってくれるのです。

    Counter()は便利なのですが、例1・例2の書き方も結構使いますので全部使い慣れましょう。

宝くじ

宝くじ

FINAL問題 宝くじ

宝くじ

    atari = input()
    n = int(input())
    
    for i in range(n):
        ticket = input()
        
        if ticket == atari:
            print('first')
        elif int(ticket) + 1 == int(atari) or int(ticket) - 1 == int(atari):
            print('adjacent')
        elif ticket[2:] == atari[2:]:
            print('second')
        elif ticket[3:] == atari[3:]:
            print('third')
        else:
            print('blank')
    

    問題に記述されていないのですが、当選の優先順位は1等からで、複数当選の場合は上位1つだけが当選ということで良いと思います。

    宝くじ券の番号は文字列で受け取って処理していきます。

    まず1等はそのまま当選番号と一致すれば1等です。

    1等前後賞は、宝くじ券と当選番号の両方を整数型に変換してから 1 を足したり引いたりして照合します。このとき 100000 と 199999 は前後賞は1つしかありませんが、
    100000 - 1 = 99999 でも
    199999 + 1 = 200000 でも
    範囲外の存在しない番号なので、『範囲外の値は比較式にかけても当選番号と一致することはない』ということになりますので特別な処理は不要です。

    2等は、要素 2 以降、つまり下4桁を照合します。
    3等も、要素 3 以降、つまり下3桁を照合します。

    そして上記のいずれにも合致しない場合ははずれとなります。

    Fizz Buzz問題と同じ様に、評価順に注意してください。逆順にすると複数当選の時、1等が当たらなくなります!困る!!

野球の審判

野球の審判

STEP: 1 野球の審判(力試し編)

野球の審判(力試し編)

    N = int(input())
    
    strike = 'strike'
    ball = 'ball'
    ball_count = {strike: 0, ball: 0}
    for _ in range(N):
        s = input()
        ball_count[s] += 1
        
        if ball_count[strike] == 3:
            print('out!')
        elif ball_count[ball] == 4:
            print('fourball!')
        else:
            print(s + '!')
    

    100点が取れなかった場合でも、そのプログラムを修正せずにそのまま次のステップに進みましょう。

STEP: 5 「野球の審判」を解くために:part4

「野球の審判」を解くために:part4

    N = int(input())
    
    for _ in range(N):
        s = input()
        if s == 'strike':
            print(s + '!')
        else:
            print(s)
    

    文字列を繋げる時は「文字列 + 文字列」とします。

STEP: 6 「野球の審判」を解くために:part5

「野球の審判」を解くために:part5

    N = int(input())
    
    strike = 'strike'
    ball_count = {strike: 0}
    for _ in range(N):
        s = input()
        if s == 'strike':
            print(s + '!')
            ball_count[s] += 1
            print(ball_count[s])
        else:
            print(s)
    

    カウントはストライクのみなので、予めストライクだけ辞書に初期値を書いておきます。整数型の変数1つでもカウントできますが、今後の為にもここでは辞書を使います。

    『strike! と出力した次の行に』とありますので、strike! を画面に出力した後、ストライクのカウントに 1 を足してからその値を画面に出力します。

STEP: 7 「野球の審判」を解くために:part6

「野球の審判」を解くために:part6

    N = int(input())
    
    strike = 'strike'
    ball_count = {strike: 0}
    for _ in range(N):
        s = input()
        ball_count[s] += 1
        
        if ball_count[strike] == 3:
            print('out!')
        elif s == 'strike':
            print(s + '!')
        else:
            print(s)
    

    1つ前の問題のプログラムの書き順をそのまま使うと、3つ目のストライクでは strike! を画面に出力してから out! と表示されてしまいます。その為、ボールカウントに 1 を足す文を条件文の前に書き、それから条件文で判定すると3つ目のストライクがアウトに置き替わるようになります。

    条件文の書き順に注意してください。アウトの判定がストライクよりも先です。

FINAL問題 野球の審判

野球の審判

    N = int(input())
    
    strike = 'strike'
    ball = 'ball'
    ball_count = {strike: 0, ball: 0}
    for _ in range(N):
        s = input()
        ball_count[s] += 1
        
        if ball_count[strike] == 3:
            print('out!')
        elif ball_count[ball] == 4:
            print('fourball!')
        else:
            print(s + '!')
    

    今度はストライクとボールのコールのしかたが同じになっています。代わりにフォアボールのコールの為のカウントと判定を追加します。

    条件に「投球はこの打者の番がちょうど終了するまで続きます。すなわち、最終的にこの打者にはアウトかフォアボールのいずれかが必ず与えられ、それ以降の投球はおこなわれません。」とありますので、アウトやフォアボールのコールの後には break は書かなくても大丈夫です。

みかんの仕分け

みかんの仕分け

FINAL問題 みかんの仕分け

みかんの仕分け

    N, M = map(int, input().split())
    
    for _ in range(M):
        w = int(input())
        num = w / N
        num_int = int(num)
    
        is_heavy = (num - num_int >= 0.5)
        g = N * num_int + is_heavy * N
        if g == 0:
            g = N
    
        print(g)
    

    例として w = 24, N = 10 の時で解説します。

    重いほうの箱と軽いほうの箱に仕分けます。
    まず入力 w を N で割ります。num = w / N
    そしてこの num の整数部分の値を取ります。

    num = 24 / 10 = 2.4
    num_int = int(2.4) = 2 ← num の整数部分

    num - num_int = 2.4 - 2 = 0.4 ← num の小数部分

    is_heavy は、num の小数部分の値が 0.5 以上の時は 1(重いほうの箱に入れる) となり、0.5 未満の時は 0(軽いほうの箱に入れる) となります。
    ※ この 01 の意味は「2章 bool型 True と False」をご覧ください。

    g = 10 * 2 + is_heavy * N

    is_heavy が 1 の時
    g = 10 * 2 + 1 * 10 = 30
    is_heavy が 0 の時
    g = 10 * 2 + 0 * 10 = 20

    {20, 21, 22, 23, 24}, {25, 26, 27, 28, 29, 30}
    結果、20gの箱に入る。

    こうしてみかんを仕分けして入れた箱に表示されている重さを画面に出力していき、ループを抜ければ完了となります。

    もし、g が 0 になった場合は、一番軽い N の箱に入れます。

    N が 10 でなくてもいくつであっても、w / N の小数部分を使えば四捨五入の要領で仕分けできますし、ループもしなくてイメージしやすく、ラクかなと。😅

    ステップは必要と感じなかったので、FINAL問題以外はすっ飛ばしました。ご容赦を。

    paiza の解答コード例を見て「continueってなんぞや?」と思ったら「3章 補足」で知ることができます。

  • おまけ

  • 値を変えて検証します。
    w = 18, N = 7 の時

    num = 18 / 7 = 2.5(71428…)
    num_int = int(2.5) = 2 ← num の整数部分

    num - num_int = 2.5 - 2 = 0.5 ← num の小数部分

    is_heavy = (2.5 - 2 >= 0.5) = True = 1
    g = N * num_int + is_heavy * N

    g = 7 * 2 + is_heavy * N

    is_heavy が 1 の時
    g = 7 * 2 + 1 * 7 = 21
    is_heavy が 0 の時
    g = 7 * 2 + 0 * 7 = 14

    {14, 15, 16, 17}, {18, 19, 20, 21}
    結果、21gの箱に入る。ちなみに w = 17 は、
    num = w / N = 2.4(28571…) だから 14gの箱に入る。

    w = 3, N = 7 の時

    num = 3 / 7 = 0.4(28571…)
    num_int = int(0.4) = 0 ← num の整数部分

    num - num_int = 0.4 - 0 = 0.4 ← num の小数部分

    is_heavy = (0.4 - 0 >= 0.5) = False = 0
    g = N * num_int + is_heavy * N

    g = 7 * 0 + is_heavy * N

    is_heavy が 1 の時
    g = 7 * 0 + 1 * 7 = 7
    is_heavy が 0 の時
    g = 7 * 0 + 0 * 7 = 0

    {0, 1, 2, 3}, {4, 5, 6, 7}
    0g の箱は無いので if g == 0:
    最も軽い箱 7g の箱に入れる。g = N

【殿堂入りキャンペーン】残り物の量

残り物の量

FINAL問題 【殿堂入りキャンペーン】残り物の量

【殿堂入りキャンペーン】残り物の量

    m, p, q = map(int, input().split())
    kg = m
    
    kg *= (100 - p) / 100
    kg *= (100 - q) / 100
    
    print(kg)
    

    これはただの算数の問題ですね。😅

    p %を販売したということは、100%から p %を引くと売れ残り分が、 で算出されます。これを最初に仕入れた量に掛け算すると、これが売れ残り分の量となります。

    m = 1(kg), p = 80(%を販売) の時

    (100 - p) / 100

    (100 - 80) / 100

    20 / 100 = 0.2

    1(kg) * 0.2 = 0.2(kg)

    q も全く同じ計算です。生鮮食品が総菜に変わっただけです。

    q = 40(%を販売) の時

    (100 - q) / 100

    (100 - 40) / 100

    60 / 100 = 0.6

    0.2(kg) * 0.6 = 0.12(kg)

    そしてその結果を画面に出力すれば完了です。

    期待する出力に「誤差が0.0001 未満」とありますので、浮動小数が扱える / を使って計算することになります。小数点以下の桁数の処理は今回は考えなくても放置で大丈夫です。

1を数えよ【ビット演算】

1を数えよ【ビット演算】

STEP: 1 1を数えよ

1を数えよ

    N = int(input())
    
    N_bin = []  # リストを使う例です
    while N > 0:  # 商が 0 になるまで割り続ける
        N, m = N // 2, N % 2  # 2 で割った商と余りの計算
        N_bin.append(m)  # 余りをリストに追加する
    
    N_bin.reverse()  # 出来上がったリストを逆順に並べ替える
    
    # 2進数のリストから 1 の数をカウントする
    cnt = 0
    for i in N_bin:
        if i == 1:
            cnt += 1
    
    print(cnt)  # 結果出力
    

    10進数を 2進数に変換して、2進数の 1 の個数を数えるという問題です。すでに paiza 解答コード例を閲覧された方は「何これ?」と思われたかもしれません。確かに問題文に書かれている通りの方法でプログラムが書かれていないのですから、とんだ肩透かしですよね。😅

    手始めなのでここでは問題文の通りにそのままのプログラムを書いてみました。

    10進数から 2進数に変換する方法は、後の STEP 4 の問題文に書かれていますので安心してください。😊

STEP: 2 「1を数えよ」を解くために:part1

「1を数えよ」を解くために:part1

    N = int(input())
    print(N)
    

    問題文に『入力を整数型で受け取り、』とありますので、入力後の文字列を整数型に変換してから変数に代入します。

    その変数を画面に出力すれば完了です。

STEP: 3 「1を数えよ」を解くために:part2

「1を数えよ」を解くために:part2

  • 例1

  • S = input()
    print(S.count('1'))
    
  • 例2

  • S = input()
    cnt = 0
    for s in S:
        if s == '1':
            cnt += 1
    print(cnt)
    

    問題文には2進数とありますが、Dランクレベルの問題なので単純に入力を文字列で受け取ってから 1 の数をカウントするだけで良いと思います。

    paiza の解答コード例では range() 関数が使われていますが、プログラム例2の様に入力で受け取った文字列をそのまま使うこともできます。


    今後の問題を解くために、例2の書き方を用いていきます。

STEP: 4 「1を数えよ」を解くために:part3

「1を数えよ」を解くために:part3

    N = int(input())
    
    N_bin = []
    while N > 0:
        N, m = N // 2, N % 2
        N_bin.append(m)
    
    N_bin.reverse()
    
    cnt = 0
    for i in N_bin:
        if i == 1:
            cnt += 1
    
    print(cnt)
    

    『ループメニュー2 約数の列挙 - STEP: 5 10 進数から 2 進数に変換』と似た問題です。注目すべきは 10進数から 2進数への変換方法です。

    paiza の解答コード例もそうなのですが、最後に 1 の数をカウントするだけですので、この問題ではわざわざリバースする必要はありません。😅
    しかし正しく 2進数を作るためには逆順にする必要がありますので、省略しないでくださいね。😉

    省略しちゃった猫さんはやりなおし!😸


    ※ paiza 解答コード例では逆順にする際に reversed()関数が使われていますが、この関数は戻り値を変数に格納しなければ、戻り値がその場で消滅してしまいます。(おそらく凡ミス😅)

    ✕ reversed(binaryNumber)
    ○ binaryNumber = reversed(binaryNumber)

    この他、

    binaryNumber = binaryNumber[::-1]

    という書き方でも逆順にできます。

STEP: 5 「1を数えよ」を解くために:part4

「1を数えよ」を解くために:part4

  • paiza 解答コード例をそのまま引用

  • N = int(input())
    
    if N & 1 == 1:
        print("Yes")
    else:
        print("No")
    

    簡単に入力値が奇数ならば必ず Yes になるのですが、今回は『N を 2 進数に変換することなく、AND 演算(論理積)を使って判定してください。』と釘を刺されているので、ビット演算を用いる方法に従います。

    これまでのステップの内容は、ひとまず横に置いといてください。

  • まず、『論理積ろんりせき』というものについて説明します。

  • セットさんは重複を許さない - 集合と集合演算」で紹介した集合演算と類似していますが、あちらは集合における演算、こちらはビットにおける演算で、用途は別です。でも使う記号は同じです。😅

    初めはややこしく感じるかもしれませんが、使われる記号が同じなだけあって理屈も同じですので、『集合演算はわかるけど、ビット演算は初めて扱う』という方ならば、早く理解できると思います。


  • 集合演算では主に「和集合」「差集合」「積集合」を扱いました。
    ビット演算(論理演算)では主に「論理和 (OR)」「排他的論理和 (XOR)」「論理積 (AND)」が使われます。
    ビット演算なので 2進数、つまり 0 と 1 のみで演算します。

    1. 論理和 (OR | ←バーティカルバー)

      A | B = C

      A OR B の時 C になる
      0 | 0 = 0
      0 | 1 = 1
      1 | 0 = 1
      1 | 1 = 1

      論理和は、どちらかにでも 1 がある時は 1 になります。


    2. 排他的論理和 (XOR ^ ←ハット)

      A ^ B = C

      A XOR B の時 C になる
      0 ^ 0 = 0
      0 ^ 1 = 1
      1 ^ 0 = 1
      1 ^ 1 = 0

      排他的論理和は、どちらかが 1 の時だけ 1 になります。


    3. 論理積 (AND & ←アンパサンド)

      A & B = C

      A AND B の時 C になる
      0 & 0 = 0
      0 & 1 = 0
      1 & 0 = 0
      1 & 1 = 1

      論理積は、どちらも 1 の時だけ 1 になります。かけ算と同じですね。😊


    この問題では、3番の論理積を扱います。問題にある「AND演算」とは、この論理積で演算するということです。

    入力して整数型に変換した N は 10進数です。ビット演算とは言いますが、Pythonでは 10進数のままでもビット演算ができます。

    例えば、問題の入力例1 では入力値が 3 ですが、3 は2進数では 11 です。N & 1 の 1 は2進数では 1 です。ビット数を合わせれば 01 となります。

    よって、N & 1 とは、

    3 & 1 ← 10進数

    11 & 01 ← 2進数

      11
    & 01
    ----
    01 ← 2進数

    1 ← 10進数

    この最後の 1 が、AND演算の結果となります。


  • if N & 1 == 1:== 1 の 1 も 10進数です。これも2進数にすると 01 です。

  • よって、「N & 1 == 1 の時」とは、

    3 & 1 == 1 の時
    ↓↑
    11 & 01 == 01 の時

    であるので、出力結果が Yes となるわけです。

    この == 1 の 1 は、ビット数が 4 (0001) であっても、8 (00000001) であっても、1ビット目のみ 1 です。1ビット目が 1 になるということは、10進数では奇数であるということです。N % 2 == 1 と同じです。

    これだけだとビット演算になんの意味があるのかがわからず、面白くもなんとも無いと思います。論理積を使った余談をしてみます。

    ネットワーク通信では、コンピュータを特定するために「IPアドレス」が使われます。IPアドレスにはグループ名代わりとなる「ネットワーク部」と、機器のID番号を表す「ホスト部」の2つに分かれています。これらについての詳しい説明は省きますが、IPv4 の場合、例えば「192.168.0.1」というIPアドレスがあるとします。

    このIPアドレスの前半のどこまでがネットワーク部で、後半のどこからがホスト部かを見つける方法に「サブネットマスク」というものが使われます。
    サブネットマスクは「255.255.255.0」という表記のしかたをします。255 のところはネットワーク部で、0 のところがホスト部です。


    255 は 8ビットで、2進数で書くと 11111111 です。0 は 00000000 です。

    「192.168.0.1」を 2進数表記にすると、

    11000000.10100000.00000000.00000001

    と書きます。一方サブネットマスク(「255.255.255.0」の場合)は、

    11111111.11111111.11111111.00000000

    と表記します。

    この2つを論理積、つまりAND演算にかけると、

      11000000.10100000.00000000.00000001
    & 11111111.11111111.11111111.00000000
    -------------------------------------
    11000000.10100000.00000000.00000000



    192.168.0.0

    これでこの 192.168.0 のところがネットワーク部であるということを通信機器に伝えることができます。
    ちなみにホスト部が 0 になっているIPアドレス「192.168.0.0」を「ネットワークアドレス」といいます。

    集合演算とビット演算は理屈は同じと先に言いましたが、「和集合と論理和」「積集合と論理積」は同じ様な考え方ができます。

    和集合は「2つの集合を合わせて重複を省く」性質ですが、言い換えると「どちらか片方、もしくは両方に同じ値がある時に選ばれる」、つまり『値』が2進数の 1 に代わっただけで上の論理和の演算表のとおりとなります。

    「和」という言葉が使われるので広く「足し算」と例えられますが、『{1}{1} を合わせると {1, 1} になり、そこから重複を除去すると {1} になる』と考えれば、集合演算と同じと考えられるので理解しやすいでしょう。

    積集合は「両方に同じ値がある時のみに選ばれる」、つまり上の論理積の演算表のとおりです。


    そして、排他的論理和(XOR)は集合演算にも存在します。

    どちらか片方にのみ存在する値が選ばれる

    a = {1, 2, 3}
    b = {2, 3, 4}
    print(a ^ b)
    
    {1, 4}

    a = {1, 2, 3}
    a ^= {2, 3, 4}
    print(a)
    
    {1, 4}

    排他的論理和は「暗号化・復号」などで使われます。興味がありましたらネットなどで調べてみてください。😊

    これまでのことを踏まえれば、次の FINAL問題 ではどのようなプログラムが求められているのかが察しやすくなると思います。

    そもそも、ビット演算はプログラミング学習的には現在Cランク問題を頑張って解いている方にはかなり難しいと思います。😅

    わからなすぎて挫折してしまったとしても、今は問題ありませんからね!!👍

FINAL問題 1を数えよ【ビット演算】

1を数えよ【ビット演算】

    N = int(input())  # part1
    
    cnt = 0
    while N > 0:  # part3
        if N & 1 == 1:  # part4
            cnt += 1  # part2
        N //= 2  # part3
    
    print(cnt)
    

    これまでの流れを混ぜ合わせれば出来上がります。

    まずここ、

    while N > 0:
        ・・・
        N //= 2
    

    この部分は 10進数を 2進数に変換する際の『商が 0 になるまで N を 2 で割っていく』所です。

    そして の次の、

    if N & 1 == 1:
        cnt += 1
    

    この部分は『N の1ビット目が 1 の時にカウントアップする』所です。


    この 文の流れを見てみましょう。

    N = 45 の時

    45

    101101
    cnt = 1

    ↓ 45 // 2 = 22

    22

    10110

    ↓ 22 // 2 = 11

    11

    1011
    cnt = 2

    ↓ 11 // 2 = 5

    5

    101
    cnt = 3

    ↓ 5 // 2 = 2

    2

    10

    ↓ 2 // 2 = 0

    0

    1
    cnt = 4

    N が 0 なのでここでループを抜け、最後に cnt の値を画面に出力して完了です。


    せっかくなので、paiza 解答コード例の補足説明もしてみます。上記の方法と真逆とも言えます。

  • paiza 解答コード例からそのまま引用

  • N = int(input())
    ans = 0
    bitMask = 1
    while bitMask<=N:
        if bitMask&N != 0:
            ans += 1
        bitMask *= 2
        
    print(ans)
    

    初めのプログラム例の「N を 2 で割っていく」方法ではなく、N & 11 の方を 2倍していく方法です。

    N = 45 の時

    bitMask = 1

    重み(10進数) 32 16 8 4 2 1
    bitMask 1
    N 1 0 1 1 0 1
    bitMask & N 1

    この時、bitMask & N は「おもみ」と等しくなります。よって if bitMask&N != 0:True となり、ans += 1 が実行されます。

    ans = 1

    ↓ bitMask * 2 = 2

    bitMask = 2

    重み(10進数) 32 16 8 4 2 1
    bitMask 1 0
    N 1 0 1 1 0 1
    bitMask & N 0 0

    bitMask & N0 となります。if bitMask&N != 0:False となりますのでカウントアップせず、次のループに移ります。== 1 ではなく != 0 になっているのは、bitMask & N が重みと等しくなる為に、0 か 0 でないかで判定しているわけです。

    ↓ bitMask * 2 = 4

    bitMask = 4

    重み(10進数) 32 16 8 4 2 1
    bitMask 1 0 0
    N 1 0 1 1 0 1
    bitMask & N 1 0 0

    if bitMask&N != 0:True

    ans = 2

    ↓ bitMask * 2 = 8

    bitMask = 8

    重み(10進数) 32 16 8 4 2 1
    bitMask 1 0 0 0
    N 1 0 1 1 0 1
    bitMask & N 1 0 0 0

    if bitMask&N != 0:True

    ans = 3

    ↓ bitMask * 2 = 16

    bitMask = 16

    重み(10進数) 32 16 8 4 2 1
    bitMask 1 0 0 0 0
    N 1 0 1 1 0 1
    bitMask & N 0 0 0 0 0

    if bitMask&N != 0:False

    ↓ bitMask * 2 = 32

    bitMask = 32

    重み(10進数) 32 16 8 4 2 1
    bitMask 1 0 0 0 0 0
    N 1 0 1 1 0 1
    bitMask & N 1 0 0 0 0 0

    if bitMask&N != 0:True

    ans = 4

    ↓ bitMask * 2 = 64

    ここで N = 45 を超えたので、while bitMask<=N: の条件が False になり、ループを抜け、ans の値を画面に出力して完了となります。


    『ビットシフトを使った解法』のほうも解説しましょう。

  • paiza 解答コード例 2

  • N = int(input())
    ans = 0
    target = 0 # 1 を何ビット左にシフトさせるか
    while 1 << target <= N:
        if 1 << target & N != 0:
            ans += 1
        target += 1
        
    print(ans)
    
  • paiza 解答コード例 (改)

  • N = int(input())
    ans = 0
    target = 1
    while target <= N:
        if target & N != 0:
            ans += 1
        target <<= 1
        
    print(ans)
    

    ビットシフトというのは、『ビット全体を左右にずらす』ことです。1ビットを左に1つシフト(移動)すると 2ビットとなります。さらに 1ビット左にシフトすると 3ビットとなります。

    4 ← 2 ← 1

    となるわけですが、左へシフトさせると掛け算、右へシフトさせると割り算することになります。2進数なので 2 で掛け算割り算をすることになります。これは 10進数でも同じことが言えます。

    100 ← 10 ← 1

    見た目だけなら 2進数も 10進数も同じですね。😆


    ビットを右にシフトすると 2 で割り算をすることになりますが、この FINAL問題の解説の初めのプログラムでは 2 で割り算するプログラムになっています。そのプログラム例の 文の流れの解説を見てみると、2 で割るごとにビットが右にシフトされていく様子がよくわかると思います。

    bitMasktarget に変数名が変わっただけです。ただ一箇所、

    target <<= 1
    

    ここでビットをシフトしています。左にシフトする時は << 、右にシフトする時は >> を使います。そして何ビットシフトするのかを正数で示します。正数とは 0以上の整数のことです。今回は1ビットなので 1 です。こうすることでビット数が1つずつ増えていく、つまり倍々となっていきます。

    これは target *= 2 と同等です。target <<= 1target *= 2 のどちらでも等しく動作します。


    ということで、初めのプログラムの

    N //= 2  # part3
    

    この部分もアレして動かしてみましょう。😉


    ちなみに target <<= 1 は、target = target << 1 のことです。

    target << 1
    target を左に1ビットシフトする。(増分は1ビット)
    都度 targetビットから左に1ビットずつシフトしています。
    10 ← 1
    100 ← 10
    1000 ← 100
    1 << target
    1ビットから左に targetビットシフトする。(増分は targetビット)
    常に1ビット目から左に targetビットシフトしています。
    10 ← 1
    100 ← 1
    1000 ← 1

    paiza 解答コード例では target の初期値が 0 になっていますが、これは一回目のループで「左に 0 ビットシフトする」、つまり 1ビットの所から始める為に初期値は 0 となっています。

    (改) のほうは初期値が 1 になっています。これはビットをシフトし始めるのがループ1周目の最後になっているからです。その上、これを 0 にしてしまうと常に 0 × 2 = 0 となり、無限ループに陥ります。0ビット、要するにビットが存在しないのにビットをシフトしようとしても、無いものは無いのでシフトできないので、常に 0 になってしまうわけです。

    ビット演算は、コンピュータが使う2進数のまま値を操作するのでコンピュータに対してとても優しい演算法です。あなたが2進数を理解し、使い慣れるようにならない限り「あ、これはビット演算が効率よさそうだな」とひらめくことはまず無いでしょうけど😅、ITの学習を幅広く行なっていくと度々登場してきます。理解半分でも、いずれまたやるべき時が必ず訪れますので『じゃあとりあえず、今はこのくらいでいいか』と割り切っちゃって次の学習に進んでしまいましょう。😄

文字列の抽出

文字列の抽出

STEP: 1 文字列の抽出

文字列の抽出

    tag_a, tag_b = input().split()
    S = input()
    
    tag_a_len = len(tag_a)
    tag_b_len = len(tag_b)
    
    idx = 0
    while idx < len(S) - tag_a_len:
        if S[idx:idx+tag_a_len] != tag_a:
            idx += 1
            continue
        
        idx += tag_a_len
        text_st_idx = idx
        while S[idx:idx+tag_b_len] != tag_b:
            idx += 1
    
        if idx - text_st_idx > 0:
            print(S[text_st_idx:idx])
        else:
            print('<blank>')
    

    Cランクにしては難易度高めだと思います。線形探索の問題です。

STEP: 2 文字列の抽出 : part1

文字列の抽出 : part1

  • 例1

  • S = input()
    print(S)
    print(S)
    
  • 例2

  • S = input()
    for _ in range(2):
        print(S)
    
STEP: 3 文字列の抽出 : part2

文字列の抽出 : part2

    tag_a, tag_b = input().split()
    S = input()
    a_index, b_index = map(int, input().split())
    
    idx_st = a_index + len(tag_a) - 1
    idx_ed = b_index - 1
    
    text = S[idx_st:idx_ed]
    
    if text:
        print(text)
    else:
        print('blank')
    

    ここで求められているスキルは「インデックスの理解」です。タグの開始位置番号は与えられていますので、これを使って抽出する文字列の範囲を作ります。

    単純に開始タグと終了タグの開始位置番号をそのまま使って文字列を抽出すると次のようになります。

    入力例1
        <a> <b>
        a<a>c<b>c
        2 6
    
    a>c<

    与えられた開始位置は1文字目から数えられています。位置番号は 0 から始まりますので、これらの数から 1 を引く必要があります。

    そうすると今度は、

    <a>c

    となります。開始タグも終了タグも開始位置の番号なのでこうなってしまいます。

    抽出する文字列の開始位置番号は「開始タグの開始位置番号 + 開始タグの文字数」で得られます。

    位置番号 0 1 2 3 4 5 6 7 8
    S a < a > c < b > c

    位置番号は 1 、開始タグの文字数は 3 、1 + 3 = 4 で、抽出する文字列の開始位置番号は 4 になります。そのプログラムが、

    idx_st = a_index + len(tag_a) - 1
    

    この部分です。ここから、

    idx_ed = b_index - 1
    

    この部分までが「抽出する文字列の範囲」となります。

    text = S[idx_st:idx_ed]
    

    ここで抽出する文字列を、スライスを使って範囲を指定しているのですが、抽出する文字列が

    ある場合
    text に抽出した文字列が代入されます。
    無かった場合
    text'' が代入されます。

    その次のプログラムの

    if text:
        print(text)
    else:
        print('blank')
    

    if text: では、text に文字列があれば True に、無い '' 場合は False となってそれぞれの結果が画面に出力されます。

    この 文の評価の判定については「bool型 True と False - 『ある』か『ない』か」で表にしてありますので、そちらをご覧ください。

STEP: 4 文字列の抽出 : part3

文字列の抽出 : part3

  • 例1

  • tag_a, tag_b = input().split()
    S = input()
    print(S.find(tag_a)+1, S.find(tag_b)+1)
    
  • 例2

  • input()
    S = input()
    
    idxs = []
    for i, s in enumerate(S, start=1):
        if s == '<':
            idxs.append(i)
    
    print(*idxs)
    
  • 例3

  • tag_a, tag_b = input().split()
    S = input()
    
    tag_a_len = len(tag_a)
    tag_b_len = len(tag_b)
    
    tag_idxs = []
    for i in range(len(S)):
        if S[i:i+tag_a_len] == tag_a or S[i:i+tag_b_len] == tag_b:
            tag_idxs.append(i + 1)
    
    print(*tag_idxs)
    

    後先考えなくてよいのであれば例1の方法が簡単です。.index() メソッドも使えます。

    例2は、「期待する出力」に『つまり、それぞれのタグの<の位置が何文字目であるかを出力してください。』とありますのでただそれに従い、なるべく簡単な方法で書いただけのプログラムです。

    例3は、各タグの文字列を S の先頭から末尾まで探し、見つけたらその位置をリストに格納します。問題文に『テキストデータは必ず開始タグと終了タグがこの順で丁度1つずつ存在します。』とありますので、必ずこの順で見つけられます。

    リストを使っているのは、出力をラクにするためです。Pythonはホント、この形式の出力がめんどくさい。😓
    この方法ならばリストをアンパックするだけで期待する出力形式が得られます。

    この print(*) の機能については「見える化ちゃん - print() のさらなる機能」で学習できます。もうこれ以降使わないけど。😅

STEP: 5 文字列の抽出 : part4

文字列の抽出 : part4

    tag_a, tag_b = input().split()
    S = input()
    
    tag_a_len = len(tag_a)
    tag_b_len = len(tag_b)
    
    idx = 0
    while idx < len(S):
        # tag_a を探す
        if S[idx:idx+tag_a_len] != tag_a:
            idx += 1
            continue
    
        tag_a_idx = idx + 1
        
        # tag_b を探す
        while S[idx:idx+tag_b_len] != tag_b:
            idx += 1
    
        tag_b_idx = idx + 1
        
        print(tag_a_idx, tag_b_idx)
    

    めんどくさいですね。😓

    しかしやっていることはいたって単純で、 文字列 S の先頭から tag_a を探し、見つけたら tag_a_idx に位置番号を記録し、次の tag_b も同様の処理をします。これを文字列 S の末尾まで行ないます。

    paiza 解答コード例では、文字列 S の末尾まで探索するのは無駄なので、tag_a (※)の文字数分を文字列 S の文字数から引いています。これはここでは次の FINAL問題で導入します。

    tag_a_len + tag_b_len - 1 だともっとよいかも。😊

FINAL問題 文字列の抽出

文字列の抽出

    tag_a, tag_b = input().split()
    S = input()
    
    tag_a_len = len(tag_a)
    tag_b_len = len(tag_b)
    
    idx = 0
    while idx < len(S) - (tag_a_len + tag_b_len - 1):
        if S[idx:idx+tag_a_len] != tag_a:
            idx += 1
            continue
        
        idx += tag_a_len
        text_st_idx = idx
        while S[idx:idx+tag_b_len] != tag_b:
            idx += 1
    
        if idx - text_st_idx > 0:
            print(S[text_st_idx:idx])
        else:
            print('<blank>')
    

    STEP 3 の複数取得版です。プログラムの形は1つ前の STEP 5 寄りですが。

    探索範囲は、ここでは「S の文字数 - (tag_a_len + tag_b_len - 1)」とします。

    while idx < len(S) - (tag_a_len + tag_b_len - 1):
        if S[idx:idx+tag_a_len] != tag_a:
            idx += 1
            continue
    

    ここで tag_a を探しています。ひたすら探すだけです。(詳しい説明を最後にします)


        idx += tag_a_len  # 位置番号を文字数分送る
        text_st_idx = idx
    

    tag_a がみつかったら位置番号を tag_a の文字数分送って(STEP 3 でやったことです)、抽出する文字列の先頭の位置番号を、変数 text_st_idx に保存します。これは最後、抽出した文字列をスライスを使って出力する際に、開始の位置として使うためのものです。

        while S[idx:idx+tag_b_len] != tag_b:
            idx += 1
    

    今度は tag_b をひたすら探します。みつけたらこのループを抜けます。
    この時、位置番号は tag_b の先頭にあり、これをスライスの [start:stop] に使うと、抽出する文字列の末尾までが取得できます。

        if idx - text_st_idx > 0:
            print(S[text_st_idx:idx])
        else:
            print('<blank>')
    

    idx - text_st_idx > 0 とは、例えば、

    text_st_idx = 9
    idx = 13

    の時、13 - 9 = 4 で抽出する文字は4文字となります。0 より大きいとは、抽出する文字が1文字以上ある時ということになりますので、その場合はスライスを使ってその範囲の文字を出力します。

    抽出する文字が無い場合は、idx が1つも動きませんので、text_st_idxidx の値は同じになっています。ですので idx - text_st_idx = 0文の判定は False となり、画面に <blank> と出力されます。


    これを初めの の条件式の判定が False となるまでループします。

  • 最後に、この部分を詳しく説明します。

  • while idx < len(S) - (tag_a_len + tag_b_len - 1):  # ← ココ
        if S[idx:idx+tag_a_len] != tag_a:
            idx += 1
            continue
    

    抽出する文字がなかった場合、開始タグと終了タグを繋げた文字数は「開始タグの文字数 + 終了タグの文字数」です。

    例)

    tag_a = '<a>'
    tag_a_len = 3

    tag_b = '<b>'
    tag_b_len = 3

    3 + 3 = 6(文字)

    抽出する文字がない場合は <a><b> という文字列になっています。この場合、最低限検索に必要な文字数は6文字です。ということは、残り5文字以下の文字列からは <a><b> という文字列はヒットしませんので、これ以降調べる意味はありません。

    残り文字数 6 5 4 3 2 1
    6文字までは
    ヒットする →
    < a > < b >
    5文字以下は
    ヒットしない →
    < a > < b

    よって、

    len(S) - (tag_a_len + tag_b_len - 1)

    と書くことができます。

    『う~ん、わかりそうでイマイチ自信ないな』と感じたら、問題集『定番アルゴリズムの習得 - 線形探索メニュー』をやってみてください。