Giải mã Morse


24

Tôi đã trở nên hoảng hốt với sự căm ghét không gian ngày càng tăng và câu trả lời này đã truyền cảm hứng cho tôi để đảm bảo mã Morse được an toàn khỏi việc loại bỏ khoảng trắng ngấm ngầm này.

Vì vậy, nhiệm vụ của bạn sẽ là tạo ra một chương trình có thể dịch thành công mã Morse với tất cả các khoảng trắng được loại bỏ.

mã Morse

Quy tắc:

  1. Đầu vào sẽ là một chuỗi chỉ bao gồm dấu gạch ngang và dấu chấm (ASCII 2D và 2E). Đầu ra không được xác định cho đầu vào có chứa bất kỳ ký tự nào khác. Vui lòng sử dụng bất kỳ phương pháp nào thuận tiện cho ngôn ngữ bạn chọn để nhận đầu vào (stdin, tệp văn bản, người dùng nhanh chóng, bất cứ điều gì). Bạn có thể giả sử rằng đầu vào mã Morse chỉ bao gồm các chữ cái AZ và không bắt buộc phải có số hoặc dấu chấm phù hợp.

  2. Đầu ra chỉ nên bao gồm các từ có trong tệp từ điển này (một lần nữa, vui lòng sử dụng bất kỳ phương pháp thuận tiện nào để truy cập tệp từ điển). Tất cả các giải mã hợp lệ phải là đầu ra cho thiết bị xuất chuẩn, và tất cả các dấu chấm và dấu gạch ngang trong đầu vào phải được sử dụng. Mỗi từ phù hợp trong đầu ra phải được phân tách bằng một khoảng trắng và mỗi giải mã có thể nên được phân tách bằng một dòng mới. Bạn có thể sử dụng chữ hoa, chữ thường hoặc đầu ra hỗn hợp tùy tiện.

  3. Tất cả các hạn chế về sơ hở tiêu chuẩn áp dụng với một ngoại lệ như đã lưu ý ở trên, bạn có thể truy cập tệp từ điển được tham chiếu trong yêu cầu 2 thông qua kết nối internet nếu bạn thực sự muốn. Rút ngắn URL là chấp nhận được, tôi tin rằng goo.gl/46I35Z có thể là ngắn nhất.

  4. Đây là mã golf, mã ngắn nhất thắng.

Lưu ý: Đăng tệp từ điển lên Pastebin đã thay đổi tất cả các kết thúc dòng thành chuỗi 0A 0E kiểu Windows. Chương trình của bạn có thể giả sử kết thúc dòng chỉ với 0A, chỉ 0E hoặc 0A 0E.

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

Đầu vào:

......-...-.. ---. -----.-..-..- ..

Đầu ra phải chứa:

Chào thế giới

Đầu vào:

. - ..-. ----- ..-.. ----- ..-. - .. - ... --- .. - ...-.... ... -.-..-.-. ---- ... -. ---.-....-.

Đầu ra phải chứa:

câu đố lập trình và mã golf

Đầu vào:

-.....-.-..-..-.-.-. - ....-. ---. --- ...-. ---- ..-.- --.. ---. - .... --- ...-..-.-......-... --- ..-. --- ..-- ---.

Đầu ra phải chứa:

The quick brown fox jumps over the lazy dog


3
Làm thế nào bạn có thể nói giữa AN (.- -.)EG (. --.)?
xem

2
@Sieg - Đầu ra sau đó sẽ cần bao gồm cả giải mã hợp lệ.
Comitern

1
@Dennis - Ahhh ... Tôi cá là Pastebin hoặc trình duyệt của tôi đã làm điều đó. Tệp nguồn của tôi không có chúng. Bạn có thể thay đổi dấu phân cách dòng thành một hệ thống phù hợp, không có thay đổi nào khác. Tôi sẽ chỉnh sửa câu hỏi khi tôi không dùng điện thoại.
Comitern

2
@Falko đó là hành vi đúng. Lưu ý rằng vấn đề nói rằng đầu ra của bạn phải bao gồm "hello world", không giới hạn ở đó. Nó sẽ in tất cả các giải mã hợp lệ.
hobbs

2
(gần như thơ mộng, thực sự)
hobbs

Câu trả lời:


5

Hồng ngọc, 210

(1..(g=gets).size).map{|x|puts IO.read(?d).split.repeated_permutation(x).select{|p|p.join.gsub(/./,Hash[(?a..?z).zip"(;=/%513':07*)29@-+&,4.<>?".bytes.map{|b|('%b'%(b-35))[1,7].tr'01','.-'}])==g}.map{|r|r*' '}}

Nếu có tồn tại một thực hành như "chơi gôn quá mức", tôi nghi ngờ rằng tôi đã tham gia khoảng thời gian này. Giải pháp này tạo ra một mảng các mảng hoán vị lặp đi lặp lại của tất cả các từ trong từ điển , từ độ dài 1 cho đến độ dài của đầu vào. Cho rằng "a" là từ ngắn nhất trong tệp từ điển và mã của nó dài hai ký tự, nó sẽ đủ để tạo ra các hoán vị có độ dài lên đến một nửa kích thước của đầu vào, nhưng việc thêm vào /2tương đương với mức độ chi tiết trong miền này, Vì vậy, tôi đã kiềm chế.

Khi mảng hoán vị đã được tạo ( NB : nó có độ dài 45404 104 trong trường hợp đầu vào ví dụ pangrammatic), mỗi mảng hoán vị được nối và các ký tự chữ cái của nó được thay thế bằng mã Morse tương đương thông qua (Regexp, Hash)biến thể khá thuận tiện của #gsubphương pháp; chúng tôi đã tìm thấy một giải mã hợp lệ nếu chuỗi này bằng với đầu vào.

Từ điển được đọc (nhiều lần) từ một tệp có tên "d" và đầu vào không được chứa một dòng mới.

Chạy ví dụ (với một từ điển sẽ cho chương trình cơ hội chiến đấu kết thúc trước cái chết nóng của vũ trụ):

$ cat d
puzzles
and
code
dummy
golf
programming
$ echo -n .--..-.-----..-..-----..-.--..--...---..--...-.......--.-..-.-.----...--.---.-....-. | ruby morse.rb
programming puzzles and code golf
^C

5

Haskell, 296 ký tự

  • Tệp từ điển: phải là tệp văn bản có tên "d"
  • Đầu vào: stdin, có thể có một dòng mới nhưng không có khoảng trắng bên trong
main=do f<-readFile"d";getLine>>=mapM(putStrLn.unwords).(words f&)
i!""=[i]
(i:j)!(p:q)|i==p=j!q
_!_=[]
_&""=[[]]
d&i=do
w<-d
j<-i!(w>>=((replicate 97"X"++words".- -... -.-. -.. . ..-. --. .... .. .--- -.- .-.. -- -. --- .--. --.- .-. ... - ..- ...- .-- -..- -.-- --..")!!).fromEnum)
n<-d&j
[w:n]

Giải thích về các yếu tố:

  • mainđọc từ điển, đọc stdin, thực thi &và định dạng đầu ra &với khoảng trắng thích hợp.
  • (replicate 97"X"++words".- -... -.-. -.. . ..-. --. .... .. .--- -.- .-.. -- -. --- .--. --.- .-. ... - ..- ...- .-- -..- -.-- --..")!!)(một biểu thức bên trong định nghĩa của &) là một danh sách có chỉ số là mã ký tự (97 là mã của 'a') và các giá trị là chuỗi Morse.
  • !(một hàm có tên là toán tử infix) khớp với một chuỗi với tiền tố; nếu có tiền tố, nó sẽ trả về phần còn lại trong danh sách một thành phần (thành công trong danh sách đơn nguyên), nếu không thì danh sách trống (thất bại trong danh sách đơn nguyên)
  • &sử dụng danh sách đơn nguyên cho việc thực hiện lệnh nondeterministic nó

    1. chọn một mục của d(một từ trong từ điển),
    2. sử dụng !để khớp với dạng Morse của từ đó ( w>>=((…)!!).fromEnumtương đương với concatMap (((…)!!) . fromEnum) w) so với chuỗi đầu vào i,
    3. gọi chính nó ( d&j) để khớp với phần còn lại của chuỗi và
    4. trả về kết quả có thể dưới dạng một danh sách các từ w:n, trong danh sách đơn nguyên [w:n](ngắn hơn, cụ thể tương đương return (w:n)).

    Lưu ý rằng mỗi dòng sau dòng 6 là một phần của dobiểu thức bắt đầu trên dòng 6; việc này có chính xác cùng số lượng ký tự như sử dụng dấu chấm phẩy trên một dòng, nhưng dễ đọc hơn, mặc dù bạn chỉ có thể làm điều đó một lần trong một chương trình.

Chương trình này cực kỳ chậm. Nó có thể được thực hiện nhanh hơn (và lâu hơn một chút) một cách dễ dàng bằng cách lưu trữ các từ được viết sai bên cạnh bản gốc trong danh sách thay vì tính toán lại chúng ở mỗi mẫu khớp. Điều tiếp theo cần làm là để lưu trữ các từ trong một cây nhị phân keyed bằng các ký hiệu Morse (2-ary Trie ) để tránh cố gắng chi nhánh không cần thiết.

Nó có thể được thực hiện ngắn hơn một chút nếu tệp từ điển không chứa các ký hiệu không được sử dụng, chẳng hạn như "-", cho phép loại bỏ có replicate 97"X"++lợi cho việc thực hiện .(-97+)trước !!.


Con trai chết tiệt, thật thông minh. +1 cho bạn.
Alexander Craggie

1
Bạn có biết rằng (+(-97))có thể được viết lại như (-97+)?
tự hào

Bạn nên xóa định nghĩa thứ ba của h và thay vào đó thêm |0<1=[]vào định nghĩa thứ hai
tự hào

2
Sử dụng interactvà giành được 12 ký tự. interact$unlines.map unwords.(words f&)
gxtaillon

1
Bạn sẽ có thể thay thế concatMapbằng>>=
tự hào

3

Con trăn - 363 345

Mã số:

D,P='-.';U,N='-.-','.-.'
def s(b,i):
 if i=='':print b
 for w in open('d').read().split():
  C=''.join([dict(zip('abcdefghijklmnopqrstuvwxyz-\'23',[P+D,D+3*P,U+P,'-..',P,D+N,'--.',4*P,2*P,P+3*D,U,N+P,2*D,D+P,D*3,'.--.',D+U,N,P*3,D,'..-',3*P+D,'.--','-..-',U+D,'--..']+['']*4))[c]for c in w]);L=len(C)
  if i[:L]==C:s(b+' '+w,i[L:])
s('',input())

Giải trình:

Từ điển phải được lưu trữ dưới dạng tệp văn bản thuần có tên "d".

D, P, UNchỉ là một số biến helper cho một định nghĩa ngắn hơn của bảng tra cứu morse.

s(i)là một hàm đệ quy in phần thông báo đã dịch trước đó pvà mỗi bản dịch hợp lệ của phần mã còn lại i: Nếu itrống, chúng tôi đã đạt đến phần cuối của mã và bchứa toàn bộ bản dịch, do đó chúng tôi chỉ đơn giản là printnó. Mặt khác, chúng tôi kiểm tra từng từ wtrong từ điển d, dịch nó thành mã morse Cvà, nếu mã còn lại ibắt đầu bằng C, chúng tôi thêm từ đó wvào đầu dịch bvà gọi hàm sđệ quy trên phần còn lại.

Lưu ý về hiệu quả:

Đây là một phiên bản khá chậm nhưng chơi golf. Đặc biệt là tải từ điển và xây dựng bảng tra cứu morse ( dict(zip(...))) trong mỗi lần lặp (để tránh nhiều biến hơn) tốn rất nhiều chi phí. Và sẽ hiệu quả hơn khi dịch tất cả các từ trong tệp từ điển trước một lần và không phải trong mỗi lần đệ quy theo yêu cầu. Những ý tưởng này dẫn đến phiên bản sau với thêm 40 ký tự nhưng tăng tốc đáng kể :

d=open('d').read().split()
D,P='-.';U,N='-.-','.-.'
M=dict(zip('abcdefghijklmnopqrstuvwxyz-\'23',[P+D,D+3*P,U+P,'-..',P,D+N,'--.',4*P,2*P,P+3*D,U,N+P,2*D,D+P,D*3,'.--.',D+U,N,P*3,D,'..-',3*P+D,'.--','-..-',U+D,'--..']+['']*4))
T=[''.join([M[c]for c in w])for w in d]
def s(b,i):
 if i=='':print b
 for j,w in enumerate(d):
  C=T[j];L=len(C)
  if i[:L]==C:s(b+' '+w,i[L:])
s('',input())

Bạn có thể lưu 2 ký tự bằng cách thay thế .startswith(C)bằng [:len(C)]==C.
Greg Hewgill

Ồ cảm ơn nhé! Điều này trở nên khá kỳ lạ, vì việc tải toàn bộ từ điển trong mỗi lần đệ quy sẽ lưu các ký tự - và làm chậm thuật toán một lần nữa.
Falko

@GregHewgill: Vâng, đó là những gì tôi đã làm ban đầu. Tôi chỉ chỉnh sửa câu trả lời của tôi để giải quyết cả hai phiên bản.
Falko

1
Bạn có thể tạo ra kết quả thú vị hơn (từ dài hơn) nhanh hơn bằng cách sắp xếp từ điển theo độ dài giảm dần của từ. d=sorted(open('d').read().split(),key=len,reverse=1)Hoặc, làm điều đó bên ngoài bằng cách sắp xếp trước từ điển của bạn theo cách đó.
Greg Hewgill

Heck, nếu bạn có thể định dạng lại tệp từ điển, định dạng tệp đó là từ điển Python được tính toán trước và M=eval(open('d').read()):)
Greg Hewgill

3

Perl (5.10+), 293 ký tự

Tệp từ điển phải được lưu dưới dạng "d" (và phải ở định dạng unix nếu bạn không muốn CR giữa các từ), nhập morse trên stdin, không có dòng mới (sử dụng echo -n).

open D,d;chomp(@w=<D>);@m{a..z}=map{substr(sprintf("%b",-61+ord),1)=~y/01/.-/r}
'BUWI?OKMATJQDCLSZGE@FNHVXY'=~/./g;%w=map{$_,qr#^\Q@{[s/./$m{$&}/reg]}#}@w;
@a=[$i=<>];while(@a){say join$",@{$$_[1]}for grep!$$_[0],@a;
@a=map{$x=$_;map{$$x[0]=~$w{$_}?[substr($$x[0],$+[0]),[@{$$x[1]},$_]]:()}@w}@a}

(ngắt dòng chỉ để định dạng).

Mã bị đánh cắp:

# Read the word list
open my $dictionary, '<', 'd';
chomp(my @words = <$dictionary>);

# Define morse characters
my %morse;
@morse{'a' .. 'z'} = map {
  $n = ord($_) - 61;
  $bits = sprintf "%b", $n;
  $bits =~ tr/01/.-/;
  substr $bits, 1;
} split //, 'BUWI?OKMATJQDCLSZGE@FNHVXY';

# Make a hash of words to regexes that match their morse representation
my %morse_words = map {
  my $morse_word = s/./$morse{$_}/reg;
  ($_ => qr/^\Q$morse_word/)
} @words;

# Read the input
my $input = <>;

# Initialize the state
my @candidates = ({ remaining => $input, words => [] });

while (@candidates) {
  # Print matches
  for my $solution (grep { $_->{remaining} eq '' } @candidates) {
    say join " ", @{ $solution->{words} }; 
  } 
  # Generate new solutions
  @candidates = map {
    my $candidate = $_;
    map {
      $candidate->{remaining} =~ $morse_words{$_}
        ? {
          remaining => substr( $candidate->{remaining}, $+[0] ),
          words => [ @{ $candidate->{words} }, $_ ],
        }
        : ()
    } @words
  } @candidates;
}

Modus Operandi:

Ký hiệu mã Morse được lưu trữ bằng cách thay đổi "." và "-" thành các chữ số nhị phân 0 và 1, đặt trước "1" (để các dấu chấm hàng đầu không bị ngấu nghiến), chuyển đổi số nhị phân thành số thập phân, sau đó mã hóa ký tự có giá trị 61 cao hơn (giúp tôi có được tất cả ký tự có thể in và không có gì cần gạch chéo).

Tôi nghĩ rằng đây là một loại vấn đề phân vùng, và xây dựng một giải pháp dựa trên đó. Đối với mỗi từ trong từ điển, nó xây dựng một đối tượng regex sẽ khớp (và bắt giữ) biểu diễn morse không giới hạn của từ đó ở đầu chuỗi. Sau đó, nó bắt đầu một tìm kiếm đầu tiên bằng cách tạo một trạng thái không khớp với các từ và có toàn bộ đầu vào là "đầu vào còn lại". Sau đó, nó mở rộng từng trạng thái bằng cách tìm kiếm các từ khớp với ở đầu của đầu vào còn lại và tạo các trạng thái mới thêm từ đó vào các từ phù hợp và loại bỏ morse khỏi đầu vào còn lại. Các tiểu bang không có đầu vào còn lại là thành công và có danh sách các từ được in. Các trạng thái không khớp với bất kỳ từ nào (kể cả những từ thành công) không tạo ra trạng thái con.

Lưu ý rằng trong phiên bản không được chỉnh sửa, các trạng thái được băm để dễ đọc; trong phiên bản golf, chúng là mảng (cho mã ngắn hơn và tiêu thụ ít bộ nhớ hơn); slot [0]là đầu vào còn lại và slot [1]là các từ khớp.

Bình luận

Điều này là vô duyên chậm. Tôi tự hỏi nếu có một giải pháp không. Tôi đã cố gắng xây dựng một cái bằng Marpa (trình phân tích cú pháp Earley với khả năng đưa ra nhiều phân tích cho một chuỗi đầu vào) nhưng hết bộ nhớ chỉ xây dựng ngữ pháp. Có lẽ nếu tôi đã sử dụng API cấp thấp hơn thay vì đầu vào BNF ...


Nếu tôi thêm yêu cầu tương tự như Kevin Reid (không có dòng mới trong đầu vào), tôi có thể lưu 7 ký tự bằng cách xóa chomp(). Tôi có nên
hobbs

"Hãy sử dụng bất kỳ phương pháp thuận tiện".
Comitern

Cạo 2 byte bằng ordthay vì ord$_. Cạo 1 byte nói join$"thay vìjoin" "
Zaid

2

Haskell - 418

Vấn đề giải mã này có thể được giải quyết hiệu quả bằng lập trình động. Tôi biết đây là một loại tiền mã hóa, nhưng tôi yêu mã nhanh.

Giả sử chúng ta có chuỗi đầu vào s, sau đó chúng ta xây dựng một mảng dp, dp[i]là danh sách tất cả các kết quả giải mã hợp lệ của chuỗi con s[:i]. Đối với mỗi từ wtrong từ điển, đầu tiên chúng ta mã hóa nó thành mw, sau đó chúng ta có thể tính một phần dp[i]từ dp[i - length(mw)]nếu s[i - length(mw):i] == mw. Sự phức tạp thời gian của việc xây dựng dpO({count of words} {length of s} {max word length}). Cuối cùng, dp[length(s)]yếu tố cuối cùng, là những gì chúng ta cần.

Thực tế, chúng ta không cần lưu trữ toàn bộ giải mã như là yếu tố của mỗi dp[i]. Những gì chúng ta cần là từ giải mã cuối cùng. Điều này làm cho việc thực hiện nhanh hơn rất nhiều. Mất chưa đến 2 giây để hoàn thành trường hợp "hello world" trên máy tính xách tay i3 của tôi. Đối với các trường hợp khác được đăng trong câu hỏi, chương trình sẽ không hoàn thành theo nguyên tắc vì có quá nhiều đầu ra.

Sử dụng kỹ thuật lập trình động, chúng ta có thể tính toán số lượng giải mã hợp lệ . Bạn có thể tìm thấy mã ở đây . Các kết quả:

input: ......-...-..---.-----.-..-..-..
count: 403856

input: .--..-.-----..-..-----..-.--..--...---..--...-.......--.-..-.-.----...--.---.-....-.
count: 2889424682038128

input: -.....--.-..-..-.-.-.--....-.---.---...-.----..-.---..---.--....---...-..-.-......-...---..-.---..-----.
count: 4986181473975221635

Bị đánh cắp

import Control.Monad

morseTable :: [(Char, String)]
morseTable = zip ['a'..'z'] $ words ".- -... -.-. -.. . ..-. --. .... .. .--- -.- .-.. -- -. --- .--. --.- .-. ... - ..- ...- .-- -..- -.-- --.."

wordToMorse :: String -> Maybe String
wordToMorse xs = return . concat =<< mapM (`lookup` morseTable) xs

slice :: (Int, Int) -> [a] -> [a]
slice (start, end) = take (end - start) . drop start

decode :: String -> String -> IO ()
decode dict s = trace (length s) [] where
  dict' = [(w, maybe "x" id . wordToMorse $ w) | w <- lines dict]
  dp = flip map [0..length s] $ \i -> [(j, w) |
        (w, mw) <- dict', let j = i - length mw, j >= 0 && mw == slice (j, i) s]

  trace :: Int -> [String] -> IO ()
  trace 0 prefix = putStrLn . unwords $ prefix
  trace i prefix = sequence_ [trace j (w:prefix) | (j, w) <- dp !! i]

main :: IO ()
main = do
  ws <- readFile "wordlist.txt"
  decode ws =<< getLine

Chơi gôn

import Control.Monad
l=length
t=zip['a'..]$words".- -... -.-. -.. . ..-. --. .... .. .--- -.- .-.. -- -. --- .--. --.- .-. ... - ..- ...- .-- -..- -.-- --.."
h s=return.concat=<<mapM(`lookup`t)s
f d s=g(l s)[]where g 0 p=putStrLn.unwords$p;g i p=sequence_[g j(w:p)|(j,w)<-map(\i->[(j,w)|(w,m)<-[(w,maybe"x"id.h$w)|w<-lines d],let j=i-l m,j>=0&&m==(take(i-j).drop j$s)])[0..l s]!!i]
main=do d<-readFile"d";f d=<<getLine

Vui mừng khi thấy một giải pháp hợp lý hiệu quả. Bây giờ tôi chỉ cần hiểu nó :)
hobbs

2

PHP, 234 226 byte

function f($s,$r=""){$s?:print"$r
";foreach(file(d)as$w){for($i=+$m="";$p=@strpos(__etianmsurwdkgohvf_l_pjbxcyzq,$w[$i++]);)$m.=strtr(substr(decbin($p),1),10,"-.");0!==strpos($s,$m)?:g(substr($s,strlen($m)),$r.trim($w)." ");}}

hàm đệ quy, lấy từ điển từ một tệp có tên d.
Thất bại cho mỗi từ trong từ điển có chứa một chữ cái.

Bạn có thể sử dụng bất kỳ tên tệp nào nếu bạn define ("d","<filename>");trước khi gọi hàm.

Thêm 2 hoặc 3 byte để thực hiện nhanh hơn:
Xóa $s?:print"$r\n";, chèn $s!=$m?trước 0!==:print$r.$wtrước ;}}.

phá vỡ

function f($s,$r="")
{
    $s?:print"$r\n";            // if input is empty, print result
    foreach(file(d)as$w)        // loop through words
    {
        // translate to morse:
        for($i=+$m="";              // init morse to empty string, $i to 0
                                        // loop while character is in the string
            $p=@strpos(__etianmsurwdkgohvf_l_pjbxcyzq,$w[$i++])
        ;)
            $m.=                        // 4. append to word morse code
                strtr(
                    substr(
                        decbin($p)      // 1: convert position to base 2
                    ,1)                 // 2: substr: remove leading `1`
                ,10,"-.")               // 3. strtr: dot for 0, dash for 1
            ;
        0!==strpos($s,$m)           // if $s starts with $m
            ?:f(                        // recurse
                substr($s,strlen($m)),  // with rest of $s as input
                $r.trim($w)." "         // and $r + word + space as result
            )
        ;
    }
}

1

Groovy 377 337

r=[:];t={x='',p=''->r[s[0]]=p+x;s=s.substring(1);if(p.size()<3){t('.',p+x);t('-',p+x)}};s='-eishvuf-arl-wpjtndbxkcymgzqo--';t()
m=('d'as File).readLines().groupBy{it.collect{r.get(it,0)}.join()}
f={it,h=[]->it.size().times{i->k=it[0..i]
if(k in m){m[k].each{j->f(it.substring(i+1),h+[j])}}}
if(it.empty){println h.join(' ')}}
f(args[0])

Ghi chú

Dict phải là một tập tin có tên d. Chuỗi morse được truyền bằng dòng lệnh. ví dụ:

% groovy morse.groovy ......-...-..---.-----.-..-..-.. | grep 'hello world'
hello world

Để "nén mã morse" tôi đang sử dụng cây nhị phâ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.