Hệ thống số dư


26

Trong bối cảnh của những thách thức số lượng lớn, tôi nghĩ rằng điều này có thể thú vị.

Trong thử thách này, chúng tôi sẽ sử dụng Hệ thống số dư (RNS) để thực hiện phép cộng, phép trừ và phép nhân trên các số nguyên lớn.

RNS là gì

RNS là một trong nhiều cách mà mọi người đã phát triển để xác định số nguyên. Trong hệ thống này, các số được biểu thị bằng một chuỗi các dư lượng (là kết quả sau một hoạt động mô đun (tức là phần còn lại sau khi chia số nguyên)). Trong hệ thống này, mỗi số nguyên có nhiều biểu diễn. Để giữ cho mọi thứ đơn giản, chúng tôi sẽ giới hạn mọi thứ sao cho mỗi số nguyên được biểu diễn duy nhất. Tôi nghĩ rằng dễ dàng hơn để mô tả những gì đang xảy ra với một ví dụ cụ thể.

Chúng ta hãy xem xét ba số nguyên tố đầu tiên: 2, 3, 5. Trong hệ thống RNS, chúng ta có thể sử dụng ba số này để biểu thị duy nhất bất kỳ số nào nhỏ hơn 2 * 3 * 5 = 30 bằng cách sử dụng dư lượng. Mất 21:

21 nhỏ hơn 30, vì vậy chúng ta có thể biểu diễn nó bằng các kết quả sau khi sửa đổi 2, 3 và 5. (tức là phần còn lại sau khi chia số nguyên cho 2, 3 và 5)

Chúng tôi sẽ xác định 21 với chuỗi số nguyên sau:

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

Và trong hệ thống RNS của chúng tôi, thay vì "21", chúng tôi sẽ sử dụng {1,0,1}.

Nói chung, với một số nguyên n , chúng tôi biểu thị n là { n mod 2, ..., n mod p_k } trong đó p_k là số nguyên tố nhỏ nhất sao cho n nhỏ hơn tích của tất cả các số nguyên tố nhỏ hơn hoặc bằng p_k .

Một ví dụ khác, giả sử chúng ta có 3412. Chúng ta cần sử dụng 2,3,5,7,11,13 ở đây vì 2*3*5*7*11*13=30030trong khi đó, 2*3*5*7*11=2310quá nhỏ.

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

Bạn nhận thấy rằng sử dụng hệ thống này, chúng tôi có thể đại diện cho số lượng rất lớn tương đối không đau. Sử dụng dư lượng {1, 2, 3, 4, 5, 6, 7, 8, ...}, chúng tôi có thể biểu thị các số lên tới {2, 6, 30, 210, 2310, 30030, 510510, 9699690 ...} tương ứng. ( Đây là loạt bài )

Nhiệm vụ của chúng ta

Chúng tôi sẽ sử dụng các dư lượng này để thực hiện +, - và * trên số lượng lớn. Tôi sẽ mô tả các quá trình dưới đây. Bây giờ ở đây là các thông số kỹ thuật đầu vào và đầu ra.

Đầu vào

Bạn sẽ được cung cấp hai số (có khả năng rất lớn) thông qua đối số stdin hoặc hàm. Chúng sẽ được đưa ra dưới dạng chuỗi gồm 10 chữ số cơ bản.

Đối với mục đích phác thảo vấn đề hơn nữa, chúng tôi gọi đầu vào đầu tiên nvà thứ hai m. Giả sử n> m> = 0 .

Bạn cũng sẽ được cung cấp +hoặc -hoặc *chỉ ra thao tác để thực hiện.

Đầu ra

Đặt x là một số nguyên. Chúng tôi sẽ sử dụng [ x ] để chỉ đại diện RNS được mô tả ở trên của x .

Bạn đang xuất [n] <operator> [m] = [result]

Cách thực hiện các thao tác trong RNS

Các thao tác này tương đối đơn giản. Cho hai số trong ký hiệu RNS, để cộng, trừ hoặc nhân chúng, chỉ cần thực hiện các thao tác thành phần đã cho là khôn ngoan và sau đó lấy mô đun.

I E

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

Lưu ý rằng nếu số lượng dư lượng được sử dụng để đại diện cho hai số khác nhau không giống nhau, khi thực hiện các thao tác, bạn sẽ cần phải mở rộng số "ngắn hơn" để có cùng số lượng dư lượng. Điều này tuân theo quy trình tương tự. Xem các trường hợp thử nghiệm cho một ví dụ.

Điều tương tự cũng xảy ra nếu kết quả yêu cầu nhiều dư lượng hơn đầu vào. Sau đó, cả hai đầu vào cần phải được "mở rộng".

Chi tiết quan trọng

  • Chúng tôi sẽ xử lý số lượng lớn ở đây, nhưng không lớn tùy tiện. Chúng tôi sẽ chịu trách nhiệm về số lượng lên tới sản phẩm của 100 số nguyên tố đầu tiên (xem bên dưới). Để kết thúc này, bạn được cung cấp 100 số nguyên tố đầu tiên miễn phí (không có chi phí byte) . Bạn có thể dán chúng trong một mảng được gọi phoặc một cái gì đó thành ngữ với ngôn ngữ của bạn và sau đó trừ đi số byte được sử dụng để bắt đầu mảng này khỏi tổng số cuối cùng của bạn. Điều này tất nhiên có nghĩa là chúng có thể được mã hóa cứng hoặc bạn có thể sử dụng một tích hợp để tạo ra chúng.

  • Nếu vì một lý do nào đó, đây là biểu diễn số nguyên mặc định được sử dụng trong ngôn ngữ của bạn. Điều đó là tốt.

  • Bạn không được sử dụng bất kỳ loại Số nguyên chính xác tùy ý trừ khi đó là mặc định của ngôn ngữ của bạn. Nếu nó là mặc định, bạn không thể sử dụng nó để lưu trữ các số nguyên thường không phù hợp với 64 bit.

  • Để rõ ràng, mỗi số nguyên sẽ luôn được đại diện với ít dư lượng nhất có thể. Điều này đi cho cả đầu vào và đầu ra.

  • Tôi nghĩ rằng các thông số kỹ thuật khác nên ngăn chặn điều này, nhưng để dự phòng: bạn có thể không thực hiện thao tác đã cho trên các đầu vào và sau đó thay đổi mọi thứ thành RNS và sau đó xuất ra. Bạn phải thay đổi đầu vào thành RNS và sau đó thực hiện các thao tác để tạo đầu ra.

Các trường hợp thử nghiệm

  1. Đầu vào:

n = 10
m = 4
+

Đầu ra:

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

Giải trình:

Đầu tiên, thay đổi từng số thành biểu diễn RNS của nó như được mô tả ở trên:

10 ~ {0,1,0}4 ~ {0,1}. Lưu ý rằng khi chúng ta muốn thực hiện bổ sung thành phần, 10có nhiều thành phần hơn 4. Do đó, chúng ta phải "mở rộng" số ngắn hơn. Vì vậy, chúng tôi sẽ viết ngắn gọn 4 ~ {0,1} --> {0,1, 4 mod 5} = {0,1,4}. Bây giờ chúng tôi tiến hành bổ sung và sau đó lấy mô-đun.

  1. Đầu vào
n=28
m=18
+

Đầu ra:

 [ 0, 1, 3 ] + [0, 0, 3 ] = [ 0, 1, 1, 4 ]
  1. Đầu vào (tôi đập mặt trên bàn phím)
n=1231725471982371298419823012819231982571923
m=1288488183
*

Đầu ra (được chia thành các dòng riêng biệt để dễ đọc):

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

nyêu cầu 28 số nguyên tố. myêu cầu 10. n*myêu cầu 33.

  1. Đầu vào
n=8709668761379269784034173446876636639594408083936553641753483991897255703964943107588335040121154680170867105541177741204814011615930342030904704147856733048115934632145172739949220591246493529224396454328521288726490
m=1699412683745170450115957274739962577420086093042490863793456500767137147999161679589295549397604032154933975242548831536518655879433595016
-

Đầu ra:

[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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]

nsử dụng 100 số nguyên tố. msử dụng 70 số nguyên tố. n-msử dụng 99 số nguyên tố.

Tôi đã kiểm tra những điều này bằng cách sử dụng ChineseRemtriển khai tích hợp của định lý Phần còn lại của Trung Quốc trên GAP (về cơ bản lấy số RNS và thay đổi chúng thành số nguyên cơ bản 10). Tôi tin rằng họ là chính xác. Nếu một cái gì đó có vẻ tanh, xin vui lòng cho tôi biết.


Đối với những người quan tâm, sản phẩm của 100 số nguyên tố đầu tiên là:

471193079990618495316248783476026042202057477340967552018863483961641533584503
422120528925670554468197243910409777715799180438028421831503871944494399049257
9030720635990538452312528339864352999310398481791730017201031090

Con số này lớn hơn 1 so với số tối đa chúng ta có thể biểu thị bằng cách sử dụng hệ thống nhất định (và giới hạn 100 số nguyên tố).

Hơi liên quan


Tôi nghĩ rằng việc thực hiện các hoạt động không phải là phần khó nhất, mà tôi cảm thấy kỳ lạ về thử thách này.
njpipe tổ chức

@njpipe Organ Tôi đồng ý, việc thực hiện thao tác chỉ đơn giản là (a,b,o)=>a.map((v,i)=>eval(v+o+b[i]))trong ES6 chẳng hạn. Tôi nghĩ phần khó nhất có lẽ là tìm số lượng số nguyên tố cần thiết để biểu diễn kết quả mà không sử dụng số học chính xác tùy ý, mặc dù việc chuyển đổi tiếp theo thành RNS không chính xác.
Neil

Tôi có thể có đầu vào như thế này ( 1234,1234,+) không?
clismique

@derpfacePython có chức năng cũng được chấp nhận
Liam

"chỉ đơn giản là thực hiện các hoạt động thành phần nhất định" - vậy thì các thành phần bổ sung trong đầu ra đến từ đâu?
smls

Câu trả lời:


6

LỖ HỔNG

Một số Bối cảnh: Tôi sẽ thừa nhận rằng khi tôi tạo câu hỏi này, rất nhiều tháng trước, tôi không có phương pháp để giải quyết phần khó của câu hỏi này: xác định số lượng chính xác để sử dụng. Chúng tôi có rất nhiều người rất thông minh trên trang web này và tôi thực sự mong đợi rằng ai đó sẽ tìm ra cách để làm điều đó khá nhanh chóng. Tuy nhiên, vì điều này đã không xảy ra, tôi thậm chí không chắc chắn rằng nó thực sự có thể giải quyết vấn đề này. Vì vậy, tôi đã phải dành thời gian để nghĩ ra một phương pháp. Tôi tin rằng những gì tôi đã làm không phá vỡ bất kỳ quy tắc nào trong thử thách này, tất nhiên tôi sẽ thích điều này để được kiểm tra thực tế.

Tôi cũng hơi tiếc nuối khi lựa chọn vì các giải pháp có chiều sâu hơn một chút so với thường sẽ phù hợp với định dạng thẻ. Đã nói điều này, để tuân theo các quy tắc của trang web, có một phiên bản "đánh gôn" của giải pháp của tôi ở cuối bài này.


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

Giải trình:

Để bắt đầu, chúng tôi tính toán tất cả 100 dư lượng cho cả hai đầu vào. Chúng tôi làm điều này với moduluschức năng trong mã. Tôi đã cố gắng cẩn thận để chúng tôi sử dụng modchức năng tích hợp sau mỗi bước. Điều này đảm bảo rằng chúng ta không bao giờ có một số lớn hơn 540^2, nhỏ hơn 1 so với số nguyên tố thứ 100.

Sau khi chúng tôi có tất cả các dư lượng, chúng tôi có thể thực hiện các hoạt động nhất định và modmỗi lần nhập lại. Bây giờ chúng ta có một chỉ định duy nhất cho kết quả, nhưng chúng ta cần xác định số lượng mục nhập tối thiểu mà chúng ta phải sử dụng để thể hiện kết quả và từng đầu vào.

Chỉ ra có bao nhiêu dư lượng chúng ta thực sự cần là phần khó nhất của vấn đề này. Để xác định điều này, chúng tôi thực hiện hầu hết các bước của Định lý còn lại của Trung Quốc (CRT). Tuy nhiên, rõ ràng là chúng tôi phải sửa đổi để không kết thúc với số lượng quá lớn.

Hãy prod(i)là tổng của các i-1số nguyên tố đầu tiên . Ví dụ,

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

Hãy Xlà một số nguyên. Hãy {r_i}là dư lượng của X, đó là

r_i = X mod p_i

Đâu p_iinguyên tố thứ. Điều này là 1<i<=100trong trường hợp của chúng tôi.

Bây giờ chúng ta sẽ sử dụng màn hình CRT để tìm một chuỗi {u_i}như vậy mà số tiền trên icủa prod(i) * u_ibằng X. Lưu ý rằng mỗi cái u_icũng là một dư lượng, như u_i < p_i. Hơn nữa, nếu X < prod(i)sau đó u_i = 0. Điều này là rất quan trọng. Điều đó có nghĩa là bằng cách kiểm tra các số 0 ở cuối, chúng ta có thể xác định có bao nhiêu phần dư r_imà chúng ta thực sự cần phải đại diện Xtrong RNS.

Nếu bạn quan tâm để kiểm tra một số trình tự u_i, partial_chinesehàm sẽ trả về u_itrình tự.

Bằng cách tìm hiểu về CRT, tôi có thể tìm ra một công thức đệ quy cho các u_igiá trị, giải quyết vấn đề xác định số lượng dư lượng chúng ta cần.

Công thức là:

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

Trong trường hợp SUMlà tổng j in [1,i)của u_j * prod(i).

Tất nhiên, prod(i)thực sự không thể được tính toán trong một số trường hợp vì nó quá lớn. Đối với mục đích này, tôi đã sử dụng phi_ichức năng. Hàm này trả về prod(j) (mod p_i). Đó modlà ở mỗi bước, vì vậy chúng tôi không bao giờ thực sự tính toán bất cứ điều gì quá lớn.

Nếu bạn tò mò công thức này đến từ đâu, tôi khuyên bạn nên làm việc với một vài ví dụ CRT, có thể tìm thấy trên trang wikipedia .

Cuối cùng, đối với mỗi đầu vào cũng như đầu ra của chúng tôi, chúng tôi tính toán u_ichuỗi và sau đó xác định các số 0 ở cuối. Sau đó, chúng tôi ném ra rất nhiều r_itừ cuối chuỗi dư lượng.


Mã "Golfed", 2621 byte

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;

Tôi bối rối vì RNS thông thường không thay đổi kích thước khi cần, nhưng bạn không bẻ cong quy tắc bằng cách tính số dư 100 mở rộng từ đầu vào, thay vì chỉ kích thước cần thiết và sau đó thực hiện các thao tác? "Đầu tiên, thay đổi từng số thành đại diện RNS của nó như được mô tả ở trên " với tôi có nghĩa là số 'RNS' chỉ nên có dư lượng cần thiết trước khi mọi thứ được thực hiện.
Linus

Xin lỗi @Linus, tôi nghĩ rằng tôi đã trả lời điều này rồi. Tôi đồng ý với bạn, nhưng tôi nghĩ rằng sự thay đổi cần thiết (mà tôi sẽ thực hiện) là tương đối tầm thường. Như tôi thấy, tất cả những gì tôi cần làm là tính toán độ dài dư lượng của đầu vào trước khi thực hiện thao tác. Sử dụng tất cả 100 số nguyên tố cho cả ba số chỉ thúc đẩy thực tế là tất cả các số được giới hạn ở trên
Liam

@Linus và để trả lời câu hỏi đầu tiên của bạn, thông thường tất cả các số sẽ sử dụng cùng một số lượng dư lượng. Điều đó sẽ làm cho câu hỏi đơn giản hơn rất nhiều
Liam

2

Toán học, không chơi gôn

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}]
  • Hàm rns[d_,l_]chuyển đổi một số nguyên cơ số 10 dthành số nguyên RNS có độ dài l.

  • Hàm plus/ times/ subtractthêm / nhân / trừ một số nguyên RNS sang / từ một số khác, cả hai đều có cùng độ dài.

  • Hàm mag[f_]ước tính độ lớn gần đúng của số dấu phẩy động ftheo giới hạn dưới của độ dài biểu diễn RNS của nó.

  • Chức năng ext[m_,n_,i_]tìm ra phần còn lại từ việc phân chia sản phẩm của mPrime[Range@n]bởi Prime[i].

  • Hàm multi[e_,p_,t_]cho số nhân nhỏ nhất mthỏa mãn điều đóDivisible[m*e+t,p]

  • Hàm appx[d_]lấy các 6chữ số đầu tiên của một số nguyên thập phân và đưa ra giá trị dấu phẩy động gần đúng của nó.


Với sự trợ giúp của các chức năng trên, bây giờ chúng tôi có thể giải quyết một vấn đề khó khăn - để xác định độ dài của kết quả .

Đầu tiên, tôi phải làm rõ rằng việc xác định độ dài RNS của một số nguyên không phải là một nhiệm vụ dễ dàng. Đối với các số nguyên nhỏ, chúng ta có thể so sánh chúng trực tiếp với sản phẩm của số nguyên tố. Nhưng đối với các số nguyên rất lớn, vì không được phép tính tích của các số nguyên tố chính xác vô cùng, nên việc so sánh như vậy không còn hiệu quả nữa.

Ví dụ, cho rằng sản phẩm của thủ 1để 303.16*10^46, chiều dài RNS của số nguyên xung quanh 3.16*10^46có thể có thể 29hay 30. Hàm magsẽ cung cấp 29làm tham chiếu cho các số nguyên này, cho thấy cả hai 2930đều có thể.

Khi biết độ lớn, chúng ta chỉ cần biểu diễn trực tiếp số nguyên theo độ lớn đó, hy vọng tính được độ dài thực của nó. Mẹo ở đây là thêm một số số mới vào số ban đầu và sửa đổi biểu diễn RNS của nó, cho đến khi đại diện hoàn toàn bằng không.

Ví dụ, mag[211.]4, và 4đại diện chiều dài của nó là {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

Bằng cách thêm một số số, chúng tôi tăng lên 211số nhỏ nhất chia hết cho 210( 2*3*5*7). Và bây giờ chúng tôi kết luận rằng số ban đầu lớn hơn 210, vì 420bằng "xấp xỉ" hai lần 210. Không khó để tưởng tượng rằng nếu chúng ta bắt đầu từ 209, số cuối cùng là "xấp xỉ" 210.

Hàm length[f_,n_]lấy giá trị dấu phẩy động fđể ước tính cường độ và sửa nó dựa trên biểu diễn RNS của nó 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]]]]

Hàm rnsOperation[a_,b_,op_,rnsop_]cho rnsop[a,b]oplà các hoạt động bình thường tương ứng được sử dụng để có được kết quả gần đúng dựa trên các giá trị dấu phẩy động.

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

Thí dụ

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
Thật không may, các quy tắc được nêu trong trung tâm trợ giúp của chúng tôi yêu cầu tất cả các bài nộp phải là một ứng cử viên nghiêm túc cho các tiêu chí chiến thắng được sử dụng. Đối với một cuộc thi golf mã, điều này có nghĩa là tất cả các bài nộp phải được đánh gôn.
Dennis

@Dennis Tôi biết về quy tắc này. Tuy nhiên, ngay cả khi không chơi golf, tôi nghĩ vấn đề này đủ khó và phức tạp, vì vậy việc giải quyết vấn đề này thay vì chơi golf là mục tiêu của tôi.
njpipe Organ

điều này có thể không được đánh gôn nhưng ngắn ngủi so với chương trình java của tôi: P, mặc dù chương trình của tôi có thể nhanh hơn rất nhiều.
Hy vọng hữu ích

1
Tôi cảm thấy như bạn có khả năng chơi gôn này
Rohan Jhunjhunwala

2

Python 3 , 435 byte

Thử thách này đã có trong danh sách xô của tôi trong một thời gian, nhưng chỉ gần đây thôi: a) Tôi đặt thời gian và sự chú ý vào việc thực sự cố gắng trả lời; và b) thực sự đã thử nghiệm ý tưởng của tôi để tính toán kích thước của các con số (và do đó, số lượng các số nguyên tố bằng cách so sánh nó với kích thước của các nguyên thủy) bằng cách sử dụng một số kết hợp logarit và Định lý còn lại của Trung Quốc. Thật không may, làm việc với logarit trong khi cố gắng xác định số lượng các số nguyên tố, ví dụ, large_primorial + 3yêu cầu, có nghĩa là tôi phải tìm cách giải quyết các vấn đề về dấu phẩy động.

Và đây là câu trả lời của Liam .

Hãy thử trực tuyến!

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

Giải trình

Trong khi cố gắng đưa ra câu trả lời của Liam, cá nhân tôi đã tìm thấy một số lời giải thích được đưa ra một cách khó hiểu, vì vậy đây là nỗ lực của tôi trong việc giải thích thuật toán của anh ấy.

Đầu tiên, chúng tôi nhận được dư lượng của nm.

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

Điều này liên quan đến việc biến tất cả các chữ số của chuỗi đầu vào và biến chúng thành số modulo mỗi số nguyên tố của chúng ta, ví dụ như trong 28, chúng ta sẽ có [(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))

Sau đó, chúng tôi thêm, nhân hoặc trừ các dư lượng theo cặp bằng cách sử dụng eval()

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

Sau đó, chúng tôi nhận được kích thước của dư lượng của chúng tôi, tức là số lượng tối thiểu của số nguyên tố chúng tôi cần.

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

Lấy kích thước là phần khó nhất và tốn nhiều mã nhất. Chúng tôi sử dụng partial_chinesechức năng, giúp chúng tôi u_ixác định trình tự để xác định kích thước. Thêm vào u_itrong một khoảnh khắc.

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

Trình tự u_iđược tính bằng cách lấy mỗi cặn r_i, trừ số tiền u_j * primorial(j) for j in [1, i), và sau đó dividingbằng cách primorial(i), tất cả modulo primes[i]. Đó là, u_i = (r_i - SUM) / primorial(i). Thêm về chức năng nguyên thủy và phân chia của chúng tôi trong một thời điểm.

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)tính toán primorial(j) mod primes[i]. Việc chia modulo bất kỳ số nguyên tố nào pcũng được thực hiện dễ dàng bằng cách kiểm tra các phép nghịch đảo nhân, vì chúng tôi có thể chắc chắn rằng bất kỳ khả năng nào u_icũng 0 <= u_i < pđược đảm bảo là tương ứng với p và do đó được đảm bảo nghịch đảo nhân.

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

Với tất cả những gì đã làm, chúng tôi in chuỗi của chúng tôi và chúng tôi đã hoàn thành.

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

Cái gì tiếp theo

Đây là niềm vui để thực hiện. Tôi vẫn muốn xem liệu tôi có thể sử dụng logarit theo một cách nào đó trong câu trả lời khác không. Và tôi muốn triển khai mã này hoặc một cái gì đó giống như trong ngôn ngữ chơi gôn chức năng, như APL hoặc Jelly. Bất kỳ và tất cả các đề xuất và chỉnh sửa golf đều được chào đón!

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.