2 puan yazan GN⁺ 2024-01-15 | 1 yorum | WhatsApp'ta paylaş
  • Advent of Code 2023 Day 12 gibi özel durumu çok olan problemler bile, aynı alt problemi tekrar tekrar çözme yapısı bulunursa dinamik programlama ile ele alınabilir
  • Esas fikir, problemi özyinelemeyle parçalara ayırdıktan sonra yinelenen hesaplamaları memoization ile azaltmak ve gerekli değerleri bağımlılık sırasına göre dolduran yinelemeli hesaplamaya taşımaktır
  • Fibonacci örneğinde saf özyineleme f(1) değerini tekrar tekrar değerlendirir; ancak önbellek kullanılırsa f(0)’dan f(n)’e kadar yalnızca n + 1 değer değerlendirilir
  • Levenshtein mesafesi ve Advent of Code Day 12, dize uzunluğu ve kural indeksi gibi durum indekslerini önbellek anahtarı olarak kullanıp özyinelemeli çağrıları dizi doldurmaya dönüştürme sürecini gösterir
  • Dinamik programlamayı öğrenmek yalnızca performansı iyileştirmekle kalmaz; algoritmanın ara durumlarını ve bağımlılık ilişkilerini görünür kılar, bellek optimizasyonu fırsatlarını bulmayı da kolaylaştırır

Adı kafa karıştırıcı, ama fikir basit

  • “dynamic programming” adı, modern anlamdaki “programlama stili” ya da “dinamik tip” gibi kavramlarla doğrudan ilişkili değildir
  • Özünde, problemi daha küçük ve benzer problemlere bölüp sonuçları yeniden kullanan bir algoritma tasarımı yaklaşımıdır
  • Tarihsel anlamdaki “programming” üzerinden bakıldığında bu ifadenin anlam kazandığını belirten bir editör notu eklenmiştir
  • Başlangıç noktası genellikle problemi daha küçük problemlere ayıran özyinelemeli fonksiyon biçimidir
  • Aynı alt problem birden çok kez ortaya çıkıyorsa, hesaplama sonucunu saklayıp yeniden kullanmaya yarayan önbellekleme doğal olarak gerekli hale gelir

Fibonacci ile önbellekleme ve yinelemeli hale getirme

  • Fibonacci fonksiyonu f(n) = f(n - 1) + f(n - 2) olarak tanımlanır; saf özyinelemeli uygulama aynı değerleri tekrar tekrar hesaplar
  • f(1) nihai sonuca gerçekten eklenen bir değer olduğu için, f(n) büyüdükçe saf özyinelemenin değerlendirme sayısı da hızla artar
  • Sonuçları önbelleğe almak veya memoization kullanmak, önceden hesaplanmış f(4), f(3), f(2) değerlerini yeniden hesaplama gereğini ortadan kaldırır
  • Bu yöntemde f(0)’dan f(6)’ya kadar toplam yalnızca 7 değer değerlendirilir; genel olarak bu sayı n + 1 değerlendirmeye düşer
  • Bir adım daha ileri gidip gerekli değerler f(0), f(1)’den başlayarak sırayla doldurulursa özyinelemeli çağrılar ortadan kalkar
    • F[2] = F[1] + F[0]
    • F[3] = F[2] + F[1]
    • Aynı şekilde F[6] = 8 değerine kadar hesaplanır
  • Fibonacci’de tüm diziye de gerek yoktur; yalnızca bir önceki değer ve ondan önceki değer olmak üzere iki değeri tutmak yeterlidir
  • Bu akış, matematiksel tanımdan başlayıp yinelemeli uygulamaya geçmek için sistematik bir yol gösterir

Düzenleme mesafesi örneğine genişletme

  • İki dizenin düzenleme mesafesi, bir dizeyi diğerine dönüştürmek için gereken en az düzenleme sayısıdır
  • İzin verilen düzenleme türlerine göre problem değişir
    • Yalnızca karakter değiştirmeye izin verilirse Hamming distance
    • Ekleme ve silmeye de izin verilirse Levenshtein distance
  • Levenshtein mesafesi, iki dize A ve B’nin son karakterleri temel alınarak daha küçük problemlere bölünebilir
    • Son karakterler aynıysa iki karakter yok sayılır ve kalan dizelerin mesafesi kullanılır
    • Son karakterler farklıysa değiştirme, silme ve ekleme arasından en düşük maliyet seçilir
    • A boşsa B’deki tüm karakterlerin eklenmesi gerekir; bu yüzden maliyet b olur
    • B boşsa A’daki tüm karakterlerin silinmesi gerekir; bu yüzden maliyet a olur
  • Bu tanımı doğrudan Python özyinelemesine aktarınca uzun dizelerde ve aralarında çok fark olan dizelerde çok yavaşlar
  • Fibonacci çağrı ağacının her seviyesinde kabaca iki dala ayrılırken, bu özyineleme duruma göre üç dala ayrılır
  • Python’ın functools.cache dekoratörü eklendiğinde aynı alt dize kombinasyonlarının hesaplama sonuçları yeniden kullanılabilir
  • Daha iyi bir uygulama, sürekli yeni dize oluşturmak yerine özgün dizeler A, B ile alt dize uzunlukları a, b değerlerini geçirir
  • Son aşamada doğrudan 2 boyutlu bir cache dizisi oluşturulur ve cache[a][b] = levenstein(A[:a], B[:b]) olacak şekilde sırayla doldurulur
  • Yinelemeli sürüm, a ve b değerlerini 0’dan dize uzunluğuna kadar dolaşır ve önceden doldurulmuş önceki satır ile önceki sütundaki değerlere başvurur

Advent of Code 2023 Day 12’ye uygulama

  • Advent of Code 2023 12 Aralık problemi, 1 boyutlu bir nonogram çözme problemidir
  • Örnek girdi .??..??...?##. 1,1,3 biçimindedir; ?, . veya # olabilir
  • Brute force yaklaşımı backtracking kullanır; ancak soru işareti sayısı n ise 2^n aday değerlendirilmesi gerektiğinden üstel olarak büyür
  • Aynı alt problemlerin tekrarlandığı bir yapı ortaya çıkar
    • ..#..??...?##. (1),1,3
    • .#...??...?##. (1),1,3
    • Önceden işlenmiş baş kısım atıldığında bunlar sırasıyla .??...?##. 1,3, ..??...?##. 1,3 gibi neredeyse aynı problemlere dönüşür
  • Temel backtracking fonksiyonu conditions ve rules alır ve olası yerleşim sayısını hesaplar
    • Kural kalmamışsa kalan koşullarda # olup olmadığına bakar
    • Koşul kalmamışsa kural kalıp kalmadığını kontrol eder
    • Geçerli karakter . veya ? ise bir hücre ilerleyerek hesaplar
    • Geçerli karakter # veya ? ise sonraki kural boyutunu ve ayırıcı koşulunu kontrol ettikten sonra sonraki duruma geçer
  • Python’da yalnızca @cache eklemek memoization uygulamak için yeterlidir
  • Dinamik programlamaya dönüştürmek için dizeleri ve kuralları kesip geçirmek yerine, durum olarak dize ofseti i ve kural ofseti j kullanılır
  • Ardından cache[i][j] doğrudan oluşturulur ve indeksler ters sırada doldurularak özyineleme yinelemeli hesaplamayla değiştirilir
  • Rust uygulama örneği yazıdaki Rust implementation bağlantısında verilmiştir

Önbelleği doğrudan doldurunca görülenler

  • Advent of Code Day 12’nin dinamik programlama sürümü, memoization sürümünden daha yavaş görünebilir
  • Bu farkın nedeni optimize edilmemiş Python uygulaması olabilir
  • Önbelleği doğrudan kurmak, hangi değerlere gerçekten ihtiyaç duyulduğunu daha iyi gösterir
  • Day 12 probleminde dinamik programlama sürümü sayesinde yalnızca önceki sütunun gerekli olduğu görülebilir
  • Bu nedenle 2 boyutlu dizi, önceki sütunu ve mevcut sütunu temsil eden iki adet 1 boyutlu diziye dönüştürülebilir

Pratik yapılabilecek problemler ve sonuç

  • Dinamik programlama önemsiz değildir; ancak çoğu programcı için erişilemez bir teknik de değildir
  • Problemi küçük parçalara bölme yöntemini anlamak, birçok durumda yalnızca memoization ile bile saf uygulamaya göre büyük iyileştirme sağlayabilir
  • Daha fazla ustalaştıkça bir algoritma ailesini anlayabilir, trade-off’ları daha iyi kavrayabilir ve ek optimizasyonlar bulabilirsiniz
  • Pratik için şu problemler önerilir
  • Uygulamadan sonra benchmark ve profiling yapmayı unutmamak gerekir

1 yorum

 
GN⁺ 2024-01-15
Hacker News yorumları
  • Yazıda dinamik programlama algoritmasının, özyinelemeyi önbelleğe almanın akıllıca bir yolundan ibaret olduğuna dikkat çekilmesini sevdim. Deneyimime göre özyinelemeli çözümü önce bulmak, dinamik programlama çözümünü bulmak için en iyi başlangıç noktası; bir kez bulunca memoization kolaydır ve büyük hız artışı sağlayabilir.
    Hatta bazen aşağıdan yukarıya dinamik programlamadan daha hızlı olabilir, çünkü yalnızca gerçekten gereken çözümleri hesaplar. Kilit nokta şu: çağrı ağacında çok sayıda alt problem olması sorun değil, ama birbirinden farklı alt problem sayısının görece az olması gerekir. Yalnızca bir kez gereken sonucu önbelleğe almanın anlamı yoktur; asıl zorluk, özgün problemi yeterince az sayıda farklı alt probleme ayırabilmektir.

    • Birbirinden farklı alt problem sayısının görece az olması gerektiği kısmı kilit nokta. Algoritmanın tamamının özyinelemeli mi yoksa yinelemeli mi olduğu ikincil; dinamik programlama genellikle özyinelemeli algoritmalarda daha sık kendini gösterir.
    • “Dinamik programlama, özyinelemeyi önbelleğe alma yöntemidir” açıklaması benim için konuyu tam oturtan şey olmuştu. Üniversitedeyken, belki de o dönemde prosedürel programlama baskın olduğu için, ders kitaplarındaki aşağıdan yukarıya tablo doldurma örnekleri sihir gibi görünüyordu.
      Pratikte kuyruk çağrısı optimizasyonu her zaman uygulanmadığından bunu yapmak mantıklı; ama keşke önce daha sezgisel olan yukarıdan aşağıya özyinelemeli önbellek bakış açısıyla öğrenseydim diye düşünüyorum.
    • İlk öğrendiğimde, bu kadar gösterişli bir özellikse buna dizi memoization’ı ya da çağrı yığını memoization’ı denmesi gerekmez mi diye düşünmüştüm. “Dinamik programlama” adının daha iyi bir şey için saklanması gerektiğini düşünüyorum.
    • Dinamik programlamayı basitçe memoization uygulanmış özyineleme olarak görmek bence yaygın bir yanlış anlama. Böyle öğrenince 2 boyutlu dizi doldurma türündeki dinamik programlama problemlerini anlamak çok zorlaşıyor.
      Örneğin LeetCode’daki “Hisse Alıp Satmak İçin En İyi Zaman” serisine bakarsak, https://leetcode.com/problems/best-time-to-buy-and-sell-stoc... gibi bir problemde diziyi doldurma yaklaşımı çok daha doğal değil mi? Bunu özyinelemeyle hiç çözmedim; doğal bir özyinelemeli çözümü var mı onu da pek bilmiyorum.
      Yukarıdaki bağlantı III’e ait, ama ilk kez deneyenler için ilk problemden, https://leetcode.com/problems/best-time-to-buy-and-sell-stoc... başlayıp dinamik programlamaya giriş yapmak iyi olur.
    • “Dinamik programlama sadece caching/memoization’dır” demek, “yatırım sadece bir şeyi alıp sonra satmaktır” demeye benziyor. Teknik olarak bir ölçüde doğru olabilir, ama konunun karmaşıklığını ve zorluğunu o kadar büyük ölçüde ıskalıyor ki, içgörü sunmaktan çok komik görünebilir.
  • “Dinamik programlama” adının kökeni, mucidi Richard Bellman’a dayanıyor. 1950’de RAND’de çok aşamalı karar verme sürecine verilecek bir ad arıyordu; o dönemde Savunma Bakanı Wilson’ın “araştırma” sözcüğünden hastalık derecesinde nefret ettiği, “matematik” sözünden ise daha da çok kaçınmak gerektiği söyleniyor.
    Bellman’ın, RAND içinde aslında matematik yaptıkları gerçeğini Wilson’dan ve Hava Kuvvetleri’nden gizleyecek bir ada ihtiyacı vardı. Bu yüzden planlama, karar verme ve düşünmeyle ilgili olsa da “planning” çeşitli nedenlerle iyi bir seçenek değildi; bunun yerine “programming”i seçti ve çok aşamalılık ile zaman içinde değişim kavramını yansıtmak için klasik fizikte kesin bir anlamı olan “dynamic” sözcüğünü ekledi.
    “Dynamic”in sıfat olarak olumsuz anlamda kullanılmasının zor olması da hoşuna gitmişti; bir milletvekilinin de karşı çıkmakta zorlanacağı bir ad olduğundan, dynamic programmingi kendi faaliyetlerini kapsayan ad olarak kullandığı söyleniyor.
    Kaynak: https://alliance.seas.upenn.edu/~cis520/dynamic/2021/wiki/in...

  • Bu yazının önce problemi özyinelemeli biçimde ortaya koyup, ardından kademeli olarak caching eklemesini ve en sonunda önbellek boyutunu yalnızca gerektiği kadar küçültmesini beğendim.
    Ben çoğu zaman doğrudan dinamik programlama çözümüne gitmeye çalışıp takıldım ya da çalışır hale getirmek için gereksiz yere zorladım. Bundan sonra kendimi adımları sırayla izlemeye zorlamayı düşünüyorum.

    • Deneyimime göre dinamik programlama doğrudan öğretilirse bulmaca gibi hissettiriyor. Adım adım gidip tablonun neden kullanıldığını açıklayınca ve bu kavramı caching ile ilişkilendirince çok daha iyi anlaşılıyor.
  • Dinamik programlamanın güzel uygulamalarından biri, nükleotid/protein dizilerinin ikili hizalanmasıdır.
    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...

    • Bu algoritmaların biyoinformatik/biyoloji alanındaki en önemli algoritmalardan bazıları olduğunu düşünüyorum. Uygulama alanları çok geniş.
  • Çok iyi bir algoritmalar hocamız vardı; UCLA’de okumuştu. Dinamik programlama dersi harikaydı: önce basit çözümü üstel zaman karmaşıklığına sahip bir problemle başlıyor, sonra problemi daha küçük problemlere bölerek karmaşıklığı polinom düzeyine indiriyor, ardından memoization uygulayarak lineere düşürüyordu.
    O zaman kullandığı problemlerin neler olduğunu hatırlayabilsem keşke

    • Adaylar arasında Fibonacci dizisi, bozuk para üstü problemi, 0/1 sırt çantası problemi, matris zinciri çarpımı, en uzun ortak alt dizi, en uzun artan alt dizi, Floyd-Warshall gibi en kısa yol problemleri ve düzenleme mesafesi (Levenshtein mesafesi) var.
      Hepsi naif çözümü verimsiz olan ve dinamik programlamayla büyük ölçüde iyileşen tipik örnekler
    • Yazının içinde de birkaçını not ettim; derslerde veya alıştırmalarda sık görülen problemler. Örneğin en uzun ortak alt dizi, en uzun ortak alt dize, line warp, alt küme toplamı, bölme ve sırt çantası problemi var.
      Daha fazla örnek için https://en.wikipedia.org/wiki/Dynamic_programming#Algorithms... sayfasına bakılabilir
    • Başkalarının yazdığı problemlerin dışında bir zamanlama problemi de olmuş olabilir. Örneğin zamanda çakışan N etkinliği, ders programını ya da CPU süreçlerini verim gibi bir ölçüte göre optimize etme problemi.
      “Bu iki ders birlikte alınmalı” gibi özel kısıtlar eklendiğinde, bildiğim kadarıyla sıradan dinamik programlamadan çok daha karmaşık ve ele alınması zor hâle geliyor
    • UCLA’de Kang’in yanında okumuş biri miydi?
  • Asıl site trafiği kaldıramıyor gibi göründüğü için arşiv bağlantısını bırakıyorum
    https://web.archive.org/web/20240114111200/https://qsantos.f...

  • Dinamik programlama sayesinde yasal Go konumlarının sayısı hesaplanabildi ve bu değer 171 basamaklı bir sayıydı.
    Naif yöntem, n×n Go tahtasındaki tüm olası konumlara baktığı için 3^(n^2) zaman alır; dinamik programlama ise fiilen bir boyutu ortadan kaldırarak zaman karmaşıklığını O(n^5 * 5.4^n), alan karmaşıklığını O(n * 5.4^n) düzeyine indirir.
    https://tromp.github.io/go/legal.html
    https://tromp.github.io/go/gostate.pdf

  • “Dynamic Programming” adının tuhaf görünmesinin nedeni, buradaki programming’in programlama alanını ifade etmemesi olabilir. Bu durumda anlamı, doğrusal programlamaya benzer şekilde daha çok optimizasyona yakındır.
    Dinamik programlama, ayrık zamandaki karar problemlerini; yani kısıtlar altında \sum_t u_t(a_t)’yi maksimize eden en iyi {a_t} sırasını seçme problemini çözme yöntemi olarak görülebilir. Değer fonksiyonu V*’ı V*(t) = max_{a_t}{ u_t(a_t) + V*(t-1) } şeklinde tanımlayarak optimizasyon probleminin boyutunu büyük ölçüde azaltır

    • Aslında adın resmî kökeni https://en.wikipedia.org/wiki/Dynamic_programming#History oldukça komik. Bellman, “dynamic” kelimesinin olumsuz anlamda kullanılmasının imkânsız bir sıfat olduğu için iyi olduğunu ve milletvekillerinin bile karşı çıkamayacağı bir ad olarak gördüğünü söylemiş
    • Başkaları “dinamik programlama” ifadesini kullandığında bazen zeki görünme gösterisi gibi hissettirdiği oluyor. Oysa gerçekte yapılan, problemi giderek daha küçük alt problemlere bölebileceğini fark eden doğal ve sezgisel bir yaklaşım kullanmak; ama bunu sanki özel bir tekniği “kullanmış” gibi söylemek
    • Eskiden optimizasyon problemleri gibi bir şeyleri hesaplama işinin, bilgisayarla ne yapılacağına dair düşüncede çok daha baskın olması ilginç. Bugün çoğunlukla veri depolama/sorgulama ve ağ işleri yapıyoruz; içinde hesaplama olsa bile genellikle iyi kapsüllenmiş gibi duruyor
    • “Optimizasyon” kelimesi de benzer biçimde yanlış anlaşılmalara yol açıyor. Bir zamanlar “optimization” adlı bir bilgisayar bilimi dersi alırken bambaşka bir şey beklemiştim
    • Daha geriye gidersek, “programming” bu kavramı tam olarak açıklıyor. Bugün “programlama” dediğimiz şey aslında kod yazma ve fonksiyonel, bildirime dayalı, prosedürel gibi çeşitli programlama türlerinden bir dal olarak ayrılabilir. Bu şemsiyenin altında çok daha fazlası var
  • “Dinamik programlama” dendiğinde bunu sadece memoization olarak düşünmek yanlış mı? Eksik kalan kısım, memoization kullanabilmek için problemi akıllıca parçalara ayırmak olabilir

    • Memoization daha genel bir tekniktir. Çoğu zaman, zaten hesaplanmış sonuçları ileride tekrar gerekirse diye cache’lemekten ibarettir
      Dinamik programlama ise sistematik memoization’a daha yakındır. Giderek daha büyük alt problemleri çözerek tüm problemin çözümüne ulaşırsınız. “Tümevarım algoritması” ifadesi de bir ölçüde uyar; çünkü tipik bir dinamik programlama algoritması aslında matematiksel tümevarım ispatına benzer. Ne yazık ki bu terimin zaten başka anlamları var
    • Ben dinamik programlamayı tam olarak böyle öğretiyorum. Önce recursive olarak çözüyoruz, sonra memoization ekliyoruz. Buna top-down deniyor
      Ardından recursive çağrıların ve memoization’ın overhead’i olduğunu görüp tabloyu aşağıdan yukarıya kurarak recursive çağrıları kaldırınca dinamik programlama oluyor
    • Benim yaklaşımımda memoization, dinamik programlamanın 3 adımından 2.’sidir. 1. adım recursive algoritmayı bulmak, 2. adım memoization, 3. adım bunu iteratif/bottom-up hâle getirmek ve mümkünse 3b olarak alan optimizasyonu yapmaktır
      3. adım dinamik programlamanın en karakteristik kısmıdır, ama 2. adımda dursanız da buna dinamik programlama denebileceğini düşünüyorum. Yalnız mümkün olan kadar verimli değildir. Başka bir deyişle memoization cache’lemedir; 3. adım ise “bu cache’i önceden doldurmanın bir yolu var mı?” diye sormaktır
    • Memoization tabanlı olmayan dinamik programlama çözümleri de vardır. Örneğin iki string’in en uzun ortak alt dizgesini bulma probleminde, tablonun yalnızca sol ve üst hücrelerine bir kez ihtiyaç duyulduğu için memoization pek yardımcı olmaz
      Genel olarak alt problemler çokça örtüşüyorsa ve optimal alt problem, genel optimal çözümün bir parçası olmak zorundaysa dinamik programlama için fırsat vardır. Sadece memoization’ın dinamik programlama olduğunu söylemek, yalnızca hash table’ın soyut veri tipi olduğunu söylemeye benzer
    • Benim ölçütüme göre böyle düşünmek yanlıştır. Öncelikle memoization’ın dinamik programlama dışında da kullanılabileceğine dair bariz karşı örnekler var. Tersine, çoğu dinamik programlama algoritması sonuçları bir tabloda saklayıp daha sonra o tabloda en iyi cevabı arayacak şekilde uygulanabilir
      Memoization temelde bir algoritmayı hızlandırma stratejisidir
  • Bu yıl Advent of Code’u bitirmek keyifliydi. 1. günün, özellikle de 2. bölümün önceki yıllara göre çok daha zor olduğu açıktı ve bunu https://blog.singleton.io/posts/2024-01-02-advent-of-code-20... adresinde de yazdım; ancak mevcut 2022 istatistikleriyle mevcut 2023 istatistiklerini karşılaştırmak tek başına net değil. Çünkü insanların 2022 bulmacalarını çözmek için fazladan bir yılı vardı
    14 Ocak 2023 tarihli 2022 istatistiklerini https://web.archive.org/web/20230114172513/https://adventofc... aldığımda fark oldukça büyüktü. 2. bölüm tamamlama istatistiklerini https://blog.singleton.io/static/imgs-aoc23/completion.png çizdiğinizde, 1. gün başlangıç kitlesinin boyutu benzerdi; ancak 2023, 15. güne kadar 2022’den belirgin şekilde daha zor görünüyor

    1. bölümü çözüp 2. bölümü çözemeyenlerin oranı https://blog.singleton.io/static/imgs-aoc23/ratios.png da 2023’te birçok günde çok daha yüksek; özellikle 5. gün, 10. gün, 12. gün ve 22. gün 2. bölümün zor olduğunu düşündürüyor
    • İlk dönem Advent of Code eğlenceliydi ve sonlara kadar büyük teknikler olmadan da idare edilebiliyordu. Sonra daha zorlaşıp eğlencesi kaçınca bıraktım ve bir daha el sürmedim
    • Bu yıl Advent of Code’da zamanım az olduğu için çok ilerleyemedim, ama ileride tekrar deneyebilirim
      Yine de 5. gün 2. bölümün ne kadar zor olduğuna şaşırdım. Pes etmeden çözdüm ama sanki bariz bir şeyi kaçırıp gereğinden karmaşık çözmüşüm gibi hissetmiştim; aslında biraz zorlayıcı bir problem olduğunu öğrenince rahatladım
    • Bu yalnızca kişisel deneyimim ve normalde kullandığım dil dışında başka bir dille denemiş olmamın da etkisi olabilir; ama 1. gün 2. bölümün zor olmaktan ziyade problem açıklamasının yetersiz olduğunu düşünüyorum
      Örnek olarak two1nine, eightwothree, abcone2threexyz, xtwone3four, 4nineeightseven2, zoneight234, 7pqrstsixteen verilmişti; ancak oneight gibi kritik bir örnek eksikti. Böyle bir örnek olmadan değerleri tam olarak nasıl değiştirmek gerektiğini anlamak zor
    • Bu tartışmaya ek olarak, günlere göre ilerlemeyi gösteren bir script’im var. Son iki sütuna bakınca 2023’ün 2022’ye kıyasla ne kadar acımasız olduğu görülüyor; özellikle de başları çok zordu
      2022’de ilk birkaç gün boyunca çoğu kişi katılmaya devam etti, birçok günde elde tutma oranı %80’in üzerindeydi ve neredeyse herkes iki bölümü de çözdü. Buna karşılık 2023’te 1. gün 1. bölümü çözenler arasında 2. bölümü de çözenlerin oranı yalnızca %76’ydı; 3. ve 5. günlerde ise çok kişi pes etti
      İlginç şekilde son birkaç gün o kadar düşük değil; bu, 2023 Advent of Code’un 2022’ye göre daha yakın tarihli olmasıyla açıklanabilir. Benim yorumum şu: Bu grup, zorluktan bağımsız olarak belli bir noktaya kadar tüm meydan okumaları aşan insanlardan oluşuyor; diğer birçok kişi ise bunun çok fazla zaman aldığını hissedince bırakıyor