Rückstandszahlensystem


26

In Anbetracht der zahlreichen Herausforderungen hielt ich dies für interessant.

In dieser Herausforderung werden wir das Residue Number System (RNS) verwenden, um Additionen, Subtraktionen und Multiplikationen mit großen ganzen Zahlen durchzuführen.

Was ist der RNS

Das RNS ist eine von vielen Möglichkeiten, die Menschen entwickelt haben, um Ganzzahlen zu identifizieren. In diesem System werden Zahlen durch eine Folge von Resten dargestellt (die die Ergebnisse nach einer Moduloperation sind (dh der Rest nach der Ganzzahldivision)). In diesem System hat jede Ganzzahl viele Darstellungen. Um die Dinge einfach zu halten, werden wir die Dinge so einschränken, dass jede ganze Zahl eindeutig dargestellt wird. Ich denke, es ist einfacher, mit einem konkreten Beispiel zu beschreiben, was passiert.

Schauen wir uns die ersten drei Primzahlen an: 2, 3, 5. Im RNS-System können wir diese drei Zahlen verwenden, um mit Resten eindeutig jede Zahl darzustellen, die kleiner als 2 * 3 * 5 = 30 ist. Nimm 21:

21 ist kleiner als 30, daher können wir es mit den Ergebnissen nach der Modifikation durch 2, 3 und 5 darstellen. (Dh der Rest nach der Ganzzahldivision durch 2, 3 und 5)

Wir würden 21 mit der folgenden Folge von ganzen Zahlen identifizieren:

21 ~ {21 mod 2, 21 mod 3, 21 mod 5} = {1, 0, 1}

Und so würden wir in unserem RNS-System anstelle von "21" {1,0,1} verwenden.

Im Allgemeinen stellen wir n mit einer ganzen Zahl n als { n mod 2, ..., n mod p_k } dar, wobei p_k die kleinste Primzahl ist, sodass n kleiner als das Produkt aller Primzahlen ist, die kleiner oder gleich p_k sind .

Ein anderes Beispiel, sagen wir, wir haben 3412. Wir müssen hier 2,3,5,7,11,13 verwenden, weil 2*3*5*7*11*13=30030wohingegen, 2*3*5*7*11=2310was zu klein ist.

3412 ~ {3412 mod 2, 3412 mod 3, 3412, mod 5, ..., 3412 mod 13} = {0, 1, 2, 3, 2, 6}

Sie bemerken, dass wir mit diesem System relativ schmerzlos sehr große Zahlen darstellen können. Mit {1, 2, 3, 4, 5, 6, 7, 8, ...} Resten können wir Zahlen bis zu {2, 6, 30, 210, 2310, 30030, 510510, 9699690 ...} darstellen. beziehungsweise. ( Hier ist die Serie )

Unsere Aufgabe

Wir werden diese Reste verwenden, um +, - und * für große Zahlen durchzuführen. Ich werde diese Prozesse im Folgenden beschreiben. Vorerst sind hier die Eingangs- und Ausgangsspezifikationen.

Eingang

Sie erhalten zwei (möglicherweise sehr große) Zahlen über ein Standard- oder Funktionsargument. Sie werden als Zeichenfolgen mit 10 Stellen zur Basis angegeben.

Um das Problem weiter zu skizzieren, bezeichnen wir die erste Eingabe nals die zweite m. Angenommen, n> m> = 0 .

Sie erhalten auch +oder -oder *, um die auszuführende Operation anzugeben.

Ausgabe

Sei x eine ganze Zahl. Wir werden mit [ x ] auf die oben beschriebene RNS-Darstellung von x verweisen .

Sie sollen ausgeben [n] <operator> [m] = [result]

So führen Sie die Vorgänge in RNS durch

Diese Operationen sind relativ einfach. Wenn Sie zwei Zahlen in der RNS-Notation angeben, um sie zu addieren, zu subtrahieren oder zu multiplizieren, führen Sie einfach die angegebenen Operationen komponentenweise aus und nehmen Sie dann den Modul.

dh

{1, 2, 3} + {1, 1, 4} = {(1 + 1) mod 2, (2 + 1) mod 3, (3 + 4) mod 5} = {0, 0, 2}

Beachten Sie, dass Sie bei der Ausführung von Vorgängen die "kürzere" Anzahl so verlängern müssen, dass sie dieselbe Anzahl von Resten enthält, wenn die Anzahl von Resten, die zur Darstellung von zwei verschiedenen Nummern verwendet wird, nicht identisch ist. Dies folgt dem gleichen Vorgang. Ein Beispiel finden Sie in den Testfällen.

Gleiches gilt, wenn das Ergebnis mehr Rückstände erfordert als jede Eingabe. Dann müssen beide Eingänge "erweitert" werden.

Wichtige Details

  • Wir werden es hier mit großen Zahlen zu tun haben, aber nicht mit willkürlich großen. Wir sind für Zahlen bis zum Produkt der ersten 100 Primzahlen verantwortlich (siehe unten). Zu diesem Zweck erhalten Sie die ersten 100 Primzahlen kostenlos (keine Bytekosten) . Sie können sie in ein Array mit dem Namen poder etwas Idiomatisches für Ihre Sprache einfügen und dann die Anzahl der Bytes, die zum Einleiten dieses Arrays verwendet wurden, von Ihrer endgültigen Summe abziehen. Dies bedeutet natürlich, dass sie fest codiert sein können oder dass Sie ein eingebautes verwenden können, um sie zu generieren.

  • Wenn dies aus irgendeinem Grund die in Ihrer Sprache verwendete Standard-Ganzzahldarstellung ist. Das ist gut.

  • Sie dürfen keinen Arbitrary Precision Integer-Typ verwenden, es sei denn, dies ist die Standardeinstellung Ihrer Sprache. Wenn dies die Standardeinstellung ist, können Sie sie möglicherweise nicht zum Speichern von Ganzzahlen verwenden, die normalerweise nicht in 64 Bit passen.

  • Es ist klar, dass jede Ganzzahl immer mit den geringstmöglichen Resten dargestellt wird. Dies gilt sowohl für die Eingabe als auch für die Ausgabe.

  • Ich denke, die anderen Spezifikationen sollten dies verhindern, aber redundant sein: Sie können die angegebene Operation nicht an den Eingängen ausführen und dann und dann alles in RNS ändern und dann ausgeben. Sie müssen die Eingaben in RNS ändern und dann die Vorgänge ausführen, um die Ausgabe zu erzeugen.

Testfälle

  1. Eingang:

n = 10
m = 4
+

Ausgabe:

{ 0, 1, 0 } + { 0, 1 } = { 0, 2, 4 }

Erläuterung:

Ändern Sie zunächst jede Zahl in ihre RNS-Darstellung, wie oben beschrieben:

10 ~ {0,1,0}und 4 ~ {0,1}. Beachten Sie, dass, wenn Sie komponentenweise hinzufügen möchten, dies 10mehr Komponenten als enthält 4. Deshalb müssen wir die kürzere Zahl "verlängern". Also werden wir kurz schreiben 4 ~ {0,1} --> {0,1, 4 mod 5} = {0,1,4}. Nun fahren wir mit der Addition fort und nehmen dann den Modul.

  1. Eingang
n=28
m=18
+

Ausgabe:

 [ 0, 1, 3 ] + [0, 0, 3 ] = [ 0, 1, 1, 4 ]
  1. Eingabe (ich zerdrücke mein Gesicht auf der Tastatur)
n=1231725471982371298419823012819231982571923
m=1288488183
*

Ausgabe (zur besseren Lesbarkeit in separate Zeilen unterteilt):

[1, 2, 3, 6, 2, 10, 2, 1, 12, 16, 7, 15, 34, 29, 31, 5, 55, 32, 66, 61, 3, 76, 52, 14, 65, 44, 99, 57 ] 
* 
[1, 0, 3, 3, 4, 8, 9, 10, 8, 0 ] 
= 
[1, 0, 4, 4, 8, 2, 1, 10, 4, 0, 17, 7, 27, 21, 44, 51, 56, 9, 6, 9, 12, 0, 52, 36, 43, 68, 99, 24, 96, 39, 96, 66, 125] 

nbenötigt 28 Primzahlen. mbenötigt 10. n*mbenötigt 33.

  1. Eingang
n=8709668761379269784034173446876636639594408083936553641753483991897255703964943107588335040121154680170867105541177741204814011615930342030904704147856733048115934632145172739949220591246493529224396454328521288726490
m=1699412683745170450115957274739962577420086093042490863793456500767137147999161679589295549397604032154933975242548831536518655879433595016
-

Ausgabe:

[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 509]
-
[0, 2, 1, 6, 1, 12, 11, 18, 14, 28, 21, 36, 37, 42, 16, 52, 41, 60, 16, 70, 49, 78, 80, 88, 49, 100, 13, 106, 4, 112, 68, 130, 36, 138, 37, 150, 0, 162, 8, 172, 163, 180, 18, 192, 129, 198, 135, 222, 78, 228, 90, 238, 57, 250, 36, 262, 87, 270, 206, 280, 193, 292, 253, 310, 224, 316, 57, 336, 48, 348]
=
[0, 1, 4, 1, 10, 1, 6, 1, 9, 1, 10, 1, 4, 1, 31, 1, 18, 1, 51, 1, 24, 1, 3, 1, 48, 1, 90, 1, 105, 1, 59, 1, 101, 1, 112, 1, 0, 1, 159, 1, 16, 1, 173, 1, 68, 1, 76, 1, 149, 1, 143, 1, 184, 1, 221, 1, 182, 1, 71, 1, 90, 1, 54, 1, 89, 1, 274, 1, 299, 1, 266, 1, 228, 1, 340, 1, 170, 1, 107, 1, 340, 1, 88, 1, 157, 1, 143, 1, 22, 1, 22, 1, 58, 1, 296, 1, 371, 1, 140]

nverwendet 100 Primzahlen. mverwendet 70 Primzahlen. n-mverwendet 99 Primzahlen.

Ich habe diese anhand der ChineseRemintegrierten Implementierung des chinesischen Resttheorems für GAP überprüft (das im Grunde genommen RNS-Zahlen verwendet und sie in Basis-10-Ganzzahlen ändert). Ich glaube, dass sie richtig sind. Wenn etwas faul zu sein scheint, lassen Sie es mich bitte wissen.


Für die, die sich interessieren, ist das Produkt der ersten 100 Primzahlen:

471193079990618495316248783476026042202057477340967552018863483961641533584503
422120528925670554468197243910409777715799180438028421831503871944494399049257
9030720635990538452312528339864352999310398481791730017201031090

Diese Zahl ist um 1 größer als die maximale Zahl, die wir mit dem angegebenen System darstellen können (und die Beschränkung auf 100 Primzahlen).

Etwas verwandt


Ich denke, dass die Durchführung der Operation alles andere als schwierig ist, weshalb mir diese Herausforderung fremd ist.
njpipeorgan

@njpipeorgan Ich stimme zu, die Operation wird einfach (a,b,o)=>a.map((v,i)=>eval(v+o+b[i]))in ES6 ausgeführt. Ich denke, der schwierigste Teil ist es wahrscheinlich, die Anzahl der Primzahlen zu finden, die zur Darstellung des Ergebnisses benötigt werden, ohne eine Arithmetik mit willkürlicher Genauigkeit zu verwenden, obwohl die anschließende Konvertierung in RNS nicht gerade trivial ist.
Neil

Kann ich die Eingabe so haben ( 1234,1234,+)?
Clismique

@derpfacePython ja Funktionen sind auch akzeptabel
Liam

"führe einfach die gegebenen Operationen komponentenweise aus" - woher kommen dann zusätzliche Komponenten in der Ausgabe?
smls

Antworten:


6

SPALT

Hintergrund: Ich gebe zu, als ich diese Frage vor so vielen Monaten erstellt habe, hatte ich keine Methode, um den schwierigen Teil dieser Frage zu lösen: die richtige Anzahl der zu verwendenden Primzahlen zu bestimmen. Wir haben eine Menge sehr intelligenter Leute auf dieser Seite, und ich hatte wirklich erwartet, dass jemand einen Weg finden würde, dies ziemlich schnell zu tun. Da dies jedoch nicht geschah, war ich mir nicht einmal sicher, ob es wirklich möglich war, dieses Problem zu lösen. Also musste ich mir die Zeit nehmen, um eine Methode zu entwickeln. Ich glaube, dass das, was ich getan habe, nicht gegen die Regeln dieser Herausforderung verstößt. Natürlich würde ich es begrüßen, wenn dies überprüft würde.

Ich bedaure auch die Wahl von weil die Lösungen etwas ausführlicher sind, als es normalerweise für das Tag-Format passt. Um die Site-Regeln zu befolgen, finden Sie unten in diesem Beitrag eine "Golf" -Version meiner Lösung.


Code

### The first 100 primes;
primes := Primes{[1..100]};

### In many of the functions below, the 'string' variable is a string of digits
###


### Returns the 'index' digit of 'string' as an integer
GetValueAsInt := function(string, index) 
    return IntChar(string[index]) - 48;
end;

### Used in the 'modulus' function. See that comment for more information. 
### Calculates the contribution to the modulus of a digit 'digit' in the 10^power place.
### 'integer' is the modulus
digit_contribution := function(digit, integer, power)
    local result, i;
    result := 1;
    for i in [0..power-1] do
        result := ( result * (10 mod integer) ) mod integer;
    od;
    result := (result * (digit mod integer) ) mod integer;
    return result;
end;

### This modulus function is used to calculate the modulus of large numbers without storing them
##### as large numbers.
### It does so by breaking them into digits, and calculating the contribution of each digit.
### e.g. 1234 mod 5 = (1000 mod 5)(1 mod 5) + (200 mod 5)(2 mod 5) + (10 mod 5)(3 mod 5) + (4 mod 5)
### It actually mods after every calculation to ensure that we never get a number larger
##### than the modulus ('integer') squared, which will never be even close to 10^64-1
modulus := function(string, integer)
    local i, result, digit, len;
    len := Length(string);
    result := 0;
    for i in [1..len] do
        digit :=  IntChar(string[i]) -48;
        result := ( result + digit_contribution(digit, integer, len-i) )  mod integer;
    od;
    return result;
end;

### This returns the product of the first i-1 primes (mod j). It must not (and does not)
##### ever store an integer larger than 2^64-1
phi_i := function(i,j)
    local index, result;
    result := 1;
    for index in [1..i-1] do
        result := ( result * primes[index] ) mod primes[j];
    od;
    return result;
end;

### Calculates the first residues of 'string' mod the first 100 primes
get_residues := function(string) 
    local p, result;
    result := [];
    for p in primes do
        Add( result, modulus(string, p) );  
    od; 
    return result;
end;

### Gets the ith element in the partial_chinese array, given the previous elements
### See the explanation section and partial_chinese function for more info
get_partial_i := function( i, residues, previous_array )
    local index, result;
    result := residues[i];
    for index in [1..Length(previous_array)] do
        result := ( result - previous_array[index]*phi_i(index,i) ) mod primes[i]; 
    od;     
    result := ( result / phi_i(i,i) ) mod primes[i];
    return result;
end;

### returns an array such that the sum of prod_primes(i)*array[i] is equal to the integer value
##### that is represented by the residues. (It basically just does the CRT without
##### actually summing everything.) prod_primes(i) is the product of the first i-1 primes 
### See the explanation for a bit more info
### This is what allows us to determine the minimal number of primes to represent a RNS number
partial_chinese := function( string )
    local array, i, residues;
    residues := get_residues(string);
    array := [];        
    for i in [1 .. Length(primes)] do
        Add( array, get_partial_i( i, residues, array ) );
    od;
    return array;   
end;

### Same as partial_chinese but takes input in a different form.
partial_chinese_from_residues := function(residues)
    local array, i;
    array := [];        
    for i in [1 .. Length(primes)] do
        Add( array, get_partial_i( i, residues, array ) );
    od;
    return array;
end;

### gives you the number of primes needed to represent an integer. Basically asks how 
##### many trailing zeros there are in the chinese array.
get_size := function(string)
    local array, i, len, result;
    array := partial_chinese(string);
    len := Length(array);
    for i in [0..len-1] do
        if  not (array[len-i] = 0) then
            return len -i;
        fi; 
    od; 
    Print("ERROR: get_size().\n");
    return 0;
end;

### Same as above but with different input format
get_size_from_residues := function(residues)
    local array, i, len, result;
    array := partial_chinese_from_residues(residues);
    len := Length(array);
    for i in [0..len-1] do
        if  not (array[len-i] = 0) then
            return len -i;
        fi; 
    od; 
    Print("ERROR: get_size().\n");
    return 0;
end;

### the actual function. inputs are all strings
f := function(in1, in2, opperation)
    local residues_1, residues_2, residues_result, i;
    residues_1 := get_residues(in1);
    residues_2 := get_residues(in2);
    residues_result := [];
    if opperation = "+" then
        for i in [1..Length(primes)] do
            Add( residues_result, ( residues_1[i] + residues_2[i] ) mod primes[i]);
        od;     
    elif opperation = "*" then
        for i in [1..Length(primes)] do
            Add( residues_result, ( residues_1[i] * residues_2[i] ) mod primes[i]);
        od;     
    elif opperation = "-" then
        for i in [1..Length(primes)] do
            Add( residues_result, ( residues_1[i] - residues_2[i] ) mod primes[i]);
        od;     
    fi;
    Print(residues_1{[1..get_size(in1)]}, " ", opperation, " ", residues_2{[1..get_size(in2)]}, " = ", residues_result{[1..get_size_from_residues(residues_result)]} );
end;

Erläuterung:

Zu Beginn berechnen wir alle 100 der Rückstände für beide Eingänge. Wir machen das mit der modulusFunktion im Code. Ich habe versucht, vorsichtig zu sein, damit wir die eingebaute modFunktion nach jedem Schritt verwenden. Dies stellt sicher, dass wir niemals eine Zahl haben, die größer als 540^2, also 1 kleiner als das 100. Quadratzoll ist.

Nachdem wir alle Reste haben, können wir die angegebene Operation und modjeden Eintrag erneut ausführen . Jetzt haben wir einen eindeutigen Bezeichner für das Ergebnis, aber wir müssen die minimale Anzahl von Einträgen bestimmen, die wir verwenden müssen, um das Ergebnis und jede der Eingaben darzustellen.

Herauszufinden, wie viele Rückstände wir tatsächlich benötigen, ist der mit Abstand schwierigste Teil dieses Problems. Um dies festzustellen, führen wir die meisten Schritte des Chinese Remainder Theorem (CRT) aus. Wir müssen natürlich Änderungen vornehmen, damit wir nicht zu große Zahlen erhalten.

Sei prod(i)die Summe der ersten i-1Primzahlen. Beispielsweise,

prod(1) = 1
prod(2) = 2
prod(3) = 6
prod(4) = 30
etc

Sei Xeine ganze Zahl. Lassen Sie {r_i}die Reste sein X, das heißt

r_i = X mod p_i

Wo p_iist der ith prime. Dies ist für 1<i<=100in unserem Fall.

Jetzt werden wir die CRT verwenden, um eine Sequenz zu finden {u_i}, bei der die Summe ivon prod(i) * u_igleich ist X. Beachten Sie, dass jedes u_iauch technisch ein Rückstand ist, wie u_i < p_i. Außerdem, wenn X < prod(i)dann u_i = 0. Dies ist von entscheidender Bedeutung. Dies bedeutet, dass wir durch Untersuchen der nachfolgenden Nullen bestimmen können, wie viele der Reste r_iwir tatsächlich Xim RNS darstellen müssen.

Wenn Sie einige Sequenzen von untersuchen möchten u_i, gibt die partial_chineseFunktion die u_iSequenz zurück.

Indem ich mit der CRT herumgespielt habe, konnte ich eine rekursive Formel für die u_iWerte finden und das Problem lösen, wie viele Rückstände wir benötigen.

Die Formel lautet:

u_i = [ r_i - SUM ] / prod(i)       (mod p_i)

Wo SUMist die Summe j in [1,i)von u_j * prod(i).

Natürlich prod(i)kann in manchen Fällen nicht wirklich gerechnet werden, weil es zu groß ist. Zu diesem Zweck habe ich die phi_iFunktion verwendet. Diese Funktion kehrt zurück prod(j) (mod p_i). Es ist modbei jedem Schritt so, dass wir nie etwas berechnen, das zu groß ist.

Wenn Sie neugierig sind, woher diese Formel stammt, empfehle ich Ihnen, einige Beispiele für CRTs zu verwenden, die auf der Wikipedia-Seite zu finden sind .

Schließlich berechnen wir für jede Eingabe sowie für unsere Ausgabe die u_iSequenz und bestimmen dann die nachfolgenden Nullen. Dann werfen wir so viele r_iaus dem Ende der Restsequenzen heraus.


"Golfed" Code, 2621 Bytes

primes:=Primes{[1..100]};GetValueAsInt:=function(string,index)return IntChar(string[index])-48;end;digit_contribution := function(digit, integer, power)local result, i;result:=1;for i in [0..power-1] do result := ( result * (10 mod integer) ) mod integer;od;result:=(result*(digit mod integer) ) mod integer;return result;end;modulus:=function(string, integer)local i,result,digit,len;len:=Length(string);result:=0;for i in [1..len] do digit:= IntChar(string[i])-48;result:=(result+digit_contribution(digit,integer,len-i)) mod integer;od;return result;end;phi_i:=function(i,j)local index,result;result:=1;for index in [1..i-1] do result:=(result*primes[index] ) mod primes[j];od;return result;end;get_residues:=function(string) local p,result;result:=[];for p in primes do Add(result,modulus(string,p));od;return result;end;get_partial_i:=function(i,residues,previous_array)local index,result;result:=residues[i];for index in [1..Length(previous_array)] do result:=(result-previous_array[index]*phi_i(index,i) ) mod primes[i];od;result:=(result/phi_i(i,i)) mod primes[i];return result;end;partial_chinese:=function(string)local array,i,residues;residues:=get_residues(string);array:=[];for i in [1 .. Length(primes)] do Add(array,get_partial_i(i,residues,array));od;return array;end;partial_chinese_from_residues:=function(residues)local array,i;array:=[];for i in [1..Length(primes)] do Add(array,get_partial_i(i,residues,array));od;return array;end;get_size:=function(string)local array,i,len,result;array:=partial_chinese(string);len:=Length(array);for i in [0..len-1] do if not (array[len-i] = 0) then return len-i;fi;od;Print("ERROR: get_size().\n");return 0;end;get_size_from_residues:=function(residues)local array,i,len,result;array:=partial_chinese_from_residues(residues);len:=Length(array);for i in [0..len-1] do if not (array[len-i]=0) then return len-i;fi;od;Print("ERROR: get_size().\n");return 0;end;f:=function(in1,in2,opperation)local residues_1,residues_2,residues_result,i;residues_1:=get_residues(in1);residues_2:=get_residues(in2);residues_result:=[];if opperation = "+" then for i in [1..Length(primes)] do Add(residues_result,(residues_1[i]+residues_2[i] ) mod primes[i]);od;elif opperation = "*" then for i in [1..Length(primes)] do Add(residues_result,(residues_1[i]*residues_2[i])mod primes[i]);od;elif opperation = "-" then for i in [1..Length(primes)] do Add(residues_result,(residues_1[i]-residues_2[i]) mod primes[i]);od;fi;Print(residues_1{[1..get_size(in1)]}, " ", opperation, " ", residues_2{[1..get_size(in2)]}, " = ", residues_result{[1..get_size_from_residues(residues_result)]} );end;

Ich bin verwirrt, weil der reguläre RNS die Dimensionen nicht nach Bedarf ändert, aber verbiegen Sie die Regeln nicht, indem Sie die erweiterte Zahl von 100 Resten aus der Eingabe berechnen, anstatt nur die benötigten Dimensionen und dann Vorgänge ausführen? "Ändern Sie zunächst jede Nummer in ihre RNS-Darstellung, wie oben beschrieben " bedeutet für mich, dass die 'RNS'-Nummer nur die Reste enthalten sollte, die benötigt werden, bevor etwas unternommen wird.
Linus

Entschuldigung @Linus, ich dachte, ich habe bereits darauf geantwortet. Ich stimme Ihnen zu, aber ich denke, dass die erforderliche Änderung (die ich vornehmen werde) relativ trivial ist. Aus meiner Sicht muss ich nur die Restlängen der Eingaben berechnen, bevor ich die Operation durchführe. Die Verwendung aller 100 Primzahlen für alle drei Zahlen erhöht lediglich die Tatsache, dass alle Zahlen oben begrenzt sind
Liam

@Linus und bei der Beantwortung Ihrer ersten Frage würden normalerweise alle Zahlen die gleiche Anzahl von Resten verwenden. Das würde die Frage viel einfacher machen
Liam

2

Mathematica, nicht golfen

rns[d_,l_]:=Table[Reap[
    FoldPairList[Sow@QuotientRemainder[10#+#2,Prime@i]&,0,d]
  ][[2,1,-1,2]],{i,l}];
plus[a_,b_]:=Mod[a+b,Prime@Range@Length@a];
subtract[a_,b_]:=Mod[a-b,Prime@Range@Length@a];
times[a_,b_]:=Mod[a b,Prime@Range@Length@a];
mag[f_]:=LengthWhile[FoldList[#/#2&,f,Prime@Range@100],#>1.1&];
ext[m_,n_,i_]:=Fold[Mod[1##,Prime@i]&,m,Prime@Range@n];
multi[e_,p_,t_]:=Tr@Position[Mod[e Range@p,p],p-t];
appx[d_] := N@FromDigits[{d~Take~UpTo[6], Length@d}]
  • Die Funktion rns[d_,l_]konvertiert eine Ganzzahl dzur Basis 10 in eine RNS-Ganzzahl der Länge l.

  • Funktion plus/ times/ subtractaddiere / multipliziere / subtrahiere eine RNS-Ganzzahl von / zu einer anderen, die beide die gleiche Länge haben.

  • Die Funktion mag[f_]schätzt die ungefähre Größe der Gleitkommazahl fin Bezug auf die Untergrenze der Länge ihrer RNS-Darstellung.

  • Die Funktion ermittelt ext[m_,n_,i_]den Rest aus der Aufteilung des Produkts von mund Prime[Range@n]nach Prime[i].

  • Die Funktion multi[e_,p_,t_]gibt den kleinsten Multiplikator, der dies merfülltDivisible[m*e+t,p]

  • Die Funktion appx[d_]nimmt die ersten 6Stellen einer Dezimalzahl und gibt den ungefähren Gleitkommawert an.


Mit Hilfe der obigen Funktionen können wir nun ein kniffliges Problem lösen - die Länge des Ergebnisses bestimmen .

Zunächst muss klargestellt werden, dass es keine leichte Aufgabe ist, die RNS-Länge einer Ganzzahl zu bestimmen. Für kleine ganze Zahlen können wir sie direkt mit dem Produkt von Primzahlen vergleichen. Da es für sehr große ganze Zahlen verboten ist, das Produkt von Primzahlen unendlich genau zu berechnen, funktioniert ein solcher Vergleich nicht mehr.

Wenn beispielsweise das Produkt von prime 1to 30ist 3.16*10^46, kann die RNS-Länge von ganzen Zahlen 3.16*10^46möglicherweise 29oder sein 30. Die Funktion maggibt 29für diese ganzen Zahlen als Referenz, die zeigen , dass beide 29und 30möglich sind.

Sobald wir die Größe kennen, stellen wir einfach die ganze Zahl direkt entsprechend dieser Größe dar, in der Hoffnung, ihre wahre Länge zu berechnen. Der Trick dabei ist, der ursprünglichen Nummer einige neue Nummern hinzuzufügen und ihre RNS-Darstellung zu ändern, bis die Darstellung vollständig Null ist.

Zum Beispiel mag[211.]ist 4, und seine Längendarstellung 4ist {1, 1, 1, 1}.

step 1:   {1,1,1,1} -> {0,2,2,2}  by adding  (1) * 1 = 1
step 2:   {0,2,2,2} -> {0,0,1,6}  by adding  (2) * 2 = 4
step 3:   {0,0,1,6} -> {0,0,0,2}  by adding  (2*3) * 4 = 24
step 4:   {0,0,0,2} -> {0,0,0,0}  by adding  (2*3*5) * 6 = 180
step 5:   calculate 211 + (1 + 4 + 24 + 180) ~ 420

Indem wir eine Zahl hinzufügen, erhöhen wir uns 211auf die kleinste Zahl, die durch 210( 2*3*5*7) teilbar ist . Und jetzt kommen wir zu dem Schluss, dass die ursprüngliche Zahl größer ist als 210, da 420"ungefähr" das Zweifache von 210. Es ist nicht schwer vorstellbar, dass 209die endgültige Zahl "ungefähr" ist , wenn wir von hier ausgehen 210.

Die Funktion verwendet length[f_,n_]den Gleitkommawert fzum Schätzen der Größe und korrigiert ihn auf der Grundlage seiner RNS-Darstellung n.

length[f_,n_]:=With[{g=mag@f},
    g+If[#==0,1,Round[(#+f)/Times@@Prime@Range@g]-1]&[
      FoldList[Times,1.,Prime[Range[g-1]]].
      FoldPairList[
        Block[{i=#2,m},
          {m=multi[ext[1,i-1,i],Prime@i,Part@##],rnsPlus[#,ext[m,i-1,#]&/@Range[g]]}
        ]&,n,Range[g]]]]

Die Funktion rnsOperation[a_,b_,op_,rnsop_]liefert rnsop[a,b]und opgibt die entsprechenden normalen Operationen an, die verwendet werden, um ungefähre Ergebnisse basierend auf Gleitkommawerten zu erhalten.

rnsOperation[a_,b_,op_,rnsop_]:=Block[{c=op[appx@a,appx@b],m},
    m=mag[c];m=length[c,rnsop[rns[a,m],rns[b,m]]];rnsop[rns[a,m],rns[b,m]]]

Beispiel

rnsOperation[
    IntegerDigits@1231725471982371298419823012819231982571923,
    IntegerDigits@1288488183,
    Times, times]
(* {1,0,4,4,8,2,1,10,4,0,17,7,27,21,44,51,56,9,6,9,12,0,52,36,43,68,99,24,96,39,96,66,125} *)

1
Leider setzen die in unserer Hilfe aufgeführten Regeln voraus, dass alle Einsendungen ein ernstzunehmender Anwärter auf die verwendeten Gewinnkriterien sind. Für einen Code-Golf-Wettbewerb bedeutet dies, dass alle Einsendungen golfen werden müssen.
Dennis

@ Tennis Ich kenne diese Regel. Aber auch ohne Golf denke ich, dass dieses Problem schwierig und komplex genug ist, so dass es mein Ziel ist, dieses Problem zu lösen, anstatt Golf zu spielen.
Njpipeorgan

Das ist vielleicht nicht Golf, aber verdammt kurz im Vergleich zu meinem Java-Programm: P, obwohl mein Programm wahrscheinlich viel schneller ist.
Hoffentlich

1
Ich habe das Gefühl, dass Sie in der Lage sind, Golf zu spielen
Rohan Jhunjhunwala

2

Python 3 , 435 Bytes

Diese Herausforderung steht schon seit einiger Zeit auf meiner Bucket-Liste, aber erst seit kurzem gilt Folgendes: a) Ich habe Zeit und Aufmerksamkeit darauf verwendet, tatsächlich eine Antwort zu versuchen. und b) tatsächlich meine Idee getestet, die Größe der Zahlen (und damit die Anzahl der Primzahlen durch Vergleichen mit der Größe der Primzahlen) unter Verwendung einer unheiligen Kombination von Logarithmen und dem chinesischen Restsatz zu berechnen. Leider bedeutete die Arbeit mit Logarithmen, während versucht wurde, die Anzahl der large_primorial + 3erforderlichen Primzahlen zu bestimmen , dass ich nach Wegen suchen musste, um Gleitkommaprobleme zu umgehen.

Und so ist dies eine Portierung von Liams Antwort .

Probieren Sie es online!

from functools import reduce as R
G=range
d=lambda s:[R(lambda z,c:(z*10+int(c))%q,s,0)for q in p]
h=lambda j,i:R(lambda z,q:z*q%p[i],p[:j],1)
def s(r):
 a=[];z=99
 for i in G(100):
  P=p[i];u=r[i]
  for j in G(len(a)):u=(u-a[j]*h(j,i))%P
  for k in G(1,P):
   if h(i,i)*k%P<2:break
  a+=u*k%P,
 while(a[z]<1)*z:z-=1
 return r[:z+1]
def f(a,b,n):u=d(a);v=d(b);print(s(u),n,s(v),'=',s([eval(str(u[i])+n+str(v[i]))%p[i]for i in G(100)]))

Erläuterung

Beim Versuch, Liams Antwort zu portieren, stellte ich persönlich fest, dass einige der gegebenen Erklärungen verwirrend formuliert waren. Dies ist also mein Versuch, seinen Algorithmus zu erklären.

Zunächst erhalten wir die Rückstände von nund m.

res1 = get_residues(n)
res2 = get_residues(m)

Dabei werden alle Ziffern der Eingabezeichenfolgen in Zahlen umgewandelt, die für jede unserer Primzahlen modulo sind, z. B. für 28, die wir haben würden [(20 + 8) mod 2, (20 + 8) mod 3, (20 + 8) mod 5, etc]

def get_residues(string):
    result = []
    for p in primes:
        result.append(reduce(lambda z, c:(z*10+int(c)) % p, string, 0))

Dann addieren, multiplizieren oder subtrahieren wir die Reste paarweise mit eval()

result = []
for i in range(len(primes)):
    result.append((eval(str(res1[i]) + op + str(res2[i])) % primes[i])

Dann erhalten wir die Größe unserer Reste, dh die minimale Anzahl von Primzahlen, die wir benötigen.

size1 = get_size(res1)
size2 = get_size(res2)
size3 = get_size(result)

Das Erhalten der Größen ist der schwierigste und codeintensivste Teil. Wir benutzen die partial_chineseFunktion, mit der wir unsere Reihenfolge u_ibestimmen können. Mehr über u_iin einem Augenblick.

def get_size(residues):
    array = partial_chinese(residues)
    size = len(residues)-1
    while array[size] == 0 and size:
        size -= 1
    return size+1  # to prevent off-by-one errors from 0-indexing

Die Sequenz u_iwird bestimmt , indem jeder Rest berechnet r_i, Subtrahieren der Summe u_j * primorial(j) for j in [1, i), und dann dividingdurch primorial(i), die alle modulo primes[i]. Das heißt, u_i = (r_i - SUM) / primorial(i). Mehr über unsere Ur- und Teilungsfunktionen in einem Moment.

def partial_chinese(residues):
    array = []
    for i in range(len(primes)):
        array.append(get_partial_i(i, residues, array))
    return array

def get_partial_i(i, residues, previous_array):
    result = residues[i]
    for j in range(len(previous_array)):
        result = (result - previous_array[j] * phi_i(j, i)) % primes[i]
    result = result * inverse(phi_i(i, i), primes[i]) % primes[i]
    return result

phi_i(j, i)berechnet primorial(j) mod primes[i]. Division modulo jeder Primzahl pleicht durch Überprüfung für multiplikativen Umkehrungen manuell implementiert ist, wie wir sicher sein können , dass eine mögliche u_iist 0 <= u_i < pgarantiert coprime p zu sein , und so ist eine multiplikative Inverse garantiert.

def phi_i(j, i):
    return reduce(lambda z, q: z * q % primes[i], primes[:j], 1)

def inverse(n, p):
    for i in range(1, p):
        if n * i % p == 1:
            return i

Nach alledem drucken wir unseren String und wir sind fertig.

print(res1[:size1], op, res2[:size2], "=", result[:size3])

Was kommt als nächstes

Die Implementierung hat Spaß gemacht. Ich möchte immer noch sehen, ob ich Logarithmen in einer anderen Antwort verwenden kann. Und ich möchte diesen Code oder so etwas in eine funktionierende Golfsprache wie APL oder Jelly implementieren. Alle Vorschläge und Korrekturen zum Golfen sind willkommen!

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.