Modularna multiplikatywna funkcja odwrotna w Pythonie


112

Czy jakiś standardowy moduł Pythona zawiera funkcję obliczającą modularną multiplikatywną odwrotność liczby, tj. y = invmod(x, p)Taką, która x*y == 1 (mod p)? Wydaje się, że Google nie daje żadnych dobrych wskazówek na ten temat.

Oczywiście, można wymyślić w domu 10-liniowy rozszerzony algorytm euklidesowy , ale po co wymyślać koło na nowo.

Na przykład Java BigIntegerma modInversemetodę. Czy Python nie ma czegoś podobnego?


18
W Pythonie 3.8 (ukaże się jeszcze w tym roku), będziesz mógł korzystać z wbudowanej powfunkcji dla tego: y = pow(x, -1, p). Zobacz bugs.python.org/issue36027 . Od zadania pytania do rozwiązania pojawiającego się w bibliotece standardowej minęło zaledwie 8,5 roku!
Mark Dickinson

4
Widzę, że @MarkDickinson skromnie zapomniał wspomnieć, że ey jest autorem tego bardzo użytecznego ulepszenia, więc to zrobię. Dzięki za tę pracę, Mark, wygląda świetnie!
Don Hatch

Odpowiedzi:


129

Może ktoś uzna to za przydatne (z wikibooks ):

def egcd(a, b):
    if a == 0:
        return (b, 0, 1)
    else:
        g, y, x = egcd(b % a, a)
        return (g, x - (b // a) * y, y)

def modinv(a, m):
    g, x, y = egcd(a, m)
    if g != 1:
        raise Exception('modular inverse does not exist')
    else:
        return x % m

1
Miałem problemy z liczbami ujemnymi przy użyciu tego algorytmu. modinv (-3, 11) nie działa. Naprawiłem to, zastępując egcd implementacją na drugiej stronie tego pliku PDF: anh.cs.luc.edu/331/notes/xgcd.pdf Mam nadzieję, że to pomoże!
Qaz

@Qaz Możesz także po prostu zmniejszyć -3 modulo 11, aby było dodatnie, w tym przypadku modinv (-3, 11) == modinv (-3 + 11, 11) == modinv (8, 11). Prawdopodobnie właśnie to robi w pewnym momencie algorytm w pliku PDF.
Thomas

1
Jeśli używasz sympy, to x, _, g = sympy.numbers.igcdex(a, m)załatwia sprawę.
Lynn

59

Jeśli twój moduł jest liczbą pierwszą (nazywasz to p), możesz po prostu obliczyć:

y = x**(p-2) mod p  # Pseudocode

Lub we właściwym Pythonie:

y = pow(x, p-2, p)

Oto ktoś, kto wdrożył pewne funkcje teorii liczb w Pythonie: http://www.math.umbc.edu/~campbell/Computers/Python/numbthy.html

Oto przykład zrobiony po monicie:

m = 1000000007
x = 1234567
y = pow(x,m-2,m)
y
989145189L
x*y
1221166008548163L
x*y % m
1L

1
Naiwne potęgowanie nie jest opcją z powodu limitu czasu (i pamięci) dla dowolnej rozsądnie dużej wartości p, jak powiedzmy 1000000007.
dorserg

16
modularne potęgowanie jest wykonywane przy użyciu co najwyżej N * 2 mnożenia, gdzie N jest liczbą bitów wykładnika. przy użyciu modułu 2 ** 63-1 można obliczyć odwrotność po znaku zachęty i natychmiast zwrócić wynik.
phkahler

3
Łał fantastycznie. Jestem świadomy szybkiego potęgowania, po prostu nie byłem świadomy, że funkcja pow () może przyjąć trzeci argument, który zamienia ją w potęgę modularną.
dorserg

5
Dlatego właśnie używasz Pythona, prawda? Bo jest super :-)
phkahler

2
Swoją drogą to działa, ponieważ z małego twierdzenia Fermata pow (x, m-1, m) musi wynosić 1. Stąd (pow (x, m-2, m) * x)% m == 1. Więc pow (x, m-2, m) jest odwrotnością x (mod m).
Piotr Dabkowski

21

Możesz również spojrzeć na moduł gmpy . Jest to interfejs między Pythonem a biblioteką o wielu precyzjach GMP. gmpy zapewnia funkcję odwracania, która robi dokładnie to, czego potrzebujesz:

>>> import gmpy
>>> gmpy.invert(1234567, 1000000007)
mpz(989145189)

Zaktualizowana odpowiedź

Jak zauważono w @hyh, gmpy.invert()zwraca 0, jeśli odwrotność nie istnieje. To pasuje do zachowania funkcji GMP mpz_invert(). gmpy.divm(a, b, m)zapewnia ogólne rozwiązanie a=bx (mod m).

>>> gmpy.divm(1, 1234567, 1000000007)
mpz(989145189)
>>> gmpy.divm(1, 0, 5)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: not invertible
>>> gmpy.divm(1, 4, 8)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: not invertible
>>> gmpy.divm(1, 4, 9)
mpz(7)

divm()zwróci rozwiązanie, gdy gcd(b,m) == 1i zgłosi wyjątek, gdy funkcja multiplikatywna odwrotna nie istnieje.

Zastrzeżenie: jestem obecnym opiekunem biblioteki gmpy.

Zaktualizowana odpowiedź 2

gmpy2 teraz poprawnie zgłasza wyjątek, gdy odwrotność nie istnieje:

>>> import gmpy2

>>> gmpy2.invert(0,5)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: invert() no inverse exists

To jest fajne, dopóki nie znalazłem gmpy.invert(0,5) = mpz(0)zamiast zgłaszania błędu ...
h__

@hyh Czy możesz zgłosić ten problem na stronie głównej gmpy? Zgłaszanie problemów jest zawsze mile widziane.
casevh

Przy okazji, czy w tym gmpypakiecie jest mnożenie modularne ? (tj. jakaś funkcja, która ma tę samą wartość, ale jest szybsza niż (a * b)% p?)
h__ Kwietnia

Zostało to zaproponowane wcześniej i eksperymentuję z różnymi metodami. Najprostsze podejście polegające na zwykłym obliczaniu (a * b) % pw funkcji nie jest szybsze niż samo obliczanie (a * b) % pw Pythonie. Narzut wywołania funkcji jest większy niż koszt oceny wyrażenia. Więcej informacji można znaleźć pod adresem code.google.com/p/gmpy/issues/detail?id=61 .
casevh

2
Wspaniałą rzeczą jest to, że działa to również dla modułów innych niż pierwotne.
synekdocha

14

Od 3,8 pythona funkcja pow () może przyjmować moduł i ujemną liczbę całkowitą. Zobacz tutaj . Ich argumentacja, jak tego używać

>>> pow(38, -1, 97)
23
>>> 23 * 38 % 97 == 1
True

8

Oto jedna linijka dla CodeFights ; to jedno z najkrótszych rozwiązań:

MMI = lambda A, n,s=1,t=0,N=0: (n < 2 and t%N or MMI(n, A%n, t, s-A//n*t, N or n),-1)[n<1]

Zwróci, -1jeśli Anie ma funkcji multiplikatywnej odwrotności w n.

Stosowanie:

MMI(23, 99) # returns 56
MMI(18, 24) # return -1

Rozwiązanie wykorzystuje rozszerzony algorytm euklidesowy .


6

Sympy , moduł Pythona do matematyki symbolicznej, ma wbudowaną modułową funkcję odwrotną, jeśli nie chcesz implementować własnej (lub jeśli już używasz Sympy):

from sympy import mod_inverse

mod_inverse(11, 35) # returns 16
mod_inverse(15, 35) # raises ValueError: 'inverse of 15 (mod 35) does not exist'

Wydaje się, że nie jest to udokumentowane na stronie internetowej Sympy, ale tutaj jest dokumentacja: Sympy mod_inverse docstring na Github


2

Oto mój kod, może być niechlujny, ale i tak wygląda na to, że działa.

# a is the number you want the inverse for
# b is the modulus

def mod_inverse(a, b):
    r = -1
    B = b
    A = a
    eq_set = []
    full_set = []
    mod_set = []

    #euclid's algorithm
    while r!=1 and r!=0:
        r = b%a
        q = b//a
        eq_set = [r, b, a, q*-1]
        b = a
        a = r
        full_set.append(eq_set)

    for i in range(0, 4):
        mod_set.append(full_set[-1][i])

    mod_set.insert(2, 1)
    counter = 0

    #extended euclid's algorithm
    for i in range(1, len(full_set)):
        if counter%2 == 0:
            mod_set[2] = full_set[-1*(i+1)][3]*mod_set[4]+mod_set[2]
            mod_set[3] = full_set[-1*(i+1)][1]

        elif counter%2 != 0:
            mod_set[4] = full_set[-1*(i+1)][3]*mod_set[2]+mod_set[4]
            mod_set[1] = full_set[-1*(i+1)][1]

        counter += 1

    if mod_set[3] == B:
        return mod_set[2]%B
    return mod_set[4]%B

2

Powyższy kod nie będzie działał w python3 i jest mniej wydajny w porównaniu z wariantami GCD. Jednak ten kod jest bardzo przejrzysty. To skłoniło mnie do stworzenia bardziej kompaktowej wersji:

def imod(a, n):
 c = 1
 while (c % a > 0):
     c += n
 return c // a

1
To jest w porządku, aby wyjaśnić to dzieciom i kiedy n == 7. Ale poza tym chodzi o odpowiednik tego "algorytmu":for i in range(2, n): if i * a % n == 1: return i
Tomasz Gandor

2

Oto zwięzła 1-liniowa wersja, która to robi, bez korzystania z zewnętrznych bibliotek.

# Given 0<a<b, returns the unique c such that 0<c<b and a*c == gcd(a,b) (mod b).
# In particular, if a,b are relatively prime, returns the inverse of a modulo b.
def invmod(a,b): return 0 if a==0 else 1 if b%a==0 else b - invmod(b%a,a)*b//a

Zauważ, że jest to tak naprawdę tylko egcd, usprawnione, aby zwracać tylko jeden współczynnik zainteresowania.


1

Aby obliczyć modularną multiplikatywną odwrotność, zalecam użycie rozszerzonego algorytmu euklidesowego w następujący sposób:

def multiplicative_inverse(a, b):
    origA = a
    X = 0
    prevX = 1
    Y = 1
    prevY = 0
    while b != 0:
        temp = b
        quotient = a/b
        b = a%b
        a = temp
        temp = X
        a = prevX - quotient * X
        prevX = temp
        temp = Y
        Y = prevY - quotient * Y
        prevY = temp

    return origA + prevY

Wygląda na to, że w tym kodzie jest błąd: a = prevX - quotient * Xpowinien być X = prevX - quotient * Xi powinien powrócić prevX. FWIW, ta implementacja jest podobna do tej w linku Qaz w komentarzu do odpowiedzi Märta Bakhoffa.
2:00 po południu,

1

Wypróbowuję różne rozwiązania z tego wątku i na koniec używam tego:

def egcd(a, b):
    lastremainder, remainder = abs(a), abs(b)
    x, lastx, y, lasty = 0, 1, 1, 0
    while remainder:
        lastremainder, (quotient, remainder) = remainder, divmod(lastremainder, remainder)
        x, lastx = lastx - quotient*x, x
        y, lasty = lasty - quotient*y, y
    return lastremainder, lastx * (-1 if a < 0 else 1), lasty * (-1 if b < 0 else 1)


def modinv(a, m):
    g, x, y = self.egcd(a, m)
    if g != 1:
        raise ValueError('modinv for {} does not exist'.format(a))
    return x % m

Modular_inverse w Pythonie


1
ten kod jest nieprawidłowy. returnw egcd jest wcięty w niewłaściwy sposób
ph4r05

0

Cóż, nie mam funkcji w Pythonie, ale mam funkcję w C, którą można łatwo przekonwertować na Pythona, w poniższej funkcji c jest używany rozszerzony algorytm euklidesowy do obliczania mod odwrotnego.

int imod(int a,int n){
int c,i=1;
while(1){
    c = n * i + 1;
    if(c%a==0){
        c = c/a;
        break;
    }
    i++;
}
return c;}

Funkcja Pythona

def imod(a,n):
  i=1
  while True:
    c = n * i + 1;
    if(c%a==0):
      c = c/a
      break;
    i = i+1

  return c

Odniesienie do powyższej funkcji C pochodzi z poniższego linku programu C w celu znalezienia modularnej multiplikatywnej odwrotności dwóch względnie pierwszych liczb


0

z kodu źródłowego implementacji cpythona :

def invmod(a, n):
    b, c = 1, 0
    while n:
        q, r = divmod(a, n)
        a, b, c, n = n, c, b - q*c, r
    # at this point a is the gcd of the original inputs
    if a == 1:
        return b
    raise ValueError("Not invertible")

zgodnie z komentarzem nad tym kodem, może zwracać małe wartości ujemne, więc możesz potencjalnie sprawdzić, czy jest ujemne i dodać n, gdy jest ujemne, przed zwróceniem b.


„więc możesz potencjalnie sprawdzić, czy jest ujemne i dodać n, gdy jest ujemne, przed zwróceniem b”. Niestety n wynosi w tym momencie 0. (Musiałbyś zapisać i użyć oryginalnej wartości n.)
Don Hatch

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.