summer_tree_home

Check iOでPython3をマスターするぜっ

CheckSum (Incinerator) - チェックサムを求めよ

どんな問題?

CheckSum
http://www.checkio.org/mission/check-digit/

入力値のチェックサムを求めよ。

チェックサムの求め方は以下の通り。

  1. 各桁の数字を変換する。
    • 右から偶数番目:数字を2倍して十の桁と一の桁を足した数字。
    • 右から奇数番目:数字をそのまま使う。
  2. 変換した数字を合計する。
  3. 合計値が10の倍数になるように、最後の数字を追加する。

戻り値は、3で求めた最後の数字と、2で求めた合計値をリストにして返す。(タプルじゃなくてリスト)

また、各桁が英字(大文字のみ)の場合にも、チェックサムを求めることができる。
その場合は、英字のアスキーコードから48を引いた数を使って、同じように計算する。

数字と英大文字以外は、無視してよい。

どうやって解く?

流れとしては、

  1. 引数から、数字と英大文字だけを取り出す。
  2. 右から偶数番目の文字と奇数番目の文字に分ける。
  3. それぞれ数字を変換して合計する。
  4. 合計値が10の倍数となるような、最後の数字を計算する。

という感じ。

数字と英大文字だけを取り出す

以前、House Passwordで知った string.digits と string.ascii_uppercase 定数を使おう。

s = [c for c in data if c in string.digits + string.ascii_uppercase]

右から偶数番目の文字と奇数番目の文字に分ける

ちょっとまぎらわしいが、index=0は偶数となる。

index 5 4 3 2 1 0
奇数 偶数 奇数 偶数 奇数 偶数

文字列を右から偶数番目と奇数番目に分けるには、スライスを使って s[-1::-2] と s[-2::-2] とする。s[-1]が一番右の文字で、そこからstep=-2で1つおきに左に進んでいくので、偶数番目を取り出せる。この方法は、Bit Messageのときにも使った。

>>> '6543210'[-1::-2]
'0246'

>>> '6543210'[-2::-2]
'135'

数字を変換して合計する

偶数番目は、数字ならint()で整数にすればよい。英字ならord()して48を引く。

def odd_digit(c):
    if c in string.digits:
        return int(c)
    else:
        return ord(c) - 48

奇数番目は、2倍して十の桁と一の桁を足す。

def even_digit(c):
    n = odd_digit(c)
    return n * 2 // 10 + n * 2 % 10

あとは、sum()で合計値を求めるだけ。

sum_digits = sum(even_digit(c) for c in s[-1::-2]) + sum(odd_digit(c) for c in s[-2::-2])

合計値が10の倍数となるような、最後の数字を計算する。

(10 - sum_digits % 10)

だと、10~1になってしまうので、もう一度 % 10 しておく。

final_char = str((10 - sum_digits % 10) % 10)

まとめてホイ

import string

def checkio(data):
    def odd_digit(c):
        if c in string.digits:
            return int(c)
        else:
            return ord(c) - 48

    def even_digit(c):
        n = odd_digit(c)
        return n * 2 // 10 + n * 2 % 10

    s = [c for c in data if c in string.digits + string.ascii_uppercase]
    sum_digits = sum(even_digit(c) for c in s[-1::-2]) + sum(odd_digit(c) for c in s[-2::-2])
    final_char = str((10 - sum_digits % 10) % 10)
    return [final_char, sum_digits]

クリア~。
公開後に気付いたけど、if ~ else は1行にまとめれば良かった。まあいいや。

他の人の答え

odd_digit()の部分は、数字と英大文字で分岐しなくても、

def odd_digit(c):
    return ord(c) - 48

でいけたのか。48ってなんのこっちゃと思ってたが、ord('0') が 48 なのか。よく見たら問題文にも書いてあった・・・。

また、final_charを求めるときに、0~9にするため

final_char = str((10 - sum_digits % 10) % 10)

としていたが、

final_char = str(-sum_digits % 10)

でいけるらしい。
負数の剰余ってあまりイメージできないけど、

>>> -1 % 10
9

>>> -2 % 10
8

となってるんだな。

http://www.checkio.org/mission/check-digit/publications/veky/python-3/explicit-is-better-than-implicit/