Julia. String dan Metaprogramming


Kami terus mempelajari bahasa tujuan umum yang muda dan menjanjikan, Julia . Kali ini kita akan lebih memperhatikan garis, mulai langkah-langkah pemalu ke dunia metaprogramming dan mengajar penerjemah untuk melakukan operasi simbolis (Hanya ada dua gambar di bawah luka, tetapi banyak gula sintaksis)


Garis


Variabel string dibuat dengan menyertakan karakter dalam tanda kutip ganda, karakter tunggal diatur ke karakter tunggal dari tipe char . Penggabungan string dilakukan menggunakan perkalian "*":


cats = 4 s1 = "How many cats "; s2 = "is too many cats?"; s3 = s1*s2 Out[]: "How many cats is too many cats?" 

Yaitu, tindakan eksponensial seperti ini:


 s1^3 Out[]: "How many cats How many cats How many cats " 

Di sisi lain, baris diindeks:


 s1[3] Out[]: 'w': ASCII/Unicode U+0077 (category Ll: Letter, lowercase) s1[5:13] Out[]: "many cats" s1[13:-1:5] Out[]: "stac ynam" s2[end] Out[]: '?': ASCII/Unicode U+003f (category Po: Punctuation, other) 

String dapat dibuat dari string dan tipe lainnya:


 s4 = string(s3, " - I don't know, but ", cats, " is too few.") #   s4 = "$s3 - I don't know, but $cats is too few." Out[]: "How many cats is too many cats? - I don't know, but 4 is too few." 

Ada banyak fungsi yang berguna di gudang senjata, misalnya, mencari elemen:


 findfirst( isequal('o'), s4 ) Out[]: 4 findlast( isequal('o'), s4 ) Out[]: 26 findnext( isequal('o'), s4, 7 ) #      Out[]: 11 

Jadi, Anda dapat dengan mudah mengimplementasikan Cipher Caesar


 caesar(X, n) = prod( [x += n for x in X] ) str3 = "    "; caesar(str3, 3) Out[]: "####" str4 = caesar(str3, -32) Out[]: "\0\0\0\0" "Latin letters go before Cyrillic" < str4 < str3 Out[]: true 

Di sini, proses terjadi pada semua karakter string, n ditambahkan ke kode masing-masing. Ternyata array karakter char yang direkatkan bersama oleh fungsi prod () , yang mengimplementasikan penggandaan semua elemen dari array yang diterima.


Komputasi Karakter


Julia sudah memiliki paket berbagai tingkat kesiapan untuk perhitungan simbolik, misalnya:



tapi kami entah bagaimana belajar untuk menempelkan string, mengapa tidak menerapkan tindakan simbolis dengan matriks. Katakanlah kita ingin menambahkan dua array yang berbeda:


 m1 = [1 1 "a"; 1 0 1] Out[]: 2×3 Array{Any,2}: 1 1 "a" 1 0 1 m3 = [1 2 "ln(3)"; 2 1 0] Out[]: 2×3 Array{Any,2}: 1 2 "ln(3)" 2 1 0 m1+m3 Out[]: MethodError: no method matching +(::String, ::String) ... 

Untuk memulai, jalankan pena lucu Anda di operator dasar:


 import Base: *, -, + 

Jika dalam C ++ mutilasi seperti itu disebut operator overloading, maka di sini kita cukup menambahkan metode untuk fungsi yang ada.


 +(a::String, b::String) = a * "+" * b Out[]: + (generic function with 164 methods) 

Lebih dari satu dan seratus metode, dan di sini adalah satu lagi yang akan hidup sampai akhir sesi.


 m1+m3 Out[]: 2×3 Array{Any,2}: 2 3 "a+ln(3)" 3 1 1 

Sekarang subport perkalian, yaitu, kami menentukan kasus mengalikan string dengan angka:


 function *(a::String, b::Number) if b == 0 0 else if b == 1 a else "$b" * "*(" * a * ")" end end end Out[]: * (generic function with 344 methods) 

Yaitu, jika string dikalikan dengan nol, kita mendapatkan nol, jika dengan satu, maka kita mendapatkan string yang sama, dan dalam kasus lain kita membutakan angka dengan tanda "*" dan string (kita menambahkan tanda kurung sehingga tidak ada ketidakpastian dengan karakter). Secara pribadi, saya suka tampilan fungsi satu baris, yang berguna untuk operator ternary:


 *(a::String, b::Number) = (b==0) ? 0 : (b==1) ? a : "$b" * "*(" * a * ")" Out[]: 

Kami akan mengekspos kasus yang tersisa ke eksekusi yang sama dan mendapatkan sesuatu seperti:


 import Base: *, -, + +(a::String, b::String) = a * "+" * b *(a::String, b::String) = a * "*(" * b * ")" *(a::String, b::Number) = (b==0) ? 0 : (b==1) ? a : "$b" * "*(" * a * ")" *(b::Number, a::String) = (b==0) ? 0 : (b==1) ? a : "$b" * "*(" * a * ")" +(a::String, b::Number) = (b==0) ? a : a * "+" * "$b" +(b::Number, a::String) = (b==0) ? a : a * "+" * "$b" -(b::Number, a::String) = (b==0) ? "-" * a : "$b" * "-" * a -(a::String, b::Number) = (b==0) ? a : a * "-" * "$b" # m1 = [1 1 "a"; 1 0 1] m2 = [2 0 2; 2 "b" 2; 2 2 2] m1*m2 Out[]: 2×3 Array{Any,2}: "2*(a)+4" "b+2*(a)" "2*(a)+4" 4 2 4 

Dan mari kita hitung determinannya! Tetapi karena fungsi bawaannya rumit, metode kami tidak cukup untuk memberinya matriks dengan baris - kami mendapatkan kesalahan. Itu berarti kita menyiapkan milik kita sendiri menggunakan simbol Levi-Civita


 ε = zeros(Int, 3,3,3) ε[1,2,3] = ε[2,3,1] = ε[3,1,2] = 1 ε[3,2,1] = ε[1,3,2] = ε[2,1,3] = -1 ε Out[]: 3×3×3 Array{Int64,3}: [:, :, 1] = 0 0 0 0 0 1 0 -1 0 [:, :, 2] = 0 0 -1 0 0 0 1 0 0 [:, :, 3] = 0 1 0 -1 0 0 0 0 0 

Formula produk campuran


 kiri[ veca vecb vecc kanan]= sumi,j,k=13 varepsilonijkaibjck


dapat digunakan untuk menemukan determinan matriks 3x3, memahami a , b , c sebagai baris pertama, kedua dan ketiga (kolom), masing-masing


 detrmnt(arr) = sum( ε[i,j,k]*arr[1,i]*arr[2,j]*arr[3,k] for i in 1:3, j in 1:3, k in 1:3 ) detrmnt(["a" 2 "b"; 1 0 1; "c" 2 "d"]) Out[]: "2*(c)+2*(b)+2*(-1*(a))+-2*(d)" 

Sangat mudah untuk menebak bahwa untuk matriks lebih rumit, semuanya akan terlihat sangat tidak trivial - ada banyak tanda kurung dan penggandaan angka, jadi alangkah baiknya untuk membuat beberapa fungsi yang melakukan penonaktifan. Ini akan menjadi pekerjaan rumah Anda.
Selain itu, penggandaan array lebih rumit:


 Rx = [1 0 0 0; 0 "cos(ϕ)" "sin(ϕ)" 0; 0 "-sin(ϕ)" "cos(ϕ)" 0; 0 0 0 1]; Rz = ["cos(ϕ)" "sin(ϕ)" 0 0; "-sin(ϕ)" "cos(ϕ)" 0 0; 0 0 1 0; 0 0 0 1]; T = [1 0 0 0; 0 1 0 0; 0 0 1 0; "x0" "y0" "z0" 1]; Rx*Rz Out[]: MethodError: no method matching zero(::String) ... 

akan memberikan kesalahan. Bisnis yang jelas, untuk matriks dengan dimensi lebih dari tiga, metode yang lebih maju dan kompleks digunakan, panggilan yang kami tidak “kelebihan beban”. Ya, kita akan membahas metode yang sudah ada dengan pengganda kita sendiri (ini, tentu saja, tidak direkomendasikan, terutama dalam program yang kurang lebih kompleks, di mana konflik internal kemungkinan besar akan muncul).


 function *( a::Array{Any,2}, b::Array{Any,2} ) if size(a)[2] == size(b)[1] res = Array{Any}(undef, size(a)[1], size(b)[2] ) fill!(res, 0) for i = 1:size(a)[1], j = 1:size(b)[2], k = 1:size(a)[2] res[i,j] = res[i,j] + a[i,k]*b[k,j] end res else error("Matrices must have same size mult") end end Out[]: * (generic function with 379 methods) 

Sekarang Anda dapat melipatgandakan dengan aman:


 X = Rx*Rz*T Out[]: 4?4 Array{Any,2}: "cos(ϕ)" "sin(ϕ)" 0 0 "cos(ϕ)*(-sin(ϕ))" "cos(ϕ)*(cos(ϕ))" "sin(ϕ)" 0 "-sin(ϕ)*(-sin(ϕ))" "-sin(ϕ)*(cos(ϕ))" "cos(ϕ)" 0 "x0" "y0" "z0" 1 

Mari kita perhatikan matriks ini, tetapi untuk sekarang, kita akan memulai langkah pertama


Metaprogramming


Kutipan


Julia mendukung metaprogramming. Ini mirip dengan pemrograman simbolis, tempat kami menangani ekspresi (mis. 6 * 7) yang bertentangan dengan nilai-nilai (mis. 42). Menarik operasi ke penerjemah, kami segera mendapatkan hasilnya, yang, seperti yang telah kita lihat, dapat dielakkan dengan bantuan garis:


 x = "6*7" Out[]: "6*7" 

String dapat diubah menjadi ekspresi menggunakan parse () , dan kemudian dievaluasi menggunakan eval () :


 eval(Meta.parse(ans)) Out[]: 42 

Kenapa semua kesulitan ini? Kuncinya adalah ketika kita memiliki ekspresi , kita dapat memodifikasinya dengan berbagai cara menarik:


 x = replace(x, "*" => "+") eval(Meta.parse(x)) Out[]: 13 

Untuk menghindari rewel dengan garis, operator wajah mengerutkan kening disediakan :()


 y = :(2^8-1) Out[]: :(2^8-1) eval(y) Out[]: 255 

Anda dapat "mengutip" ekspresi, fungsi, blok kode ...


 quote x = 2 + 2 hypot(x, 5) end Out[]: quote #= In[13]:2 =# x = 2 + 2 #= In[13]:3 =# hypot(x, 5) end :(function mysum(xs) sum = 0 for x in xs sum += x end end) Out[]: :(function mysum(xs) #= In[14]:2 =# sum = 0 #= In[14]:3 =# for x = xs #= In[14]:4 =# sum += x end end) 

Dan sekarang kita akan mem-parsing matriks yang dihitung sebelumnya (lebih baik memulai sesi baru setelah bagian sebelumnya, kelebihan saya yang jelek dapat dengan mudah bertentangan dengan paket plug-in):


 X = [ "cos(ϕ)" "sin(ϕ)" 0 0; "cos(ϕ)*(-sin(ϕ))" "cos(ϕ)*(cos(ϕ))" "sin(ϕ)" 0; "-sin(ϕ)*(-sin(ϕ))" "-sin(ϕ)*(cos(ϕ))" "cos(ϕ)" 0; "x0" "y0" "z0" 1; ] for i = 1:size(X,1), j = 1:size(X,2) if typeof(X[i,j]) == String X[i,j] = Meta.parse(X[i,j]) end end X Out[]: 4×4 Array{Any,2}: :(cos(ϕ)) :(sin(ϕ)) 0 0 :(cos(ϕ) * -(sin(ϕ))) :(cos(ϕ) * cos(ϕ)) :(sin(ϕ)) 0 :(-(sin(ϕ)) * -(sin(ϕ))) :(-(sin(ϕ)) * cos(ϕ)) :(cos(ϕ)) 0 :x0 :y0 :z0 1 

Seperti yang sudah beberapa orang duga, ini adalah matriks transformasi, kami telah memilah ini , hanya sekarang untuk koordinat tiga dimensi. Kami akan menghitung nilai-nilai spesifik:


 ϕ = 20*pi/180 x0 = 4 y0 = -0.5 z0 = 1.3 Xtr = [ eval(x) for x in X] Out[]: 4×4 Array{Real,2}: 0.939693 0.34202 0 0 -0.321394 0.883022 0.34202 0 0.116978 -0.321394 0.939693 0 4 -0.5 1.3 1 

Matriks ini berputar di sekitar sumbu X dan Z oleh 20 circdan transfer ke \ vec {R} = \ {x0, y0, z0 \}


 x = [-1 -1 1 1 -1 -1 1 1 -1 -1]; y = [-1 -1 -1 -1 -1 1 1 1 1 -1]; z = [1 -1 -1 1 1 1 1 -1 -1 -1] R = [ x' y' z' ones( length(x) ) ] plot(x', y', z', w = 3) 


 R2 = R*Xtr plot!( R2[:,1], R2[:,2], R2[:,3], w = 3) 


Buah dari Pohon Ekspresi


Seperti yang telah kita lihat, string mendukung "interpolasi," yang memungkinkan kita untuk dengan mudah membuat string besar dari komponen yang lebih kecil.


 x = "" print("$x $x $x  ...") Out[]:     ... 

Dengan kutipan (quotes) - cerita yang sama:


 x = :(6*7) y = :($x + $x) Out[]: :(6 * 7 + 6 * 7) eval(y) Out[]: 84 

Akar semua Eval


eval() tidak hanya mengembalikan nilai ekspresi. Mari kita coba mengutip deklarasi fungsi:


 ex = :( cats() = println("Meow!") ) cats() Out[]: UndefVarError: cats not defined 

Sekarang hidupkan dia:


 eval(ex) Out[]: cats (generic function with 1 method) cats() Out[]: Meow! 

Menggunakan interpolasi, kita dapat membangun definisi fungsi dengan cepat; Bahkan, kita bisa langsung melakukan sejumlah fungsi.


 for name in [:dog, :bird, :mushroom] println(:($name() = println($("I'm $(name)!")))) end Out[]: dog() = begin #= In[27]:2 =# println("I'm dog!") end bird() = begin #= In[27]:2 =# println("I'm bird!") end mushroom() = begin #= In[27]:2 =# println("I'm mushroom!") end for name in [:dog, :bird, :mushroom] eval(:($name() = println($("I'm $(name)!")))) end dog() Out[]: I'm dog! mushroom() Out[]: I'm mushroom! 

Ini bisa sangat berguna saat membungkus API (misalnya, dari pustaka C atau melalui HTTP). API sering mendefinisikan daftar fungsi yang tersedia, sehingga Anda dapat menangkapnya dan membuat seluruh shell sekaligus! Lihat Contoh Clang.jl, TensorFlow.jl, atau Aljabar Linear Dasar.


Dosa asal


Ini adalah contoh yang lebih praktis. Pertimbangkan definisi fungsi sin() didasarkan pada seri Taylor:


sin(x)= sumk=1 infty frac(1)k(1+2k)!x1+2k


 mysin(x) = sum((-1)^k/factorial(1+2*k) * x^(1+2k) for k = 0:5) mysin(0.5), sin(0.5) Out[]: (0.4794255386041834, 0.479425538604203) using BenchmarkTools @benchmark mysin(0.5) Out[]: BenchmarkTools.Trial: memory estimate: 112 bytes allocs estimate: 6 -------------- minimum time: 1.105 μs (0.00% GC) median time: 1.224 μs (0.00% GC) mean time: 1.302 μs (0.00% GC) maximum time: 9.473 μs (0.00% GC) -------------- samples: 10000 evals/sample: 10 

Sekarang jauh lebih lambat dari yang seharusnya. Alasannya adalah bahwa kita beralih ke k, yang relatif mahal. Tulisan eksplisit jauh lebih cepat:


 mysin(x) = x - x^3/6 + x^5/120 # + ... 

Tapi ini membosankan untuk ditulis dan tidak lagi seperti seri Taylor yang asli. Selain itu, ada risiko tinggi anakan. Apakah ada cara untuk membunuh kelinci dengan dua tembakan? Bagaimana dengan Julia yang menulis kode ini kepada kami? Pertama, pertimbangkan versi simbolik dari fungsi +.


 plus(a, b) = :($a + $b) plus(1, 2) Out[]: :(1 + 2) 

Dengan plus() kita dapat melakukan banyak hal menarik, misalnya, jumlah simbolis:


 reduce(+, 1:10) Out[]: 55 reduce(plus, 1:10) Out[]: :(((((((((1 + 2) + 3) + 4) + 5) + 6) + 7) + 8) + 9) + 10) eval(ans) Out[]: 55 reduce(plus, [:(x^2), :x, 1]) Out[]: :((x ^ 2 + x) + 1) 

Ini memberi kita bagian penting dari teka-teki, tetapi kita juga perlu mencari tahu apa yang kita simpulkan. Mari kita buat versi simbolis dari seri Taylor di atas yang menginterpolasi nilai k.


 k = 2 :($((-1)^k) * x^$(1+2k) / $(factorial(1+2k))) Out[]: :((1 * x ^ 5) / 120) 

Sekarang Anda dapat memusatkan elemen baris seperti pada konveyor:


 terms = [:($((-1)^k) * x^$(1+2k) / $(factorial(1+2k))) for k = 0:5] Out[]: 6-element Array{Expr,1}: :((1 * x ^ 1) / 1) :((-1 * x ^ 3) / 6) :((1 * x ^ 5) / 120) :((-1 * x ^ 7) / 5040) :((1 * x ^ 9) / 362880) :((-1 * x ^ 11) / 39916800) 

Dan rangkum mereka:


 reduce(plus, ans) Out[]: :((((((1 * x ^ 1) / 1 + (-1 * x ^ 3) / 6) + (1 * x ^ 5) / 120) + (-1 * x ^ 7) / 5040) + (1 * x ^ 9) / 362880) + (-1 * x ^ 11) / 39916800) :(mysin(x) = $ans) Out[]: :(mysin(x) = begin #= In[52]:1 =# (((((1 * x ^ 1) / 1 + (-1 * x ^ 3) / 6) + (1 * x ^ 5) / 120) + (-1 * x ^ 7) / 5040) + (1 * x ^ 9) / 362880) + (-1 * x ^ 11) / 39916800 end) eval(ans) mysin(0.5), sin(0.5) Out[]: (0.4794255386041834, 0.479425538604203) @benchmark mysin(0.5) Out[]: BenchmarkTools.Trial: memory estimate: 0 bytes allocs estimate: 0 -------------- minimum time: 414.363 ns (0.00% GC) median time: 416.328 ns (0.00% GC) mean time: 431.885 ns (0.00% GC) maximum time: 3.352 μs (0.00% GC) -------------- samples: 10000 evals/sample: 201 

Tidak buruk untuk implementasi yang naif! Foton tidak punya waktu untuk menjalankan satu setengah ratus meter, dan sinus sudah dihitung!
Inilah saatnya untuk mengakhiri. Bagi mereka yang ingin memilah topik ini lebih lanjut, saya akan menyarankan tutorial interaktif yang dilakukan di Jupyter , terjemahan yang sebagian besar digunakan di sini, kursus video dari situs resmi, dan bagian yang sesuai dari dokumentasi. Untuk pendatang baru, saya sarankan Anda berjalan di sekitar hub , karena sudah ada jumlah material yang dapat diterima.

Source: https://habr.com/ru/post/id431438/


All Articles