2 poin oleh GN⁺ 2024-01-15 | 1 komentar | Bagikan ke WhatsApp
  • Bahkan masalah dengan banyak kasus khusus seperti Advent of Code 2023 Day 12 pun bisa ditangani dengan pemrograman dinamis jika kita menemukan struktur yang berulang kali menyelesaikan submasalah yang sama
  • Kuncinya adalah memecah masalah dengan rekursi, lalu mengurangi perhitungan berulang dengan memoisasi, dan memindahkannya ke perhitungan iteratif yang mengisi nilai yang diperlukan sesuai urutan dependensi
  • Contoh Fibonacci menunjukkan bahwa rekursi naif berulang kali mengevaluasi f(1), tetapi jika memakai cache, kita hanya perlu mengevaluasi n + 1 nilai dari f(0) hingga f(n)
  • Levenshtein distance dan Advent of Code Day 12 menunjukkan proses mengubah pemanggilan rekursif menjadi pengisian array dengan menggunakan indeks status seperti panjang string dan indeks aturan sebagai kunci cache
  • Jika terbiasa dengan pemrograman dinamis, kita bukan hanya bisa meningkatkan performa, tetapi juga bisa melihat status antara dan hubungan dependensi dari algoritme, serta lebih mudah menemukan peluang optimasi memori

Namanya membingungkan, tetapi idenya sederhana

  • Nama “dynamic programming” tidak berhubungan langsung dengan makna modern seperti “gaya pemrograman” atau “dynamic typing”
  • Intinya adalah pendekatan perancangan algoritme yang membagi masalah menjadi masalah serupa yang lebih kecil, lalu menggunakan kembali hasilnya
  • Ada catatan editorial yang menambahkan bahwa istilah ini masuk akal jika dilihat dari makna historis “programming”
  • Titik awalnya biasanya berupa bentuk yang memecah masalah menjadi masalah kecil, seperti fungsi rekursif
  • Jika submasalah yang sama muncul berkali-kali, maka secara alami diperlukan caching untuk menyimpan hasil perhitungan dan memakainya kembali

Caching dan iterasi lewat Fibonacci

  • Fungsi Fibonacci didefinisikan sebagai f(n) = f(n - 1) + f(n - 2), dan implementasi rekursif naif menghitung nilai yang sama berulang kali
  • f(1) adalah nilai yang benar-benar dijumlahkan ke hasil akhir, sehingga makin besar f(n), makin cepat pula jumlah evaluasi pada rekursi naif meningkat
  • Jika hasilnya di-cache atau dimemoisasi, kita tidak perlu lagi menghitung ulang f(4), f(3), dan f(2) yang sudah pernah dihitung
  • Dengan cara ini, kita hanya mengevaluasi total 7 nilai dari f(0) sampai f(6), dan secara umum berkurang menjadi n + 1 evaluasi
  • Selangkah lebih jauh, jika kita mengisi nilai yang diperlukan secara berurutan mulai dari f(0) dan f(1), pemanggilan rekursif pun hilang
    • F[2] = F[1] + F[0]
    • F[3] = F[2] + F[1]
    • Dengan cara yang sama, hitung hingga F[6] = 8
  • Pada Fibonacci, kita bahkan tidak memerlukan seluruh array; cukup simpan nilai sebelumnya dan nilai sebelum itu
  • Alur ini menunjukkan jalur sistematis dari definisi matematis menuju implementasi iteratif

Meluas ke contoh edit distance

  • Edit distance antara dua string adalah jumlah minimum operasi edit yang diperlukan untuk mengubah satu string menjadi string lain
  • Masalahnya berubah tergantung jenis edit yang diizinkan
    • Jika hanya substitusi karakter yang diizinkan: Hamming distance
    • Jika penyisipan dan penghapusan juga diizinkan: Levenshtein distance
  • Levenshtein distance dapat dibagi menjadi masalah yang lebih kecil berdasarkan karakter terakhir dari dua string A, B
    • Jika karakter terakhir sama, abaikan kedua karakter itu dan gunakan distance untuk sisa string
    • Jika karakter terakhir berbeda, pilih biaya minimum di antara substitusi, penghapusan, dan penyisipan
    • Jika A kosong, semua karakter B harus disisipkan, sehingga biayanya b
    • Jika B kosong, semua karakter A harus dihapus, sehingga biayanya a
  • Jika definisi ini langsung dipindahkan ke rekursi Python, performanya akan sangat lambat untuk string panjang atau string dengan banyak perbedaan
  • Jika Fibonacci berkembang kira-kira menjadi dua cabang di tiap tingkat pohon pemanggilan, rekursi ini dalam beberapa kasus berkembang menjadi tiga cabang
  • Dengan menambahkan functools.cache di Python, hasil perhitungan untuk kombinasi substring yang sama bisa digunakan kembali
  • Implementasi yang lebih baik tidak terus-menerus membuat string baru, melainkan hanya meneruskan string asli A, B serta panjang substring a, b
  • Pada tahap akhir, kita membuat sendiri array cache dua dimensi, lalu mengisinya berurutan agar cache[a][b] = levenstein(A[:a], B[:b])
  • Versi iteratif akan menelusuri a dan b dari 0 hingga panjang string, sambil merujuk ke nilai pada baris sebelumnya dan kolom sebelumnya yang sudah terisi

Penerapan pada Advent of Code 2023 Day 12

  • Soal Advent of Code 12 Desember 2023 adalah masalah menyelesaikan nonogram satu dimensi
  • Contoh input berbentuk .??..??...?##. 1,1,3, dan ? bisa menjadi . atau #
  • Pendekatan brute force memakai backtracking, tetapi jika ada n tanda tanya, kita harus mengevaluasi 2^n kandidat, sehingga ukurannya tumbuh eksponensial
  • Muncul struktur di mana submasalah yang sama berulang
    • ..#..??...?##. (1),1,3
    • .#...??...?##. (1),1,3
    • Jika bagian depan yang sudah diproses dibuang, masing-masing menjadi masalah yang hampir sama seperti .??...?##. 1,3 dan ..??...?##. 1,3
  • Fungsi backtracking dasar menerima conditions dan rules, lalu menghitung jumlah susunan yang mungkin
    • Jika tidak ada aturan tersisa, periksa apakah masih ada # pada kondisi yang tersisa
    • Jika tidak ada kondisi tersisa, periksa apakah masih ada aturan tersisa
    • Jika karakter saat ini adalah . atau ?, lanjutkan dengan melewati satu posisi
    • Jika karakter saat ini adalah # atau ?, periksa ukuran aturan berikutnya dan kondisi pemisah, lalu pindah ke status berikutnya
  • Di Python, memoisasi bisa diterapkan hanya dengan menambahkan @cache
  • Untuk mengubahnya menjadi pemrograman dinamis, kita tidak lagi mengoper potongan string dan aturan, melainkan memakai offset string i dan offset aturan j sebagai status
  • Setelah itu, kita membuat cache[i][j] secara langsung, dan mengganti rekursi dengan perhitungan iteratif menggunakan cara mengisi indeks dalam urutan terbalik
  • Contoh implementasi Rust disediakan melalui tautan Rust implementation di artikel

Apa yang terlihat saat cache diisi langsung

  • Versi pemrograman dinamis untuk Advent of Code Day 12 bisa tampak lebih lambat daripada versi memoization
  • Perbedaan ini mungkin disebabkan oleh implementasi Python yang belum dioptimalkan
  • Saat menyusun cache secara langsung, kita bisa lebih jelas melihat nilai mana yang benar-benar diperlukan
  • Pada masalah Day 12, versi pemrograman dinamis memperlihatkan bahwa hanya kolom sebelumnya yang diperlukan
  • Karena itu, array dua dimensi dapat diganti menjadi dua array satu dimensi yang mewakili kolom sebelumnya dan kolom saat ini

Masalah latihan dan kesimpulan

  • Pemrograman dinamis memang tidak sepele, tetapi juga bukan teknik yang mustahil diakses oleh kebanyakan programmer
  • Jika memahami cara membagi masalah menjadi masalah-masalah kecil, dalam banyak situasi memoization saja sudah bisa jauh lebih baik daripada implementasi naif
  • Jika makin mahir, kita bisa memahami satu keluarga algoritme, menangkap trade-off dengan lebih baik, dan menemukan optimasi tambahan
  • Beberapa masalah berikut disarankan untuk latihan
  • Setelah mengimplementasikannya, jangan lupa benchmarking dan profiling

1 komentar

 
GN⁺ 2024-01-15
Opini Hacker News
  • Saya suka bahwa tulisan ini menyoroti bahwa algoritma dynamic programming pada dasarnya hanyalah cara cerdas untuk melakukan cache pada rekursi. Berdasarkan pengalaman saya, menemukan solusi rekursif terlebih dahulu adalah titik awal terbaik untuk menemukan solusi dynamic programming, dan begitu menemukannya, memoization mudah dilakukan serta bisa memberi peningkatan kecepatan yang besar
    Kadang bahkan bisa lebih cepat daripada dynamic programming bottom-up, karena hanya menghitung solusi yang benar-benar diperlukan. Intinya, tidak masalah jika pohon pemanggilan memiliki banyak submasalah, tetapi jumlah submasalah yang berbeda harus relatif sedikit. Tidak ada alasan untuk melakukan cache pada hasil yang hanya dibutuhkan sekali, dan kesulitannya ada pada membagi masalah awal menjadi jumlah submasalah berbeda yang cukup sedikit

    • Bagian bahwa submasalah yang berbeda harus relatif sedikit adalah intinya. Apakah keseluruhan algoritmanya rekursif atau iteratif itu sekunder, dan dynamic programming biasanya lebih sering terlihat dalam algoritma rekursif
    • Penjelasan bahwa “dynamic programming adalah cara melakukan cache pada rekursi” adalah momen yang membuat saya benar-benar paham. Saat kuliah, mungkin karena pemrograman prosedural masih dominan waktu itu, contoh mengisi tabel secara bottom-up di buku teks terasa seperti sihir
      Secara praktis, karena tail call elimination tidak selalu diterapkan, memang masuk akal melakukannya seperti itu, tetapi saya merasa akan lebih baik jika lebih dulu mempelajarinya dari sudut pandang yang lebih intuitif: cache rekursif top-down
    • Saat pertama kali mempelajarinya, saya sempat berpikir kalau fiturnya semewah ini, bukankah seharusnya disebut memoization array atau memoization call stack? Menurut saya nama “dynamic programming” seharusnya disisakan untuk sesuatu yang lebih baik
    • Menurut saya, melihat dynamic programming sekadar sebagai rekursi yang diberi memoization adalah kesalahpahaman yang tersebar luas. Jika mempelajarinya seperti itu, akan sangat sulit memahami soal dynamic programming bertipe mengisi array dua dimensi
      Misalnya, melihat seri “Best Time to Buy and Sell Stock” di LeetCode, untuk soal seperti https://leetcode.com/problems/best-time-to-buy-and-sell-stoc... rasanya pendekatan mengisi array jauh lebih alami. Saya belum pernah menyelesaikannya dengan rekursi, dan juga tidak yakin apakah ada solusi rekursif yang alami
      Tautan di atas adalah III, tetapi bagi yang baru pertama mencoba, mulai dari soal pertama https://leetcode.com/problems/best-time-to-buy-and-sell-stoc... bagus sebagai pengantar dynamic programming
    • Mengatakan “dynamic programming itu cuma caching/memoization” mirip dengan mengatakan “investasi itu cuma membeli barang lalu menjualnya nanti.” Secara teknis mungkin ada benarnya sampai batas tertentu, tetapi itu terlalu banyak melewatkan kompleksitas dan kesulitan topiknya, sehingga bisa terdengar lebih konyol daripada memberi wawasan
  • Asal-usul nama “dynamic programming” berasal dari penemunya, Richard Bellman. Pada 1950 di RAND, ia sedang mencari nama untuk proses pengambilan keputusan bertahap, dan konon Menteri Pertahanan saat itu, Wilson, sangat membenci kata “research” secara patologis, sementara kata “mathematics” bahkan lebih harus dihindari
    Bellman membutuhkan nama yang bisa menyembunyikan dari Wilson dan Angkatan Udara bahwa di dalam RAND ia sebenarnya sedang mengerjakan matematika. Jadi ia memilih “programming” karena membahas perencanaan, pengambilan keputusan, dan pemikiran, sementara “planning” tidak cocok karena berbagai alasan; lalu menambahkan “dynamic”, yang punya makna tepat dalam fisika klasik, untuk memuat konsep bertahap dan perubahan terhadap waktu
    Ia juga menyukai fakta bahwa “dynamic” sebagai kata sifat sulit dipakai dengan makna negatif, dan karena nama itu juga sulit ditentang oleh anggota parlemen, ia memakai dynamic programming sebagai nama yang mencakup aktivitasnya
    Sumber: https://alliance.seas.upenn.edu/~cis520/dynamic/2021/wiki/in...

  • Saya suka cara tulisan ini pertama-tama mengungkap masalah secara rekursif, lalu menambahkan caching secara bertahap, dan pada akhirnya mengurangi ukuran cache sebatas yang diperlukan
    Saya sering mencoba langsung menuju solusi dynamic programming lalu mentok, atau memaksakan terlalu banyak usaha agar bisa berjalan. Ke depannya saya berniat memaksa diri mengikuti tahapannya secara berurutan

    • Berdasarkan pengalaman saya, jika dynamic programming diajarkan langsung, rasanya seperti teka-teki. Jika dijelaskan bertahap mengapa tabel dipakai, lalu konsep itu dihubungkan dengan caching, pemahamannya menjadi jauh lebih baik
  • Salah satu penerapan keren dari dynamic programming adalah penyelarasan berpasangan pada sekuens nukleotida/protein
    https://en.wikipedia.org/wiki/Sequence_alignment
    https://en.wikipedia.org/wiki/Needleman%E2%80%93Wunsch_algor...
    https://en.wikipedia.org/wiki/Smith%E2%80%93Waterman_algorit...

    • Menurut saya algoritma-algoritma ini termasuk salah satu yang paling penting dalam bioinformatika/biologi. Cakupan penerapannya sangat luas
  • Ada seorang profesor algoritma yang sangat bagus, dan beliau lulusan UCLA. Kelas dynamic programming-nya luar biasa: pertama dimulai dari masalah yang solusi sederhananya punya kompleksitas waktu eksponensial, lalu masalahnya dipecah menjadi masalah-masalah yang lebih kecil untuk menurunkan kompleksitas ke tingkat polinomial, kemudian menerapkan memoization sehingga turun lagi menjadi linear
    Andai saja bisa ingat masalah apa yang dipakai waktu itu

    • Kandidatnya antara lain deret Fibonacci, masalah kembalian koin, 0/1 knapsack problem, perkalian rantai matriks, longest common subsequence, longest increasing subsequence, masalah shortest path seperti Floyd-Warshall, dan edit distance (Levenshtein distance)
      Semuanya adalah contoh klasik yang solusi naifnya tidak efisien dan dapat ditingkatkan secara besar dengan dynamic programming
    • Di dalam artikelnya juga sudah dicantumkan beberapa, dan itu adalah masalah yang umum terlihat di kuliah atau praktikum. Misalnya longest common subsequence, longest common substring, line warp, subset sum, partition, dan knapsack problem
      Untuk contoh lebih banyak, lihat https://en.wikipedia.org/wiki/Dynamic_programming#Algorithms...
    • Selain masalah yang disebut orang lain, bisa juga itu masalah scheduling. Misalnya mengoptimalkan N event yang saling tumpang tindih secara waktu, jadwal kuliah, atau proses CPU, berdasarkan kriteria seperti throughput
      Setahu saya, jika ditambahkan constraint khusus seperti “dua mata kuliah ini harus diambil bersamaan”, masalahnya menjadi jauh lebih kompleks dan lebih sulit ditangani dibanding dynamic programming biasa
    • Apakah beliau belajar di UCLA di bawah Kang?
  • Sepertinya situs asli tidak sanggup menahan traffic, jadi saya tinggalkan link arsip
    https://web.archive.org/web/20240114111200/https://qsantos.f...

  • Berkat dynamic programming, jumlah posisi Go yang legal dapat dihitung, dan nilainya adalah angka 171 digit
    Cara naif membutuhkan waktu 3^(n^2) karena memeriksa semua kemungkinan posisi pada papan Go n×n, tetapi dynamic programming pada dasarnya menghilangkan satu dimensi sehingga kompleksitas waktunya turun menjadi O(n^5 * 5.4^n), dan kompleksitas ruangnya menjadi O(n * 5.4^n)
    https://tromp.github.io/go/legal.html
    https://tromp.github.io/go/gostate.pdf

  • Nama “Dynamic Programming” bisa terasa janggal karena programming di sini bukan berarti bidang pemrograman. Dalam kasus ini, maknanya lebih dekat ke optimisasi, mirip seperti linear programming
    Dynamic programming dapat dilihat sebagai metode untuk menyelesaikan masalah pengambilan keputusan dalam waktu diskret, yaitu memilih urutan optimal {a_t} yang memaksimalkan \sum_t u_t(a_t) di bawah constraint. Caranya adalah mendefinisikan fungsi nilai V* sebagai V*(t) = max_{a_t}{ u_t(a_t) + V*(t-1) }, sehingga dimensi masalah optimisasi berkurang drastis

    • Sebenarnya asal-usul resmi namanya https://en.wikipedia.org/wiki/Dynamic_programming#History cukup lucu. Bellman menyukai “dynamic” karena itu adalah kata sifat yang mustahil dipakai dengan makna negatif, dan menganggapnya sebagai nama yang bahkan anggota parlemen pun tidak bisa menolaknya
    • Saat orang lain memakai istilah “dynamic programming”, kadang terasa seperti pamer agar terlihat pintar. Padahal sebenarnya yang dipakai hanyalah pendekatan alami dan intuitif ketika menyadari bahwa sebuah masalah dapat dipecah menjadi submasalah yang semakin kecil, tetapi kemudian dikatakan seolah-olah telah “menggunakan” suatu teknik khusus
    • Menarik bahwa dulu pekerjaan menghitung sesuatu, seperti masalah optimisasi, jauh lebih dominan dalam gagasan tentang apa yang akan dilakukan dengan komputer. Sekarang kebanyakan yang dilakukan adalah penyimpanan dan pengambilan data serta networking, dan kalaupun ada komputasi di dalamnya, biasanya terasa sudah terenkapsulasi dengan baik
    • Istilah “optimisasi” juga menimbulkan salah paham serupa. Dulu saya pernah mengambil kelas ilmu komputer bernama “optimization” dan mengharapkan sesuatu yang sama sekali berbeda
    • Jika ditarik lebih jauh ke belakang, “programming” sebenarnya menjelaskan konsep ini dengan tepat. Yang sekarang kita sebut “programming” sebetulnya adalah menulis kode, dan dapat dibagi menjadi cabang-cabang seperti functional, declarative, dan procedural programming. Di bawah payung itu masih ada jauh lebih banyak hal
  • Saat mendengar “pemrograman dinamis”, apakah salah jika menganggapnya sekadar memoization? Bagian yang terlewat mungkin adalah cara memecah masalah secara cerdas agar memoization bisa dipakai

    • Memoization adalah teknik yang lebih umum. Sering kali ini sebatas meng-cache hasil yang sudah dihitung untuk berjaga-jaga jika dibutuhkan lagi nanti
      Pemrograman dinamis lebih dekat dengan memoization yang sistematis. Kita menyelesaikan submasalah yang makin besar hingga mencapai solusi untuk masalah keseluruhan. Istilah “algoritma induksi” juga cukup cocok, karena algoritma pemrograman dinamis yang tipikal pada dasarnya mirip dengan pembuktian induksi matematika. Sayangnya, istilah itu sudah punya makna lain
    • Saya mengajarkan pemrograman dinamis persis seperti itu. Pertama selesaikan secara rekursif, lalu tambahkan memoization. Ini disebut top-down
      Setelah itu, kita melihat bahwa rekursi dan memoization punya overhead; jika tabel dibangun dari bawah ke atas dan pemanggilan rekursif dihilangkan, jadilah pemrograman dinamis
    • Dalam pendekatan saya, memoization adalah langkah ke-2 dari 3 langkah pemrograman dinamis. Langkah 1 adalah menemukan algoritma rekursif, langkah 2 adalah memoization, langkah 3 adalah membuatnya menjadi iteratif/bottom-up, dan jika memungkinkan lakukan optimasi ruang sebagai 3b
      Langkah 3 adalah bagian yang paling khas dari pemrograman dinamis, tetapi menurut saya berhenti di langkah 2 pun masih bisa disebut pemrograman dinamis. Hanya saja tidak seefisien yang mungkin dicapai. Dengan kata lain, memoization adalah caching, dan langkah 3 adalah menanyakan apakah ada cara untuk mengisi cache itu terlebih dahulu
    • Ada juga solusi pemrograman dinamis yang tidak berbasis memoization. Misalnya pada masalah mencari substring bersama terpanjang dari dua string, kita hanya membutuhkan sel di kiri dan di atas tabel satu kali, sehingga memoization tidak banyak membantu
      Secara umum, jika submasalah banyak bertumpang tindih, dan submasalah optimal harus menjadi bagian dari solusi optimal keseluruhan, maka ada peluang untuk memakai pemrograman dinamis. Mengatakan bahwa memoization saja adalah pemrograman dinamis mirip seperti mengatakan bahwa hanya hash table yang merupakan tipe data abstrak
    • Menurut kriteria saya, berpikir seperti itu salah. Pertama, ada kontra-contoh yang jelas bahwa memoization bisa dipakai di luar pemrograman dinamis. Sebaliknya, sebagian besar algoritma pemrograman dinamis bisa diimplementasikan dengan cara menyimpan hasil dalam tabel lalu mencari jawaban terbaik dari tabel itu nanti
      Memoization pada dasarnya adalah strategi untuk membuat algoritma lebih cepat
  • Menyelesaikan Advent of Code tahun ini terasa menyenangkan. Jelas bahwa hari ke-1, terutama bagian 2, jauh lebih sulit daripada tahun-tahun sebelumnya, dan saya juga menulis tentang itu di https://blog.singleton.io/posts/2024-01-02-advent-of-code-20..., tetapi hal itu tidak tampak jelas hanya dengan membandingkan statistik 2022 saat ini dan statistik 2023 saat ini. Sebab orang-orang punya waktu satu tahun lebih lama untuk menyelesaikan puzzle 2022
    Ketika saya mengambil statistik 2022 per 14 Januari 2023 https://web.archive.org/web/20230114172513/https://adventofc..., perbedaannya cukup besar. Jika menggambar statistik penyelesaian bagian 2 https://blog.singleton.io/static/imgs-aoc23/completion.png, ukuran kelompok awal pada hari ke-1 mirip, tetapi 2023 jelas terlihat lebih sulit daripada 2022 hingga hari ke-15
    Rasio orang yang menyelesaikan bagian 1 tetapi tidak menyelesaikan bagian 2 https://blog.singleton.io/static/imgs-aoc23/ratios.png juga jauh lebih tinggi pada banyak tanggal di 2023, dan terutama mengisyaratkan bahwa hari ke-5, hari ke-10, hari ke-12, serta hari ke-22 bagian 2 sulit

    • Advent of Code pada masa awal menyenangkan, dan sampai sebelum bagian akhir, kita masih bisa bertahan tanpa teknik yang muluk-muluk. Setelah itu menjadi lebih sulit dan kurang menyenangkan, jadi saya menyerah dan tidak lagi menyentuhnya
    • Tahun ini saya tidak bisa banyak melanjutkan Advent of Code karena kekurangan waktu, tetapi mungkin akan mencobanya lagi nanti
      Namun saya terkejut melihat betapa sulitnya hari ke-5 bagian 2. Saya memang berhasil menyelesaikannya tanpa menyerah, tetapi sempat merasa mungkin saya melewatkan sesuatu yang jelas sehingga menyelesaikannya dengan cara yang terlalu rumit; jadi saya lega setelah tahu bahwa memang itu soal yang cukup menantang
    • Ini hanya pengalaman pribadi, dan mungkin juga dipengaruhi oleh fakta bahwa saya mencobanya dengan bahasa yang bukan bahasa yang biasa saya pakai, tetapi menurut saya hari ke-1 bagian 2 bukan sulit, melainkan deskripsi soalnya kurang tepat
      Contoh yang diberikan adalah two1nine, eightwothree, abcone2threexyz, xtwone3four, 4nineeightseven2, zoneight234, 7pqrstsixteen, tetapi contoh penting seperti oneight tidak ada. Tanpa contoh seperti ini, sulit mengetahui secara tepat bagaimana nilai harus diganti
    • Menambahkan ke diskusi ini, saya punya skrip untuk melihat progres per hari. Dua kolom terakhir menunjukkan seberapa keras 2023 dibandingkan 2022, dan terutama bagian awalnya parah
      Pada 2022, selama beberapa hari pertama kebanyakan orang terus ikut, tingkat retensi pada banyak hari melampaui 80%, dan hampir semua orang menyelesaikan kedua bagian. Sebaliknya, pada hari ke-1 2023, dari orang yang menyelesaikan bagian 1, hanya 76% yang menyelesaikan sampai bagian 2, dan banyak orang menyerah pada hari ke-3 dan hari ke-5
      Menariknya, beberapa hari terakhir tidak serendah itu, yang bisa dijelaskan oleh fakta bahwa Advent of Code 2023 lebih baru daripada 2022. Interpretasi saya, kelompok ini adalah orang-orang yang akan menembus semua tantangan sampai batas tertentu terlepas dari tingkat kesulitannya, sementara banyak orang lain berhenti jika merasa itu memakan terlalu banyak waktu