Pengantar Teori Komputasi dan Kompleksitas

Diterbitkan: 2022-03-11

Pernahkah Anda bertanya-tanya: Apa sebenarnya perangkat yang Anda gunakan untuk membaca artikel ini? Apa itu Komputer?

Ilmu komputasi sudah ada jauh sebelum perangkat komputasi modern ini dibayangkan. Dalam industri di mana pertanyaan yang lebih sering diajukan berkisar pada bahasa pemrograman, kerangka kerja, dan perpustakaan, kita sering menerima begitu saja konsep dasar yang membuat komputer berdetak.

Tetapi komputer-komputer ini, yang tampaknya memiliki potensi tak terbatas—apakah mereka memiliki keterbatasan? Apakah ada masalah yang tidak dapat diselesaikan oleh komputer?

Teori dan kompleksitas komputasi

Pada artikel ini, kami akan menjawab pertanyaan-pertanyaan ini dengan menjauh dari hal-hal khusus bahasa pemrograman dan arsitektur komputer. Dengan memahami kekuatan dan keterbatasan komputer dan algoritme, kita dapat meningkatkan cara kita berpikir dan alasan yang lebih baik tentang berbagai strategi.

Pandangan abstrak komputasi menghasilkan hasil yang telah teruji oleh waktu, sama berharganya bagi kita hari ini seperti ketika awalnya dikembangkan pada 1970-an.

Komputasi

Apa itu Komputer? Apa itu Masalah?

Di sekolah, kita sering diajari model mental dari masalah dan fungsi yang kira-kira seperti ini:

Fungsi adalah prosedur yang Anda terapkan pada input x untuk menemukan output f(x).

Ternyata definisi matematikanya berbeda:

Fungsi adalah himpunan pasangan terurut sedemikian rupa sehingga elemen pertama dari setiap pasangan berasal dari himpunan X (disebut domain), elemen kedua dari setiap pasangan berasal dari himpunan Y (disebut kodomain atau range), dan setiap elemen dari domain dipasangkan dengan tepat satu elemen rentang.

Itu cukup seteguk. Tapi, apa sebenarnya artinya?

Fungsi

Definisi ini memberitahu kita bahwa komputer adalah mesin untuk fungsi komputasi.

Mengapa?

Karena komputer mengubah input sewenang-wenang menjadi beberapa output. Dengan kata lain, mereka memecahkan masalah. Dua definisi fungsi, yang sangat kita kenal dan yang formal, bertepatan untuk banyak tujuan praktis.

Namun, definisi matematika memungkinkan kita untuk mencapai kesimpulan yang menarik seperti keberadaan fungsi yang tidak dapat dihitung (yaitu, masalah yang tidak dapat dipecahkan):

Sebab, tidak semua fungsi dapat digambarkan sebagai sebuah algoritma.

Aturan Permainan

Untuk membantu membuat argumen kita, mari kita bayangkan komputer sebagai mesin yang mengambil beberapa input, melakukan urutan operasi, dan setelah beberapa waktu, memberikan beberapa output.

Kami akan memanggil input alfabet mesin; yaitu, satu set string karakter dari beberapa set terbatas. Misalnya, alfabet mesin mungkin biner (0s dan 1s) atau mungkin set karakter ASCII. Urutan karakter yang terbatas adalah string—misalnya, “0110.”

Selanjutnya, kami akan merepresentasikan output mesin sebagai keputusan menerima-menolak biner, dikirimkan setelah mesin (semoga) menyelesaikan perhitungannya. Abstraksi ini cocok dengan definisi matematika fungsi dari sebelumnya.

Terima-tolak komputer

Mengingat parameter ini, penting untuk mengkarakterisasi satu jenis lagi: kumpulan string. Mungkin kita peduli dengan set string yang diterima oleh beberapa mesin, atau mungkin kita sedang membangun mesin yang menerima string di set tertentu dan tidak ada yang lain, atau mungkin kita bertanya apakah mungkin merancang mesin yang menerima segala sesuatu di beberapa himpunan tertentu dan tidak ada yang lain.

Dalam semua kasus ini, sekumpulan string disebut bahasa—misalnya, himpunan semua string biner yang mewakili bilangan genap atau himpunan string yang memiliki jumlah karakter genap. Ternyata bahasa, seperti angka, dapat dioperasikan dengan operator seperti penggabungan, gabungan, persimpangan, dan sejenisnya.

Salah satu operator penting adalah operator bintang Kleene yang juga digunakan dengan ekspresi reguler. Ini dapat dianggap sebagai penyatuan semua kemungkinan kekuatan bahasa. Misalnya, jika bahasa kita A adalah himpunan string { '01', '1' }, maka salah satu anggota A* adalah string '0101111'.

Hitungan

Bagian terakhir dari teka-teki sebelum kami membuktikan klaim kami bahwa tidak semua fungsi dapat dihitung adalah konsep keterhitungan. Secara intuitif, bukti kami akan menunjukkan bahwa ada lebih banyak bahasa; yaitu, lebih banyak masalah daripada program yang mungkin untuk menyelesaikannya. Ini berfungsi karena pertanyaan apakah string termasuk dalam suatu bahasa (Ya/Tidak) itu sendiri merupakan masalah.

Lebih tepatnya, bukti kami mengklaim bahwa rangkaian program yang mungkin dapat dihitung tak terbatas sementara rangkaian bahasa di atas alfabet tak terhitung jumlahnya.

Pada titik ini, Anda mungkin berpikir, “Ketakterbatasan adalah ide yang cukup aneh dengan sendirinya; sekarang aku harus berurusan dengan mereka berdua!”

Yah, itu tidak terlalu buruk. Himpunan tak hingga yang dapat dihitung adalah himpunan yang dapat dihitung. Ini mungkin untuk mengatakan ini adalah elemen pertama, ini adalah elemen kedua, dan seterusnya, akhirnya menetapkan nomor untuk setiap elemen himpunan. Ambil himpunan bilangan genap, misalnya. Kita dapat mengatakan bahwa 2 adalah yang pertama, 4 yang kedua, 6 yang ketiga, dan seterusnya. Himpunan seperti itu terhitung tak terbatas atau dapat dihitung.

Dengan beberapa set, seperti bilangan real, tidak peduli seberapa pintar Anda; tidak ada enumerasi. Himpunan ini tak terhitung tak terbatas atau tak terhitung.

Terhitung Banyak Program

Pertama, kami ingin menunjukkan bahwa himpunan program komputer dapat dihitung. Untuk tujuan kami, kami melakukan ini dengan mengamati bahwa himpunan semua string di atas alfabet yang terbatas dapat dihitung. Ini berfungsi karena program komputer adalah string yang terbatas itu sendiri.

Buktinya mudah, dan kami tidak membahas detailnya di sini. Kuncinya adalah bahwa ada banyak program komputer di luar sana seperti halnya, katakanlah, bilangan asli.

Untuk mengulangi:

Himpunan semua string di atas alfabet apa pun (misalnya, kumpulan semua program komputer) dapat dihitung.

Tak terhitung Banyak Bahasa

Mengingat kesimpulan ini, bagaimana dengan himpunan bagian dari string ini? Ditanya dengan cara lain, bagaimana dengan himpunan semua bahasa? Ternyata set ini tidak terhitung.

Himpunan semua bahasa di atas alfabet apa pun tidak terhitung.

Sekali lagi, kami tidak menutupi bukti di sini.

Konsekuensi

Meskipun mereka mungkin tidak segera terlihat, konsekuensi dari tak terhitungnya bahasa dan hitungan set semua program komputer yang mendalam.

Mengapa?

Misalkan A adalah himpunan karakter ASCII; Karakter ASCII hanyalah yang diperlukan untuk membuat program komputer. Kita dapat melihat bahwa himpunan string yang mewakili, katakanlah, program JavaScript, adalah subset dari A* (di sini, * adalah operator bintang Kleene). Pilihan JavaScript adalah sewenang-wenang. Karena set program ini adalah subset dari set yang dapat dihitung, kami memiliki set program JavaScript yang dapat dihitung.

Selain itu, mari kita pertimbangkan bahwa untuk bahasa apa pun L , kita dapat mendefinisikan beberapa fungsi f yang bernilai 1 jika beberapa string x ada di L dan 0 sebaliknya. Semua fungsi tersebut berbeda. Karena ada korespondensi 1:1 dengan himpunan semua bahasa dan karena himpunan semua bahasa tidak dapat dihitung, maka himpunan semua fungsi tersebut tidak dapat dihitung.

Inilah intinya:

Karena himpunan semua program yang valid dapat dihitung tetapi himpunan fungsinya tidak, maka pasti ada beberapa fungsi yang programnya tidak bisa kita tulis.

Kami belum tahu seperti apa fungsi atau masalah ini, tetapi kami tahu itu ada. Ini adalah realisasi yang merendahkan, karena ada beberapa masalah di luar sana yang tidak ada solusinya. Kami menganggap komputer sangat kuat dan mampu, namun beberapa hal bahkan di luar jangkauan mereka.

Sekarang pertanyaannya menjadi, "Seperti apa masalah-masalah ini?" Sebelum kita melanjutkan menjelaskan masalah seperti itu, kita harus terlebih dahulu memodelkan komputasi secara umum.

Mesin Turing

Salah satu model matematika komputer pertama dikembangkan oleh Alan Turing. Model ini, yang disebut mesin Turing, adalah perangkat yang sangat sederhana yang sepenuhnya menangkap gagasan kami tentang komputabilitas.

mesin turing

Input ke mesin adalah pita di mana input telah ditulis. Menggunakan kepala baca/tulis, mesin mengubah input menjadi output melalui serangkaian langkah. Pada setiap langkah, keputusan dibuat tentang apakah dan apa yang harus ditulis ke kaset dan apakah akan memindahkannya ke kanan atau ke kiri. Keputusan ini didasarkan pada dua hal:

  • Simbol saat ini di bawah kepala, dan

  • Status internal mesin, yang juga diperbarui saat simbol ditulis

Itu dia.

Keuniversalan

Pada tahun 1926, Alan Turing tidak hanya mengembangkan mesin Turing tetapi juga memiliki sejumlah wawasan utama lainnya tentang sifat komputasi ketika dia menulis makalahnya yang terkenal, “On Computable Numbers.” Dia menyadari bahwa program komputer itu sendiri dapat dianggap sebagai input ke komputer. Dengan sudut pandang ini, dia memiliki ide bagus bahwa mesin Turing dapat mensimulasikan atau mengeksekusi input tersebut.

Sementara kami menerima ide-ide ini begitu saja hari ini, di masa Turing, ide mesin universal seperti itu adalah terobosan besar yang memungkinkan Turing mengembangkan masalah yang tak terpecahkan.

Tesis Turing-Gereja

Sebelum melanjutkan, mari kita periksa poin penting: Kita tahu mesin Turing adalah model komputasi, tetapi apakah itu cukup umum? Untuk menjawab pertanyaan ini, kita beralih ke Tesis Church-Turing, yang memberikan kepercayaan pada pernyataan berikut:

Segala sesuatu yang dapat dihitung dapat dihitung oleh mesin Turing.

Sementara Turing mengembangkan mesin Turing sebagai model komputasi, Alonzo Church juga mengembangkan model komputasi yang dikenal sebagai kalkulus lambda. Model-model ini sangat kuat, karena keduanya menggambarkan komputasi dan melakukannya dengan cara yang sama dengan komputer mana pun saat ini atau dalam hal ini komputer mana pun yang pernah ada. Ini berarti kita dapat menggunakan mesin Turing untuk menjelaskan masalah tak terpecahkan yang kita cari, mengetahui bahwa temuan kita akan berlaku untuk semua komputer yang mungkin dulu, sekarang, dan seterusnya.

Pengakuan & Decidability

Kita harus membahas sedikit lebih banyak latar belakang sebelum kita menggambarkan secara konkret masalah yang tidak terpecahkan, yaitu konsep pengenal bahasa dan penentu bahasa.

Suatu bahasa dapat dikenali jika ada mesin Turing yang mengenalinya.

dan

Suatu bahasa dapat ditentukan jika ada mesin Turing yang memutuskannya.

Untuk menjadi pengenal suatu bahasa, mesin Turing harus menerima setiap string dalam bahasa tersebut dan tidak boleh menerima apa pun yang tidak ada dalam bahasa tersebut. Ini mungkin menolak atau mengulang string tersebut. Untuk menjadi penentu, mesin Turing harus selalu berhenti pada inputnya baik dengan menerima atau menolak.

Di sini, gagasan untuk menghentikan input sangat penting. Faktanya, kita melihat bahwa penentu lebih kuat daripada yang mengenali. Lebih jauh, suatu masalah dapat dipecahkan, atau dengan kata lain, suatu fungsi dapat ditentukan hanya jika ada mesin Turing yang memutuskan bahasa yang dijelaskan oleh fungsi tersebut.

Ketidakpastian

Jika Anda pernah menulis sebuah program komputer, tentunya Anda pasti tahu bagaimana rasanya duduk di sana hanya menonton komputer memutar rodanya ketika program tersebut dijalankan. Anda tidak tahu apakah program ini hanya memakan waktu lama atau jika ada kesalahan dalam kode yang menyebabkan infinite loop. Anda bahkan mungkin bertanya-tanya mengapa kompiler tidak memeriksa kode untuk melihat apakah kode itu akan berhenti atau berulang selamanya saat dijalankan.

Kompiler tidak memiliki pemeriksaan seperti itu karena itu tidak bisa dilakukan. Bukan karena insinyur kompiler tidak cukup pintar atau kekurangan sumber daya; tidak mungkin memeriksa program komputer sewenang-wenang untuk menentukan apakah program itu berhenti.

Kita bisa membuktikannya dengan menggunakan mesin Turing. Mesin turing dapat digambarkan sebagai string, jadi jumlahnya dapat dihitung. Misalkan M 1 , M 2 , dan seterusnya membentuk himpunan semua mesin Turing. Mari kita definisikan fungsi berikut:

f(i, j) = 1 jika M i menerima <M j >, 0 sebaliknya

Di sini, <M> adalah sintaks untuk "pengkodean string M," dan fungsi ini mewakili masalah keluaran 1 jika M i berhenti dengan menerima M j sebagai masukan dan keluaran 0 sebaliknya. Perhatikan bahwa M saya harus berhenti (yaitu, menjadi penentu). Ini diperlukan karena kami ingin menggambarkan fungsi yang tidak dapat diputuskan (yaitu, masalah yang tidak dapat dipecahkan).

Sekarang, mari kita juga mendefinisikan bahasa L yang terdiri dari pengkodean string mesin Turing yang TIDAK menerima deskripsinya sendiri:

L = { <M> | M tidak menerima <M> }

Misalnya, beberapa mesin M 1 dapat mengeluarkan 0 pada input <M 1 > sementara mesin lain M 2 dapat mengeluarkan 1 pada input <M 2 >. Untuk membuktikan bahwa bahasa ini tidak dapat diputuskan, kami menanyakan apa yang dilakukan M L , mesin yang memutuskan bahasa L, ketika diberi deskripsinya sendiri <M L > sebagai input. Ada dua kemungkinan:

M L menerima < ML >

atau

M L menolak <M L >

Jika M L menerima encodingnya sendiri, maka itu berarti <M L > tidak ada dalam bahasa L; namun, jika itu masalahnya, maka ML seharusnya tidak menerima penyandiannya sejak awal. Di sisi lain, jika M L tidak menerima pengkodean sendiri, maka <M L > dalam bahasa L, jadi M L harus menerima pengkodean string.

Dalam kedua kasus, kami memiliki paradoks, atau dalam istilah matematika, kontradiksi, membuktikan bahwa bahasa L tidak dapat ditentukan; dengan demikian, kami telah menjelaskan masalah pertama kami yang tidak terpecahkan.

Menghentikan Masalah

Meskipun masalah yang baru saja kita uraikan mungkin tampak tidak relevan, masalah tersebut dapat direduksi menjadi masalah tambahan yang tidak dapat dipecahkan dan penting secara praktis, terutama masalah penghentian:

Bahasa pengkodean mesin Turing yang berhenti pada string kosong.

Masalah penghentian berlaku untuk pertanyaan mengapa kompiler tidak dapat mendeteksi loop tak terbatas dari sebelumnya. Jika kita tidak dapat menentukan apakah suatu program berakhir pada string kosong, lalu bagaimana kita dapat menentukan apakah eksekusinya akan menghasilkan infinite loop?

Pada titik ini, sepertinya kita hanya melambaikan tangan untuk mencapai beberapa kesimpulan sederhana; namun, kami benar-benar menyadari bahwa tidak ada mesin Turing yang dapat mengetahui apakah program komputer akan berhenti atau tetap dalam lingkaran selamanya. Ini adalah masalah penting dengan aplikasi praktis, dan tidak dapat diselesaikan pada mesin Turing atau jenis komputer lainnya. IPhone tidak dapat menyelesaikan masalah ini. Desktop dengan banyak inti tidak dapat menyelesaikan masalah ini. Cloud tidak dapat menyelesaikan masalah ini. Bahkan jika seseorang menciptakan komputer kuantum, itu tetap tidak akan dapat memecahkan masalah penghentian.

Ringkasan

Dalam pemeriksaan teori komputabilitas kami, kami telah melihat bagaimana ada banyak fungsi yang tidak dapat dihitung dalam arti kata biasa dengan argumen penghitungan. Kami secara tepat mendefinisikan apa yang kami maksud dengan komputasi, kembali ke inspirasi Turing dari pengalamannya sendiri dengan pena dan kertas untuk memformalkan mesin Turing. Kami telah melihat bagaimana model ini dapat menghitung apa pun yang dapat dilakukan oleh komputer mana pun hari ini atau yang dibayangkan untuk masa depan, dan kami menyadari sekelompok masalah yang tidak dapat dihitung sama sekali.

Namun, komputabilitas memiliki sisi negatifnya. Hanya karena kita bisa memecahkan masalah bukan berarti kita bisa menyelesaikannya dengan cepat. Lagi pula, apa gunanya komputer jika perhitungannya tidak akan selesai sebelum matahari melakukan nova pada kita puluhan juta tahun di masa depan?

Meninggalkan fungsi dan bahasa yang dapat dihitung, sekarang kita membahas kompleksitas komputasi, survei komputasi yang efisien, dan masalah P vs. NP yang terkenal.

Kompleksitas

Lambat vs Cepat

Ilmuwan komputer mengenali berbagai kelas masalah, dan dua kelas yang kami pedulikan termasuk masalah yang dapat diselesaikan komputer dengan cepat atau efisien yang dikenal sebagai P dan masalah yang solusinya dapat diverifikasi dengan cepat tetapi tidak dapat diperoleh dengan cepat yang dikenal sebagai NP .

Misalnya, Anda bertanggung jawab untuk mengembangkan algoritme untuk layanan kencan online dan seseorang mengajukan pertanyaan, “Dapatkah semua orang mendapatkan kencan?” Jawabannya bermuara pada memasangkan individu yang kompatibel sehingga setiap orang dipasangkan. Ternyata ada algoritma yang efisien untuk memecahkan masalah ini. Masalah ini ada di himpunan P .

Nah, bagaimana jika kita ingin mengidentifikasi klik terbesar di antara pengguna kita? Yang kami maksud dengan klik adalah jaringan individu terbesar yang semuanya kompatibel satu sama lain. Ketika jumlah pengguna rendah, masalah ini dapat diselesaikan dengan cepat. Kami dapat dengan mudah mengidentifikasi, katakanlah, sebuah klik dengan 3 pengguna. Namun, ketika kami mulai mencari kelompok yang lebih besar, masalahnya menjadi semakin sulit untuk dipecahkan. Masalah ini ada di set NP .

Definisi Formal

P adalah himpunan masalah yang dapat diselesaikan dalam waktu polinomial. Artinya, jumlah langkah komputasi dibatasi oleh fungsi polinomial sehubungan dengan ukuran masalah. Kita tahu bahwa "Bisakah semua orang mendapatkan kencan?" pertanyaan, juga dikenal sebagai masalah pencocokan bipartit, ada di P .

NP adalah himpunan masalah yang dapat diverifikasi dalam waktu polinomial. Ini termasuk setiap masalah di P, tentu saja; namun, kami tidak tahu apakah penahanan ini ketat. Kami mengetahui masalah yang dapat diverifikasi secara efisien tetapi tidak dapat dipecahkan secara efisien, tetapi kami tidak tahu apakah masalahnya benar-benar tidak dapat diselesaikan. Masalah klik adalah salah satu masalah tersebut. Kami tahu kami dapat memverifikasi solusi secara efisien, tetapi kami tidak tahu pasti apakah kami dapat menyelesaikan masalah secara efisien.

Terakhir, NP-complete adalah kumpulan masalah yang merupakan masalah tersulit di NP . Mereka disebut sebagai yang paling sulit karena masalah apa pun di NP dapat secara efisien diubah menjadi NPC . Akibatnya, jika seseorang mengidentifikasi solusi yang efisien untuk masalah di NPC , maka seluruh kelas NP akan diserap oleh P . Masalah klik juga ada di NPC .

P vs. NP

Jadi, kita sampai pada masalah P vs. NP . Banyak ilmuwan komputer dan matematikawan sangat percaya bahwa P dan NP tidak sama. Jika ya, implikasinya akan sangat besar. Sebagian besar infrastruktur digital saat ini bergantung pada fakta bahwa ada masalah di NP yang tidak ada di P . Jika bukan itu masalahnya, maka metode kriptografi, misalnya, akan runtuh dalam semalam, memungkinkan seseorang yang memiliki solusi efisien untuk masalah NPC untuk menumbangkan protokol keamanan yang paling ketat sekalipun.

Kehalusan Tractability

Untuk pemula ilmu komputer, perbedaan antara masalah pencocokan dan klik mungkin tidak tampak seperti masalah besar. Faktanya, perbedaan antara masalah di P dan masalah di NP bisa sangat tipis. Mampu membedakannya penting bagi siapa pun yang merancang algoritma di dunia nyata.

Pertimbangkan masalah jalur terpendek. Mengingat dua lokasi, tujuannya adalah untuk mengidentifikasi jalur terpendek di antara mereka. Sebuah iPhone menghitung ini dalam hitungan milidetik. Ini adalah masalah yang dapat ditangani secara komputasi.

Di sisi lain, pertimbangkan masalah salesman perjalanan, di mana tujuannya adalah untuk mengunjungi subset tujuan yang mungkin berakhir di titik asal sambil menempuh jarak sesingkat mungkin. Masalah ini mirip dengan masalah jalur terpendek tetapi NP-lengkap; itu juga menjelaskan mengapa logistik rantai pasokan adalah industri bernilai miliaran dolar.

Kita sebenarnya bisa lebih halus. Alih-alih menanyakan jalur terpendek (P), kita dapat meminta jalur terpanjang tanpa siklus. Ternyata masalah jalur terpanjang juga NP-lengkap.

Ada banyak lagi contoh perbedaan halus ini, termasuk identifikasi penutup simpul dalam grafik bipartit vs. umum atau kepuasan rumus boolean dengan dua vs. tiga literal per klausa. Intinya adalah tidak jelas apakah masalah ada di P atau NP, dan inilah mengapa analisis waktu berjalan adalah keterampilan yang penting. Jika algoritma yang harus dirancang adalah untuk masalah di P, maka kita tahu ada solusi yang efisien. Jika di sisi lain masalahnya ada di NP, maka kita memiliki alasan kuat untuk menentang pencarian solusi, karena algoritme, umumnya, akan memakan waktu terlalu lama untuk menyelesaikan masalah.

Ringkasan

Dalam pemeriksaan kompleksitas ini, kami mendefinisikan kelas masalah P dan NP. P secara informal mewakili masalah yang dapat diselesaikan secara efisien oleh komputer sementara NP mewakili masalah yang dapat diverifikasi secara efisien.

Belum ada yang bisa membuktikan bahwa P tidak sama dengan NP. Apakah kedua kelas masalah ini ekuivalen dikenal sebagai masalah P vs. NP, dan ini adalah masalah terbuka yang paling penting dalam ilmu komputer teoretis saat ini jika tidak dalam semua matematika. Faktanya, pada tahun 2000, Clay Math Institute menyebut masalah P vs. NP sebagai salah satu dari tujuh pertanyaan terbuka paling penting dalam matematika dan telah menawarkan hadiah jutaan dolar untuk bukti yang menentukan solusi untuk masalah ini.

Kesimpulan

Dalam artikel ini, kami mempelajari bidang komputasi dan kompleksitas, menjawab pertanyaan besar seperti, "Apa itu komputer?" Meskipun detailnya bisa sangat banyak, ada beberapa hal penting yang perlu diingat:

  • Ada beberapa hal yang tidak bisa dihitung, seperti masalah berhenti.

  • Ada beberapa hal yang tidak bisa dihitung secara efisien, seperti masalah di NPC.

Lebih penting daripada detail adalah cara berpikir tentang komputasi dan masalah komputasi. Dalam kehidupan profesional kita dan bahkan dalam kehidupan sehari-hari, kita mungkin menemukan masalah yang belum pernah terlihat sebelumnya, dan kita dapat menggunakan alat dan teknik yang teruji dan benar untuk menentukan tindakan terbaik.


Bacaan Lebih Lanjut di Blog Teknik Toptal:

  • Bagaimana Pendekatan Menulis Penerjemah Dari Awal