Система счисления остатков


26

В свете большого количества проблем я подумал, что этот может быть интересным.

В этой задаче мы будем использовать систему счисления остатков (RNS) для сложения, вычитания и умножения больших целых чисел.

Что такое RNS

RNS является одним из многих способов, которые люди разработали для идентификации целых чисел. В этой системе числа представлены последовательностью вычетов (которые являются результатами после операции модуля (то есть остатком после целочисленного деления)). В этой системе каждое целое число имеет много представлений. Для простоты мы собираемся ограничить вещи так, чтобы каждое целое число было уникально представлено. Я думаю, что проще описать, что происходит на конкретном примере.

Давайте посмотрим на первые три простых числа: 2, 3, 5. В системе RNS мы можем использовать эти три числа для уникального представления любого числа, которое меньше, чем 2 * 3 * 5 = 30, используя вычеты. Возьми 21:

21 меньше 30, поэтому мы можем представить его, используя результаты после модификации на 2, 3 и 5. (т. Е. Остаток после целочисленного деления на 2, 3 и 5)

Мы бы идентифицировали 21 со следующей последовательностью целых чисел:

21 ~ {21 мод 2, 21 мод 3, 21 мод 5} = {1, 0, 1}

И поэтому в нашей системе RNS вместо «21» мы будем использовать {1,0,1}.

В общем случае, учитывая целое число n , мы представляем n как { n mod 2, ..., n mod p_k }, где p_k - наименьшее простое число, такое, что n меньше, чем произведение всех простых чисел, меньше или равно p_k .

Другой пример, скажем, у нас 3412. Нам нужно использовать 2,3,5,7,11,13 здесь, потому что, 2*3*5*7*11*13=30030тогда как, 2*3*5*7*11=2310который слишком мал.

3412 ~ {3412 мод 2, 3412 мод 3, 3412, мод 5, ..., 3412 мод 13} = {0, 1, 2, 3, 2, 6}

Вы замечаете, что используя эту систему, мы можем представлять очень большие цифры относительно безболезненно. Используя остатки {1, 2, 3, 4, 5, 6, 7, 8, ...}, мы можем представить числа до {2, 6, 30, 210, 2310, 30030, 510510, 9699690 ...} соответственно. ( Вот серия )

Наша задача

Мы будем использовать эти остатки для выполнения +, - и * на больших числах. Я опишу эти процессы ниже. На данный момент вот спецификации ввода и вывода.

вход

Вам будет дано два (потенциально очень больших) числа через аргумент stdin или function. Они будут даны как строки из 10 цифр.

Для дальнейшего описания проблемы мы называем первый вход nи второй m. Предположим, что n> m> = 0 .

Вам также будет дано +или -или *для указания операции для выполнения.

Выход

Пусть х целое число. Мы будем использовать [ x ] для ссылки на RNS-представление, описанное выше для x .

Вы должны вывести [n] <operator> [m] = [result]

Как выполнять операции в РНС

Эти операции относительно просты. Учитывая два числа в обозначениях RNS, чтобы сложить, вычесть или умножить их, просто выполните данные операции компонентно, а затем возьмите модуль.

т.е.

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

Обратите внимание, что если число остатков, используемых для представления двух разных чисел, не одинаково, при выполнении операций вам нужно будет увеличить «более короткое» число, чтобы оно имело одинаковое количество остатков. Это следует тому же процессу. Смотрите тестовые примеры для примера.

То же самое происходит, если результат требует больше остатков, чем любой из входных данных. Затем оба входа должны быть «расширены».

Важные детали

  • Здесь мы будем иметь дело с большими числами, но не произвольно большими. Мы будем нести ответственность за числа до произведения первых 100 простых чисел (см. Ниже). Для этого вам бесплатно дается первые 100 простых чисел (без стоимости байтов) . Вы можете вставить их в массив, называемый pили что-то идиоматическое для вашего языка, а затем вычесть количество байтов, использованных для инициации этого массива, из вашего итогового итога. Это, конечно, означает, что они могут быть жестко запрограммированы или вы можете использовать встроенный для их генерации.

  • Если по какой-либо причине это целочисленное представление по умолчанию, используемое на вашем языке. Это хорошо.

  • Вы не можете использовать любой тип произвольного целочисленного типа, если он не является языком по умолчанию. Если это значение по умолчанию, вы не можете использовать его для хранения целых чисел, которые обычно не помещаются в 64 бита.

  • Чтобы было ясно, каждое целое число всегда будет представлено с наименьшим возможным остатком. Это касается как ввода, так и вывода.

  • Я думаю, что другие спецификации должны предотвращать это, но быть избыточными: вы не можете выполнять данную операцию на входах, а затем и затем изменить все на RNS и затем вывести. Вы должны изменить входные данные на RNS, а затем выполнить операции для получения выходных данных.

Тестовые случаи

  1. Входные данные:

n = 10
m = 4
+

Выход:

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

Объяснение:

Во-первых, измените каждое число на его представление RNS, как описано выше:

10 ~ {0,1,0}и 4 ~ {0,1}. Обратите внимание, что когда мы хотим сделать компонентное сложение, в 10нем больше компонентов, чем 4. Поэтому мы должны «расширить» более короткое число. Поэтому мы вкратце напишем 4 ~ {0,1} --> {0,1, 4 mod 5} = {0,1,4}. Теперь приступим к сложению, а затем возьмем модуль.

  1. вход
n=28
m=18
+

Выход:

 [ 0, 1, 3 ] + [0, 0, 3 ] = [ 0, 1, 1, 4 ]
  1. Ввод (я растираю лицо на клавиатуре)
n=1231725471982371298419823012819231982571923
m=1288488183
*

Вывод (разбит на отдельные строки для удобства чтения):

[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требует 28 простых чисел. mтребует 10. n*mтребует 33.

  1. вход
n=8709668761379269784034173446876636639594408083936553641753483991897255703964943107588335040121154680170867105541177741204814011615930342030904704147856733048115934632145172739949220591246493529224396454328521288726490
m=1699412683745170450115957274739962577420086093042490863793456500767137147999161679589295549397604032154933975242548831536518655879433595016
-

Выход:

[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использует 100 простых чисел. mиспользует 70 простых чисел. n-mиспользует 99 простых чисел.

Я проверил их, используя ChineseRemвстроенную реализацию китайской теоремы об остатках для GAP (которая в основном берет числа RNS и заменяет их на целые 10). Я считаю, что они верны. Если что-то кажется подозрительным, пожалуйста, дайте мне знать.


Для тех, кто заботится, продукт первых 100 простых чисел:

471193079990618495316248783476026042202057477340967552018863483961641533584503
422120528925670554468197243910409777715799180438028421831503871944494399049257
9030720635990538452312528339864352999310398481791730017201031090

Это число на 1 больше максимального числа, которое мы можем представить, используя данную систему (и ограничение 100 простых чисел).

Несколько связаны


Я думаю, что выполнение операции - далеко не самая сложная часть, из-за которой я чувствую себя странно в этой задаче.
njpipeorgan

@njpipeorgan Я согласен, выполнение операции просто (a,b,o)=>a.map((v,i)=>eval(v+o+b[i]))в ES6, например. Я думаю, что самая сложная часть - это, вероятно, найти число простых чисел, необходимое для представления результата без использования арифметики произвольной точности, хотя последующее преобразование в RNS не совсем тривиально.
Нил

Могу ли я иметь такой ввод ( 1234,1234,+)?
clismique

@derpfacePython да, функции также приемлемы
Лиам

«просто выполнять данные операции по компонентам» - тогда откуда появляются дополнительные компоненты в выводе?
smls

Ответы:


6

GAP

Немного предыстории: я признаю, что когда я создавал этот вопрос много месяцев назад, у меня не было метода для решения трудной части этого вопроса: определения правильного числа простых чисел для использования. На этом сайте много очень умных людей, и я действительно ожидал, что кто-то найдет способ сделать это довольно быстро. Однако, поскольку этого не произошло, я даже не был уверен, что эту проблему действительно можно решить. Итак, мне пришлось потратить время на разработку метода. Я считаю, что то, что я сделал, не нарушает ни одного из правил в этом испытании, конечно, я хотел бы, чтобы это было проверено фактом.

Я также немного сожалею о выборе потому что решения немного глубже, чем обычно бы подходили для формата тегов. Сказав это, чтобы следовать правилам сайта, в нижней части этого поста есть версия моего решения для игры в гольф.


Код

### 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;

Объяснение:

Для начала мы рассчитаем все 100 остатков для обоих входов. Мы делаем это с помощью modulusфункции в коде. Я старался быть осторожным, чтобы мы использовали встроенную modфункцию после каждого шага. Это гарантирует, что у нас никогда не будет числа, которое больше, чем 540^2на 1 меньше 100-го простого квадрата.

После того, как у нас есть все остатки, мы можем выполнить данную операцию и modкаждую запись снова. Теперь у нас есть уникальный указатель для результата, но нам нужно определить минимальное количество записей, которые мы должны использовать для представления результата и каждого из входных данных.

Выяснение того, сколько остатков нам действительно нужно, является самой сложной частью этой проблемы. Чтобы определить это, мы выполним большинство шагов китайской теоремы об остатках (CRT). Тем не менее, мы, очевидно, должны внести изменения, чтобы мы не получили слишком большие числа.

Позвольте prod(i)быть сумма первых i-1простых чисел. Например,

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

Позвольте Xбыть целым числом. Позвольте {r_i}быть остатки X, то есть

r_i = X mod p_i

Где p_iэто iй премьер. Это для 1<i<=100нашего случая.

Теперь мы будем использовать CRT , чтобы найти последовательность {u_i}таким образом, что сумма по iот prod(i) * u_iравна X. Обратите внимание, что каждый u_iтакже технически является остатком, как u_i < p_i. Кроме того, если X < prod(i)потом u_i = 0. Это имеет решающее значение. Это означает, что, изучая конечные нули, мы можем определить, сколько остатков r_iнам действительно нужно представить Xв RNS.

Если вы хотите изучить некоторые последовательности u_i, partial_chineseфункция возвращает u_iпоследовательность.

Возиться с CRT, я смог найти рекурсивную формулу для u_iзначений, решая вопрос определения, сколько остатков нам нужно.

Формула:

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

Где SUMэто сумма по j in [1,i)из u_j * prod(i).

Конечно, prod(i)в некоторых случаях его невозможно рассчитать, потому что он слишком велик. Для этого я использовал phi_iфункцию. Эта функция возвращает prod(j) (mod p_i). Это происходит modна каждом шагу, поэтому мы никогда не вычисляем слишком большие значения.

Если вам интересно, откуда взялась эта формула, я бы порекомендовал поработать над несколькими примерами CRT, которые можно найти на странице википедии .

Наконец, для каждого входа, а также для нашего вывода мы вычисляем u_iпоследовательность и затем определяем конечные нули. Затем мы выбрасываем это много r_iиз конца последовательностей вычетов.


Код "Golfed", 2621 байт

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;

Я сбит с толку, потому что обычная RNS не меняет размеры по мере необходимости, но разве вы не изгибаете правила, вычисляя расширенный номер остатка 100 из ввода, а не только необходимые измерения и затем выполняя операции? «Во-первых, измените каждое число на его представление RNS, как описано выше », для меня означает, что номер «RNS» должен иметь только те остатки, которые необходимы, прежде чем что-либо будет сделано.
Линус

Извините @Linus, я думал, что уже ответил на это. Я согласен с вами, но я думаю, что необходимые изменения (которые я сделаю) относительно тривиальны. Насколько я понимаю, все, что мне нужно сделать, это рассчитать длину остатков входных данных перед выполнением операции. Использование всех 100 простых чисел для всех трех чисел просто использует тот факт, что все числа ограничены сверху
Лиам

@Linus и в ответ на ваш первый вопрос, как правило, все числа используют одинаковое количество остатков. Это сделало бы вопрос намного проще
Лиам

2

Mathematica, не игра в гольф

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}]
  • Функция rns[d_,l_]преобразует целое число-10 в целое число dRNS длины l.

  • Функция plus/ times/ subtractдобавить / умножить / вычесть одно целое число RNS в / из другого, оба из которых имеют одинаковую длину.

  • Функция mag[f_]оценивает приблизительную величину числа с плавающей запятой fв терминах нижней границы длины своего представления RNS.

  • Функция ext[m_,n_,i_]узнает остаток от деления на произведение mи Prime[Range@n]на Prime[i].

  • Функция multi[e_,p_,t_]дает наименьший множитель, mудовлетворяющийDivisible[m*e+t,p]

  • Функция appx[d_]берет первые 6цифры десятичного целого числа и выдает приблизительное значение с плавающей запятой.


С помощью вышеперечисленных функций теперь мы можем решить непростую задачу - определить длину результата .

Во-первых, я должен уточнить, что определить длину RNS целого числа нелегко. Для небольших целых чисел мы можем сравнить их непосредственно с произведением простых чисел. Но для очень больших целых чисел, поскольку запрещено вычислять произведение простых чисел бесконечно точно, такое сравнение больше не работает.

Например, при условии , что продукт простого 1To 30есть 3.16*10^46, длина РНС целых чисел вокруг 3.16*10^46может быть возможна 29или 30. Функция magдаст 29в качестве ссылки для этих целых чисел, показывая, что оба 29и 30возможны.

Зная величину, мы просто представляем целое число в соответствии с этой величиной напрямую, надеясь вычислить его истинную длину. Хитрость здесь в том, чтобы добавить несколько новых чисел к исходному номеру и изменить его представление RNS, пока представление не станет нулевым.

Например, mag[211.]есть 4, а его 4представление длины есть {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

Добавляя некоторое число, мы увеличиваем 211до наименьшего числа, которое делится на 210( 2*3*5*7). И теперь мы заключаем, что исходное число больше, чем 210, поскольку 420равно «примерно» в два раза 210. Нетрудно представить, что если мы начнем 209, то окончательное число будет «приблизительно» 210.

Функция length[f_,n_]принимает значение с плавающей запятой, fчтобы оценить величину, и скорректировать ее на основе своего представления RNS 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]]]]

Функция rnsOperation[a_,b_,op_,rnsop_]дает rnsop[a,b]и opсоответствует нормальным операциям, используемым для получения приблизительных результатов на основе значений с плавающей запятой.

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]]]

пример

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
К сожалению, правила, изложенные в нашем справочном центре, требуют, чтобы все заявки были серьезным претендентом на критерии победы в использовании. Для соревнования по коду в гольф это означает, что все заявки должны быть заполнены.
Деннис

@ Денис, я знаю об этом правиле. Тем не менее, даже без игры в гольф, я думаю, что эта проблема сложная и достаточно сложная, так что решение этой проблемы, а не игра в гольф, является моей целью.
njpipeorgan

это, возможно, не игра в гольф, но чертовски мало по сравнению с моей программой Java: P, хотя моя программа, вероятно, намного быстрее.
Надеюсь, что это

1
Я чувствую, что вы способны играть в гольф
Рохан Джунджхунвала

2

Python 3 , 435 байт

Эта проблема была в моем списке некоторое время, но только недавно: а) я потратил время и внимание на фактическую попытку ответа; и б) фактически проверил мою идею вычислить размер чисел (и, таким образом, число простых чисел, сравнивая его с размером примитивов), используя некую нечестивую комбинацию логарифмов и теоремы об остатках в Китае. К сожалению, работа с логарифмами при попытке определить количество простых чисел, которые, например, large_primorial + 3требуются, означала, что мне пришлось искать способы обхода проблем с плавающей запятой.

И так, это порт ответа Лиама .

Попробуйте онлайн!

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)]))

объяснение

Пытаясь перенести ответ Лиама, я лично обнаружил, что некоторые из представленных объяснений были смущающе сформулированы, так что это моя попытка объяснить его алгоритм.

Во-первых, мы получаем остатки nи m.

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

Это включает в себя превращение всех цифр входных строк и превращение их в числа по модулю каждого из наших простых чисел, например, для 28, мы бы [(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))

Затем мы добавляем, умножаем или вычитаем остатки попарно, используя eval()

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

Затем мы получаем размеры наших остатков, то есть минимальное количество простых чисел, которые нам нужны.

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

Получение размеров - самая сложная и интенсивная часть кода. Мы используем partial_chineseфункцию, которая дает нам нашу последовательность, u_iчтобы определить размер с. Больше на u_iмгновение.

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

Последовательность u_iвычисляется путем r_iвычитания каждого остатка , вычитания суммы u_j * primorial(j) for j in [1, i), а затем dividingпо primorial(i)модулю primes[i]. То есть u_i = (r_i - SUM) / primorial(i). Подробнее о наших основных и разделительных функциях.

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)рассчитывает primorial(j) mod primes[i]. Отдел по модулю любого простого pлегко реализуется путем проверки мультипликативных инверсий вручную, как мы можем быть уверены , что любой возможный u_iбудет 0 <= u_i < pгарантировано быть взаимно просты с р и т гарантируется мультипликативный обратный.

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

После всего этого мы печатаем нашу строку и все готово.

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

Что дальше

Это было весело реализовать. Я все еще хочу посмотреть, могу ли я каким-то образом использовать логарифмы в другом ответе. И я хотел бы реализовать этот код или что-то вроде функционального языка игры в гольф, например, APL или Jelly. Любые предложения и исправления в гольф приветствуются!

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.