2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

PythonでModintをつくったよ

Last updated at Posted at 2023-04-15

コード

"""Modint

・つかい方
mint = Modint(998244353)
value = mint(10)
"""


class Modint():
    def __init__(self, mod: int):
        self.mod: int = mod

    def __call__(self, num: int):
        return NumberMint(num, self.mod)


class NumberMint():
    def __init__(self, num: int, mod: int):
        self.num: int = num
        self.mod: int = mod
        if self.num < 0 or self.mod <= self.num:
            self.num %= self.mod
        self.__inverse = None  # 逆数

    def __int__(self) -> int:
        return self.num

    def __str__(self) -> str:
        return str(self.num)

    def __repr__(self) -> str:
        return "Modint({})".format(self.num)

    def __add__(self, other):
        if isinstance(other, NumberMint):
            new_value = self.num + other.num
        else:
            new_value = self.num + other
        if self.mod <= new_value:
            new_value -= self.mod
        return NumberMint(new_value, self.mod)

    def __sub__(self, other):
        if isinstance(other, NumberMint):
            new_value = self.num - other.num
        else:
            new_value = self.num - other
        if new_value < 0:
            new_value += self.mod
        return NumberMint(new_value, self.mod)

    def __mul__(self, other):
        if isinstance(other, NumberMint):
            new_value = self.num * other.num % self.mod
        else:
            new_value = other % self.mod * self.num % self.mod
        return NumberMint(new_value, self.mod)

    def __truediv__(self, other):
        if isinstance(other, NumberMint):
            new_value = self.num * other.inverse % self.mod
        else:
            other_inverse = pow(other, self.mod - 2, self.mod)
            new_value = self.num * other_inverse % self.mod
        return NumberMint(new_value, self.mod)

    def __floordiv__(self, other):
        return self / other

    def __pow__(self, power):
        return pow(self.num, power, self.mod)

    def __iadd__(self, other):
        return self + other

    def __isub__(self, other):
        return self - other

    def __imul__(self, other):
        return self * other

    def __itruediv__(self, other):
        return self / other

    def __ifloordiv__(self, other):
        return self / other

    def __radd__(self, other):
        return self + other

    def __rsub__(self, other):
        return NumberMint(other, self.mod) - self

    def __rmul__(self, other):
        return self * other

    def __rtruediv__(self, other):
        return NumberMint(other, self.mod) / self

    def __rfloordiv__(self, other):
        return NumberMint(other, self.mod) / self

    def __eq__(self, other) -> bool:
        if isinstance(other, NumberMint):
            return self.num == other.num
        return self.num == other % self.mod

    def __ne__(self, other) -> bool:
        if isinstance(other, NumberMint):
            return self.num != other.num
        return self.num != other % self.mod

    def __inverse_generator(self):
        return pow(self.num, self.mod - 2, self.mod)

    @property
    def inverse(self) -> int:
        if self.__inverse is None:
            self.__inverse = self.__inverse_generator()
        return self.__inverse

つかい方

インスタンス作成

mint = Modint(998244353)  # MODをセットする。
x = mint(15)  # これ以降は、xを使った演算はすべて998244353を法にしてくれる。

できる演算の一覧

mint = Modint(998244353)
x = mint(10)
y = mint(7)

print(x + y)  # 17
print(x - y)  # 3
print(x * y)  # 70
print(x / y)  # 570425346
print(x // y)  # 570425346
print(y ** 5)  # 16807
print(x == y)  # False
print(x != y)  # True

# こういうタイプのもできます!
x += y
x -= y
x *= y
x /= y
x //= y

# 片方だけでもModintだったら、演算結果もModintになります!
print(x + 5)  # 15
print(22 + y)  # 29

# 逆数
print(x.inverse)  # 99824436

コメント

わざわざ % mod を書く手間がはぶけます!
割り算(分数)が直感的に書けます!
mod $998244353$ や mod $10^9+7$ が問題で出てきたら、とりあえずこれを使っておけばmodのことを気にせずに問題に集中できます。
気に入ったら、ぜひつかってみてね。

こだわりポイント (オタク向け)

逆数に関しては、実はクラスの中ではself.__inverse という変数にして外部アクセスができないようにしていて、初期値はNoneにしてあります。
inverseメソッドを使うと、もしself.__inverse がNoneのときは下のようにself.__inverseをちゃんと計算します。Noneでなければ、その値をそのまま返します。

self.__inverse = pow(self.num, self.mod - 2, self.mod)

.inverse() と書くとメソッドの雰囲気が出ちゃうので、propertyデコレータを使うことで変数を扱っている感じにしました。

@property
def inverse(self) -> int:
    if self.__inverse is None:
        self.__inverse = self.__inverse_generator()
    return self.__inverse

問題によってはたくさん逆数が呼び出されることがあって、その度に逆数を計算してたらTLEしちゃうこともあるので、逆数をクラス内の変数に持っておく工夫をしました。

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?