Na linha de grandes desafios, pensei que este poderia ser interessante.
Neste desafio, usaremos o sistema de número de resíduos (RNS) para executar adição, subtração e multiplicação em números inteiros grandes.
O que é o RNS
O RNS é uma das muitas maneiras que as pessoas desenvolveram para identificar números inteiros. Neste sistema, os números são representados por uma sequência de resíduos (que são os resultados após uma operação de módulo (ou seja, o restante após a divisão inteira)). Neste sistema, cada número inteiro tem muitas representações. Para manter as coisas simples, vamos limitar as coisas para que cada número inteiro seja representado exclusivamente. Eu acho que é mais fácil descrever o que está acontecendo com um exemplo concreto.
Vejamos os três primeiros números primos: 2, 3, 5. No sistema RNS, podemos usar esses três números para representar exclusivamente qualquer número que seja menor que 2 * 3 * 5 = 30 usando resíduos. Tome 21:
21 é menor que 30, então podemos representá-lo usando os resultados após a modificação de 2, 3 e 5. (ou seja, o restante após o número inteiro dividido por 2, 3 e 5)
Identificamos 21 com a seguinte sequência de números inteiros:
21 ~ {21 mod 2, 21 mod 3, 21 mod 5} = {1, 0, 1}
E assim, em nosso sistema RNS, em vez de "21", usaríamos {1,0,1}.
Em geral, dado um número inteiro n , representamos n como { n mod 2, ..., n mod p_k }, em que p_k é o menor primo, de modo que n é menor que o produto de todos os números primos menores ou iguais a p_k .
Outro exemplo, digamos que temos 3412. Precisamos usar 2,3,5,7,11,13 aqui porque 2*3*5*7*11*13=30030
considerando 2*3*5*7*11=2310
que é muito pequeno.
3412 ~ {3412 mod 2, 3412 mod 3, 3412, mod 5, ..., 3412 mod 13} = {0, 1, 2, 3, 2, 6}
Você percebe que, usando esse sistema, podemos representar números muito grandes de maneira relativamente indolor. Usando resíduos {1, 2, 3, 4, 5, 6, 7, 8, ...}, podemos representar números até {2, 6, 30, 210, 2310, 30030, 510510, 9699690 ...} respectivamente. ( Aqui está a série )
Nossa tarefa
Usaremos esses resíduos para executar +, - e * em grandes números. Vou descrever esses processos abaixo. Por enquanto, aqui estão as especificações de entrada e saída.
Entrada
Você receberá dois números (potencialmente muito grandes) por meio de um argumento stdin ou função. Eles serão dados como cadeias de caracteres de base 10.
Para fins de descrever o problema ainda mais, chamamos a primeira entrada n
e a segunda m
. Suponha que n> m> = 0 .
Você também receberá +
ou -
ou *
para indicar a operação a ser executada.
Saída
Seja x um número inteiro. Usaremos [ x ] para nos referir à representação RNS descrita acima de x .
Você deve produzir [n] <operator> [m] = [result]
Como executar as operações no RNS
Essas operações são relativamente simples. Dados dois números na notação RNS, para adicioná-los, subtraí-los ou multiplicá-los, simplesmente execute as operações especificadas em componentes e, em seguida, faça o módulo.
ie
{1, 2, 3} + {1, 1, 4} = {(1 + 1) mod 2, (2 + 1) mod 3, (3 + 4) mod 5} = {0, 0, 2}
Observe que, se o número de resíduos usado para representar dois números diferentes não for o mesmo, ao executar operações, será necessário estender o número "mais curto" para que ele tenha o mesmo número de resíduos. Isso segue o mesmo processo. Veja os casos de teste para um exemplo.
O mesmo acontece se o resultado exigir mais resíduos do que qualquer entrada. Então ambas as entradas precisam ser "estendidas".
Detalhes importantes
Lidaremos com grandes números aqui, mas não arbitrariamente grandes. Seremos responsáveis pelos números até o produto dos 100 primeiros números primos (veja abaixo). Para esse fim, você recebe os primeiros 100 números primos gratuitamente (sem custo de bytes) . Você pode colocá-los em uma matriz chamada
p
ou algo idiomático no seu idioma e subtrair o número de bytes usados para iniciar essa matriz do total final. Obviamente, isso significa que eles podem ser codificados ou você pode usar um built-in para gerá-los.Se, por algum motivo, essa for a representação inteira padrão usada no seu idioma. Está bem.
Você não pode usar nenhum tipo de número inteiro de precisão arbitrária, a menos que seja o padrão do seu idioma. Se for o padrão, você não pode usá-lo para armazenar números inteiros que normalmente não cabem em 64 bits.
Para ficar claro, cada número inteiro sempre será representado com o menor número de resíduos possível. Isso vale para entrada e saída.
Eu acho que as outras especificações devem evitar isso, mas para ser redundante: você pode não executar a operação especificada nas entradas e depois alterar tudo para RNS e, em seguida, saída. Você deve alterar as entradas para RNS e, em seguida, executar as operações para produzir a saída.
Casos de teste
Entrada:
n = 10
m = 4
+
Saída:
{ 0, 1, 0 } + { 0, 1 } = { 0, 2, 4 }
Explicação:
Primeiro, altere cada número para sua representação RNS, conforme descrito acima:
10 ~ {0,1,0}
e 4 ~ {0,1}
. Observe que quando queremos fazer a adição de componentes, isso 10
tem mais componentes que 4
. Portanto, devemos "estender" o número mais curto. Então, escreveremos brevemente 4 ~ {0,1} --> {0,1, 4 mod 5} = {0,1,4}
. Agora prosseguimos com a adição e, em seguida, tomamos o módulo.
- Entrada
n=28
m=18
+
Saída:
[ 0, 1, 3 ] + [0, 0, 3 ] = [ 0, 1, 1, 4 ]
- Entrada (eu esmagando meu rosto no teclado)
n=1231725471982371298419823012819231982571923
m=1288488183
*
Saída (dividida em linhas separadas para facilitar a leitura):
[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
requer 28 números primos. m
requer 10. n*m
requer 33.
- Entrada
n=8709668761379269784034173446876636639594408083936553641753483991897255703964943107588335040121154680170867105541177741204814011615930342030904704147856733048115934632145172739949220591246493529224396454328521288726490
m=1699412683745170450115957274739962577420086093042490863793456500767137147999161679589295549397604032154933975242548831536518655879433595016
-
Saída:
[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
usa 100 números primos. m
usa 70 números primos. n-m
usa 99 números primos.
Eu os verifiquei usando a ChineseRem
implementação interna do teorema chinês Remainder no GAP (que basicamente pega números RNS e os altera para a base 10 inteiros). Eu acredito que eles estão corretos. Se algo parecer suspeito, entre em contato.
Para quem se importa, o produto dos 100 primeiros números primos é:
471193079990618495316248783476026042202057477340967552018863483961641533584503
422120528925670554468197243910409777715799180438028421831503871944494399049257
9030720635990538452312528339864352999310398481791730017201031090
Esse número é 1 maior que o número máximo que podemos representar usando o sistema fornecido (e a limitação de 100 primos).
(a,b,o)=>a.map((v,i)=>eval(v+o+b[i]))
no ES6, por exemplo. Acho que a parte mais difícil é provavelmente encontrar o número de números primos necessários para representar o resultado sem usar aritmética de precisão arbitrária, embora a conversão subsequente ao RNS não seja exatamente trivial.1234,1234,+
)?Respostas:
GAP
Alguns antecedentes: admito que, quando criei essa pergunta, há muitos meses, eu não tinha um método para resolver a parte difícil dessa questão: determinar o número correto de números primos a serem usados. Temos muitas pessoas muito inteligentes neste site, e eu realmente esperava que alguém descobrisse uma maneira de fazê-lo rapidamente. No entanto, como isso não aconteceu, eu nem tinha certeza de que era realmente possível resolver esse problema. Então, eu tive que dedicar um tempo para criar um método. Acredito que o que fiz não viole nenhuma das regras deste desafio, é claro que eu adoraria que isso fosse verificado.
Também me arrependo um pouco da escolha do código-golfe, porque as soluções são um pouco mais profundas do que normalmente se ajustariam ao formato da tag. Dito isto, para seguir as regras do site, há uma versão "golfed" da minha solução na parte inferior deste post.
Código
Explicação:
Para começar, calculamos todos os 100 resíduos para ambas as entradas. Fazemos isso com a
modulus
função no código. Tentei tomar cuidado para usar amod
função interna após cada etapa. Isso garante que nunca tenhamos um número maior que540^2
, que seja 1 a menos que o centésimo primo ao quadrado.Depois de termos todos os resíduos, podemos executar a operação especificada e
mod
cada entrada novamente. Agora, temos um designador exclusivo para o resultado, mas precisamos determinar o número mínimo de entradas que precisamos usar para representar o resultado e cada uma das entradas.Descobrir quantos resíduos realmente precisamos é de longe a parte mais difícil desse problema. Para determinar isso, realizamos a maioria das etapas do Teorema do Restante Chinês (CRT). No entanto, obviamente temos que fazer modificações para não acabar com números muito grandes.
Let
prod(i)
Ser a soma dos primeirosi-1
números primos. Por exemplo,Let
X
Ser um número inteiro. Let{r_i}
Ser os resíduos deX
, isto éOnde
p_i
está oi
th th prime. Isto é para o1<i<=100
nosso caso.Agora vamos usar o CRT para encontrar uma sequência em
{u_i}
que a soma acimai
deprod(i) * u_i
seja igual aX
. Observe que cadau_i
um também é tecnicamente um resíduo, comou_i < p_i
. Além disso, seX < prod(i)
entãou_i = 0
. Isto é de importância crítica. Isso significa que, examinando os zeros à direita, podemos determinar quantos resíduosr_i
realmente precisamos representarX
no RNS.Se você deseja examinar algumas seqüências de
u_i
, apartial_chinese
função retornará au_i
sequência.Brincando com o CRT, consegui encontrar uma fórmula recursiva para os
u_i
valores, resolvendo a questão de determinar quantos resíduos precisamos.A fórmula é:
Onde
SUM
está a somaj in [1,i)
deu_j * prod(i)
.Obviamente,
prod(i)
em alguns casos não pode ser calculado porque é muito grande. Para esse fim, usei aphi_i
função Esta função retornaprod(j) (mod p_i)
. Émod
a cada passo, portanto, na verdade, nunca calculamos algo muito grande.Se você está curioso de onde vem essa fórmula, eu recomendaria trabalhar alguns exemplos de CRT, que podem ser encontrados na página da wikipedia .
Finalmente, para cada entrada e também para nossa saída, calculamos a
u_i
sequência e depois determinamos os zeros à direita. Então jogamos fora muitosr_i
deles no final das seqüências de resíduos.Código "Golfed", 2621 bytes
fonte
Mathematica, não jogou golfe
A função
rns[d_,l_]
converte um número inteiro de base 10d
em um número inteiro RNS de comprimentol
.A função
plus
/times
/subtract
adiciona / multiplica / subtrai um número inteiro do RNS para / de outro, ambos com o mesmo comprimento.A função
mag[f_]
estima a magnitude aproximada do número de ponto flutuantef
em termos do limite inferior do comprimento de sua representação RNS.A função
ext[m_,n_,i_]
descobre o restante da divisão do produto dem
ePrime[Range@n]
porPrime[i]
.A função
multi[e_,p_,t_]
fornece o menor multiplicadorm
que satisfazDivisible[m*e+t,p]
A função obtém
appx[d_]
os primeiros6
dígitos de um número inteiro decimal e fornece seu valor aproximado de ponto flutuante.Com a ajuda das funções acima, agora somos capazes de resolver um problema complicado - para determinar a duração do resultado .
Primeiro, preciso esclarecer que não é uma tarefa fácil determinar o comprimento do RNS de um número inteiro. Para números inteiros pequenos, podemos compará-los diretamente com o produto de números primos. Mas para números inteiros muito grandes, uma vez que é proibido calcular o produto de números primos infinitamente preciso, essa comparação não funciona mais.
Por exemplo, dado que o produto de prime
1
to30
é3.16*10^46
, o comprimento RNS dos números inteiros ao redor3.16*10^46
pode ser29
ou30
. A funçãomag
fornecerá29
como referência para esses números inteiros, mostrando que ambos29
e30
são possíveis.Depois de conhecer a magnitude, simplesmente representamos o número inteiro de acordo com essa magnitude diretamente, na esperança de calcular seu comprimento real. O truque aqui é adicionar alguns novos números ao número original e modificar sua representação RNS, até que a representação seja zero.
Por exemplo,
mag[211.]
é4
, e sua4
representação de comprimento é{1, 1, 1, 1}
.Ao adicionar algum número, aumentamos
211
para o menor número que é divisível por210
(2*3*5*7
). E agora concluímos que o número original é maior que210
, uma vez que420
é igual a "aproximadamente" duas vezes de210
. Não é difícil imaginar que, se começarmos209
, o número final é "aproximadamente"210
.A função
length[f_,n_]
pega o valor do ponto flutuantef
para estimar a magnitude e corrige-o com base na sua representação RNSn
.A função
rnsOperation[a_,b_,op_,rnsop_]
fornecernsop[a,b]
eop
corresponde às operações normais usadas para obter resultados aproximados com base nos valores de ponto flutuante.Exemplo
fonte
Python 3 , 435 bytes
Esse desafio está na minha lista de desejos por um tempo, mas é apenas recentemente que: a) dedico tempo e atenção para realmente tentar uma resposta; eb) realmente testou minha idéia para calcular o tamanho dos números (e, portanto, o número de primos comparando-o com o tamanho dos primoriais) usando alguma combinação profana de logaritmos e o Teorema Chinês de Restantes. Infelizmente, trabalhar com logaritmos enquanto tentava determinar o número de números primos que, por exemplo,
large_primorial + 3
requer, significava que eu estava tendo que encontrar maneiras de solucionar problemas de ponto flutuante.E assim, este é um porto da resposta de Liam .
Experimente online!
Explicação
Enquanto tentava portar a resposta de Liam, eu pessoalmente achei que algumas das explicações dadas eram confusas, portanto essa é minha tentativa de explicar seu algoritmo.
Primeiro, obtemos os resíduos de
n
em
.Isso envolve transformar todos os dígitos das seqüências de entrada e transformá-los em números módulo cada um de nossos números primos, por exemplo, para 28, teríamos
[(20 + 8) mod 2, (20 + 8) mod 3, (20 + 8) mod 5, etc]
Em seguida, adicionamos, multiplicamos ou subtraímos os resíduos aos pares usando
eval()
Em seguida, obtemos os tamanhos de nossos resíduos, ou seja, o número mínimo de primos de que precisamos.
Obter os tamanhos é a parte mais complicada e que exige mais código. Usamos a
partial_chinese
função, que nos leva a nossa sequência deu_i
para determinar o tamanho com. Maisu_i
em um momento.A sequência
u_i
é calculada pegando cada resíduor_i
, subtraindo a somau_j * primorial(j) for j in [1, i)
e depoisdividing
porprimorial(i)
todo o móduloprimes[i]
. Isto éu_i = (r_i - SUM) / primorial(i)
. Mais sobre nossas funções primárias e de divisão em um momento.phi_i(j, i)
calculaprimorial(j) mod primes[i]
. O módulo de divisão qualquer primop
é facilmente implementado verificando-se inversos multiplicativos manualmente, pois podemos ter certeza de que qualquer possívelu_i
é0 <= u_i < p
coprime para ep e, portanto, um inverso multiplicativo.Com tudo isso, imprimimos nossa string e pronto.
Qual é o próximo
Foi divertido de implementar. Ainda quero ver se posso usar logaritmos de alguma maneira em outra resposta. E eu gostaria de implementar esse código ou algo parecido em uma linguagem de golfe funcional, como APL ou Jelly. Todas e quaisquer sugestões e correções de golfe são bem-vindas!
fonte