Dans la foulée de nombreux défis, j'ai pensé que celui-ci pouvait être intéressant.
Dans ce défi, nous utiliserons le système de numération des résidus (RNS) pour effectuer l'addition, la soustraction et la multiplication sur de grands nombres entiers.
Qu'est-ce que le RNS
Le RNS est l'une des nombreuses façons que les gens ont développées pour identifier les nombres entiers. Dans ce système, les nombres sont représentés par une séquence de résidus (qui sont les résultats après une opération de module (c'est-à-dire le reste après la division entière)). Dans ce système, chaque entier a de nombreuses représentations. Pour garder les choses simples, nous allons limiter les choses afin que chaque entier soit représenté de manière unique. Je pense qu'il est plus facile de décrire ce qui se passe avec un exemple concret.
Examinons les trois premiers nombres premiers: 2, 3, 5. Dans le système RNS, nous pouvons utiliser ces trois nombres pour représenter de manière unique tout nombre inférieur à 2 * 3 * 5 = 30 en utilisant des résidus. Prenez 21:
21 est inférieur à 30, nous pouvons donc le représenter en utilisant les résultats après modding par 2, 3 et 5. (c'est-à-dire le reste après la division de l'entier par 2, 3 et 5)
Nous identifierions 21 avec la séquence d'entiers suivante:
21 ~ {21 mod 2, 21 mod 3, 21 mod 5} = {1, 0, 1}
Et donc dans notre système RNS, au lieu de "21", nous utiliserions {1,0,1}.
En général, étant donné un entier n , nous représentons n comme { n mod 2, ..., n mod p_k } où p_k est le plus petit premier tel que n est inférieur au produit de tous les nombres premiers inférieurs ou égaux à p_k .
Un autre exemple, disons que nous avons 3412. Nous devons utiliser 2,3,5,7,11,13 ici parce 2*3*5*7*11*13=30030
que, 2*3*5*7*11=2310
ce qui est trop petit.
3412 ~ {3412 mod 2, 3412 mod 3, 3412, mod 5, ..., 3412 mod 13} = {0, 1, 2, 3, 2, 6}
Vous remarquez qu'en utilisant ce système, nous pouvons représenter de très grands nombres relativement facilement. En utilisant {1, 2, 3, 4, 5, 6, 7, 8, ...} résidus, nous pouvons représenter des nombres jusqu'à {2, 6, 30, 210, 2310, 30030, 510510, 9699690 ...} respectivement. ( Voici la série )
Notre mission
Nous utiliserons ces résidus pour effectuer +, - et * sur de grands nombres. Je décrirai ces processus ci-dessous. Pour l'instant, voici les spécifications d'entrée et de sortie.
Contribution
Vous recevrez deux nombres (potentiellement très grands) via un argument stdin ou fonction. Ils seront donnés sous forme de chaînes de base 10 chiffres.
Afin de décrire le problème plus en détail, nous appelons la première entrée n
et la seconde m
. Supposons que n> m> = 0 .
Vous recevrez également +
ou -
ou *
pour indiquer l'opération à effectuer.
Sortie
Soit x un entier. Nous utiliserons [ x ] pour faire référence à la représentation RNS décrite ci-dessus de x .
Vous devez sortir [n] <operator> [m] = [result]
Comment effectuer les opérations dans RNS
Ces opérations sont relativement simples. Étant donné deux nombres en notation RNS, pour les ajouter, les soustraire ou les multiplier, effectuez simplement les opérations données par composant, puis prenez le module.
c'est à dire
{1, 2, 3} + {1, 1, 4} = {(1 + 1) mod 2, (2 + 1) mod 3, (3 + 4) mod 5} = {0, 0, 2}
Notez que si le nombre de résidus utilisé pour représenter deux nombres différents n'est pas le même, lors de l'exécution des opérations, vous devrez étendre le nombre "plus court" afin qu'il ait le même nombre de résidus. Cela suit le même processus. Voir les cas de test pour un exemple.
Il en va de même si le résultat nécessite plus de résidus que l'une ou l'autre entrée. Ensuite, les deux entrées doivent être "étendues".
Détails importants
Nous allons traiter ici de grands nombres, mais pas arbitrairement grands. Nous serons responsables des nombres jusqu'au produit des 100 premiers nombres premiers (voir ci-dessous). À cette fin, vous recevez gratuitement les 100 premiers nombres premiers (sans coût en octets) . Vous pouvez les coller dans un tableau appelé
p
ou quelque chose d'idiomatique dans votre langue, puis soustraire le nombre d'octets utilisés pour lancer ce tableau de votre total final. Cela signifie bien sûr qu'ils peuvent être codés en dur ou que vous pouvez utiliser un intégré pour les générer.Si pour une raison quelconque, c'est la représentation entière par défaut utilisée dans votre langue. C'est bon.
Vous ne pouvez pas utiliser de type entier de précision arbitraire, sauf s'il s'agit de la langue par défaut de votre langue. S'il s'agit de la valeur par défaut, vous ne pouvez pas l'utiliser pour stocker des entiers qui ne tiennent généralement pas en 64 bits.
Pour être clair, chaque entier sera toujours représenté avec le moins de résidus possible. Cela vaut pour l'entrée et la sortie.
Je pense que les autres spécifications devraient empêcher cela, mais pour être redondantes: vous ne pouvez pas effectuer l'opération donnée sur les entrées, puis tout changer en RNS puis en sortie. Vous devez remplacer les entrées par RNS, puis effectuer les opérations pour produire la sortie.
Cas de test
Contribution:
n = 10
m = 4
+
Sortie:
{ 0, 1, 0 } + { 0, 1 } = { 0, 2, 4 }
Explication:
Tout d'abord, changez chaque nombre en sa représentation RNS comme décrit ci-dessus:
10 ~ {0,1,0}
et 4 ~ {0,1}
. Notez que lorsque nous voulons effectuer une addition par composant, cela 10
a plus de composants que 4
. Par conséquent, nous devons "étendre" le nombre le plus court. Nous allons donc écrire brièvement 4 ~ {0,1} --> {0,1, 4 mod 5} = {0,1,4}
. Maintenant, nous procédons à l'addition, puis prenons le module.
- Contribution
n=28
m=18
+
Sortie:
[ 0, 1, 3 ] + [0, 0, 3 ] = [ 0, 1, 1, 4 ]
- Entrée (moi écrasant mon visage sur le clavier)
n=1231725471982371298419823012819231982571923
m=1288488183
*
Sortie (divisée sur des lignes distinctes pour plus de lisibilité):
[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]
n
nécessite 28 nombres premiers. m
nécessite 10. n*m
nécessite 33.
- Contribution
n=8709668761379269784034173446876636639594408083936553641753483991897255703964943107588335040121154680170867105541177741204814011615930342030904704147856733048115934632145172739949220591246493529224396454328521288726490
m=1699412683745170450115957274739962577420086093042490863793456500767137147999161679589295549397604032154933975242548831536518655879433595016
-
Sortie:
[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]
n
utilise 100 nombres premiers. m
utilise 70 nombres premiers. n-m
utilise 99 nombres premiers.
J'ai vérifié ceux-ci en utilisant l' ChineseRem
implémentation intégrée du théorème du reste chinois sur GAP (qui prend essentiellement des nombres RNS et les change en base 10 entiers). Je pense qu'ils ont raison. Si quelque chose semble louche, faites-le moi savoir.
Pour ceux qui s'en soucient, le produit des 100 premiers nombres premiers est:
471193079990618495316248783476026042202057477340967552018863483961641533584503
422120528925670554468197243910409777715799180438028421831503871944494399049257
9030720635990538452312528339864352999310398481791730017201031090
Ce nombre est 1 plus grand que le nombre maximal que nous pouvons représenter en utilisant le système donné (et 100 limitation principale).
(a,b,o)=>a.map((v,i)=>eval(v+o+b[i]))
en ES6 par exemple. Je pense que la partie la plus difficile est probablement de trouver le nombre de nombres premiers nécessaires pour représenter le résultat sans utiliser d'arithmétique de précision arbitraire, bien que la conversion ultérieure en RNS ne soit pas exactement triviale.1234,1234,+
)?Réponses:
ÉCART
Contexte: Je dois admettre que lorsque j'ai créé cette question, il y a tant de mois, je n'avais pas de méthode pour résoudre la partie difficile de cette question: déterminer le nombre correct de nombres premiers à utiliser. Nous avons beaucoup de gens très intelligents sur ce site, et je m'attendais vraiment à ce que quelqu'un trouve un moyen de le faire assez rapidement. Cependant, comme cela ne s'est pas produit, je n'étais même pas sûr qu'il était vraiment possible de résoudre ce problème. J'ai donc dû prendre le temps de concevoir une méthode. Je crois que ce que j'ai fait ne viole aucune des règles de ce défi, bien sûr, j'aimerais que cela soit vérifié.
Je regrette également légèrement le choix du code-golf car les solutions sont un peu plus en profondeur que celles qui conviendraient habituellement au format tag. Cela dit, pour suivre les règles du site, il y a une version "golfée" de ma solution au bas de cet article.
Code
Explication:
Pour commencer, nous calculons la totalité des 100 résidus pour les deux entrées. Nous le faisons avec la
modulus
fonction dans le code. J'ai essayé d'être prudent afin que nous utilisions lamod
fonction intégrée après chaque étape. Cela garantit que nous n'avons jamais un nombre supérieur à540^2
, qui est inférieur de 1 au 100e premier carré.Une fois que nous avons tous les résidus, nous pouvons effectuer l'opération donnée et
mod
chaque entrée à nouveau. Nous avons maintenant un désignateur unique pour le résultat, mais nous devons déterminer le nombre minimal d'entrées que nous devons utiliser pour représenter le résultat et chacune des entrées.Déterminer le nombre de résidus dont nous avons réellement besoin est de loin la partie la plus difficile de ce problème. Pour déterminer cela, nous effectuons la plupart des étapes du théorème du reste chinois (CRT). Cependant, nous devons évidemment apporter des modifications afin de ne pas nous retrouver avec des nombres trop grands.
Soit
prod(i)
la somme des premiersi-1
nombres premiers. Par exemple,Soit
X
un entier. Soit{r_i}
les résidus deX
, c'est-à-direOù
p_i
est lei
e premier. C'est pour1<i<=100
dans notre cas.Maintenant, nous allons utiliser le CRT pour trouver une séquence
{u_i}
telle que la sommei
deprod(i) * u_i
soit égale àX
. Notez que chacunu_i
est également techniquement un résidu, commeu_i < p_i
. De plus, siX < prod(i)
alorsu_i = 0
. Ceci est d'une importance cruciale. Cela signifie qu'en examinant les zéros à la fin, nous pouvons déterminer le nombre de résidus quer_i
nous devons réellement représenterX
dans le RNS.Si vous souhaitez examiner certaines séquences de
u_i
, lapartial_chinese
fonction renvoie lau_i
séquence.En jouant avec le CRT, j'ai pu trouver une formule récursive pour les
u_i
valeurs, résolvant le problème de déterminer combien de résidus nous avons besoin.La formule est:
Où
SUM
est la sommej in [1,i)
deu_j * prod(i)
.Bien sûr,
prod(i)
ne peut pas être calculé dans certains cas car il est trop grand. Pour cela, j'ai utilisé laphi_i
fonction. Cette fonction revientprod(j) (mod p_i)
. C'estmod
à chaque étape, donc nous ne calculons jamais rien de trop grand.Si vous êtes curieux d'où vient cette formule, je recommanderais de travailler quelques exemples CRT, qui peuvent être trouvés sur la page wikipedia .
Enfin, pour chaque entrée ainsi que pour notre sortie, nous calculons la
u_i
séquence puis déterminons les zéros de fin. Ensuite, nous jetons ce nombrer_i
à la fin des séquences de résidus.Code "Golfé", 2621 octets
la source
Mathematica, non golfé
La fonction
rns[d_,l_]
convertit un entier de base 10d
en un entier RNS de longueurl
.Fonction
plus
/times
/subtract
ajouter / multiplier / soustraire un entier RNS à / d'un autre, les deux étant de la même longueur.La fonction
mag[f_]
estime l'amplitude approximative du nombref
à virgule flottante en fonction de la limite inférieure de la longueur de sa représentation RNS.Une fonction
ext[m_,n_,i_]
découvre le reste de la division du produit dem
etPrime[Range@n]
parPrime[i]
.Une fonction
multi[e_,p_,t_]
donne le plus petit multiplicateurm
satisfaisant queDivisible[m*e+t,p]
Une fonction
appx[d_]
prend les premiers6
chiffres d'un entier décimal et donne sa valeur approximative en virgule flottante.Avec l'aide des fonctions ci-dessus, nous sommes maintenant en mesure de résoudre un problème délicat - pour déterminer la longueur du résultat .
Tout d'abord, je dois préciser qu'il n'est pas facile de déterminer la longueur RNS d'un entier. Pour les petits entiers, nous pouvons les comparer directement avec le produit des nombres premiers. Mais pour les très grands entiers, puisqu'il est interdit de calculer le produit de nombres premiers infiniment précis, une telle comparaison ne fonctionne plus.
Par exemple, étant donné que le produit de prime
1
à30
est3.16*10^46
, la longueur RNS des entiers autour3.16*10^46
peut éventuellement être29
ou30
. La fonctionmag
donnera29
comme référence pour ces entiers, montrant que les deux29
et30
sont possibles.Une fois la magnitude connue, nous représentons simplement l'entier en fonction de cette magnitude directement, en espérant calculer sa vraie longueur. L'astuce consiste à ajouter de nouveaux nombres au nombre d'origine et à modifier sa représentation RNS, jusqu'à ce que la représentation soit entièrement nulle.
Par exemple,
mag[211.]
est4
, et sa4
représentation de longueur est{1, 1, 1, 1}
.En ajoutant un certain nombre, nous augmentons
211
au plus petit nombre qui est divisible par210
(2*3*5*7
). Et maintenant, nous concluons que le nombre d'origine est supérieur à210
, car420
est égal à "environ" deux fois210
. Il n'est pas difficile d'imaginer que si nous partons de209
, le nombre final est "approximativement"210
.La fonction
length[f_,n_]
prend la valeurf
en virgule flottante pour estimer l'amplitude et la corriger en se basant sur sa représentation RNSn
.La fonction
rnsOperation[a_,b_,op_,rnsop_]
donnernsop[a,b]
etop
correspond aux opérations normales utilisées pour obtenir des résultats approximatifs basés sur des valeurs à virgule flottante.Exemple
la source
Python 3 , 435 octets
Ce défi est sur ma liste de seaux depuis un certain temps, mais ce n'est que récemment que: a) j'ai consacré du temps et de l'attention à essayer de trouver une réponse; et b) a réellement testé mon idée pour calculer la taille des nombres (et donc le nombre de nombres premiers en la comparant à la taille des primitifs) en utilisant une combinaison impie de logarithmes et le théorème des restes chinois. Malheureusement, travailler avec des logarithmes tout en essayant de déterminer le nombre de nombres premiers qui, par exemple,
large_primorial + 3
nécessite, signifiait que je devais trouver des moyens de contourner les problèmes à virgule flottante.Et donc, c'est un port de la réponse de Liam .
Essayez-le en ligne!
Explication
En essayant de porter la réponse de Liam, j'ai personnellement trouvé que certaines des explications données étaient formulées de manière confuse, c'est donc ma tentative d'expliquer son algorithme.
Tout d'abord, nous obtenons les résidus de
n
etm
.Cela implique de transformer tous les chiffres des chaînes d'entrée et de les transformer en nombres modulo chacun de nos nombres premiers, par exemple pour 28, nous aurions
[(20 + 8) mod 2, (20 + 8) mod 3, (20 + 8) mod 5, etc]
Ensuite, nous ajoutons, multiplions ou soustrayons les résidus par paire en utilisant
eval()
Ensuite, nous obtenons la taille de nos résidus, c'est-à-dire le nombre minimum de nombres premiers dont nous avons besoin.
Obtenir les tailles est la partie la plus délicate et la plus gourmande en code. Nous utilisons la
partial_chinese
fonction, qui nous donne notre séquence deu_i
pour déterminer la taille avec. Plus d'informationsu_i
dans un instant.La séquence
u_i
est calculée en prenant chaque résidur_i
, en soustrayant la sommeu_j * primorial(j) for j in [1, i)
, puisdividing
parprimorial(i)
tout le moduloprimes[i]
. C'estu_i = (r_i - SUM) / primorial(i)
. Plus sur nos fonctions primitives et de division dans un instant.phi_i(j, i)
calculeprimorial(j) mod primes[i]
. Division modulo tout premierp
est facilement mis en œuvre en vérifiant manuellement multiplicatif inverses, comme on peut être sûr que toute possibleu_i
est0 <= u_i < p
est assuré d'être à la page et coprime donc est garanti un inverse multiplicatif.Avec tout cela fait, nous imprimons notre chaîne et nous avons terminé.
Et après
C'était amusant à mettre en œuvre. Je veux toujours voir si je peux utiliser les logarithmes d'une manière ou d'une autre dans une autre réponse. Et j'aimerais implémenter ce code ou quelque chose comme dans un langage de golf fonctionnel, comme APL ou Jelly. Toutes les suggestions et corrections de golf sont les bienvenues!
la source