Hackerrank: Rekorları kırmak

Oct 22 2020

Clojure öğreniyorum ve kitaplardan ve çevrimiçi öğreticilerden öğrenmeye çalışıyorum (ancak bazen kötü alışkanlıklar veya en azından tüm iyi alışkanlıkları almadığım konusunda endişeliyim). Egzersiz için Hackerrank'te Breaking the Records problemini yaptım.

TL; DR problem tanımı:

Puanların bir listesi için (geçmiş sırayla), önceki en iyi puanın kaç kez aşıldığını ve önceki en kötü puanın altının kesildiğini sayın.

Yinelemeli bir dilde , listeyi yinelemek oldukça kolaydır ; ancak Clojure yineleme yapmıyor ve ben (kuyruk ucu) yinelemeli bir çözüm oluşturmada alıştırma yapmak için bu sorunu çözmeye karar verdim. Bunu kendim için kolaylaştırmak için önce Java'da özyinelemeli bir çözüm yaptım, ardından bunu tercüme ettim. Sonuç olarak, roket ameliyatı olmadan oldukça basit bir yinelemeli işlev.

Açıkçası benim kodum, dahil edilen birim testlerinde gösterildiği gibi çalışıyor. Ancak endişelerim şu şekildedir:

  • Java kodunun yanına koyulduğunda, ikisi oldukça benzer görünüyor. "Deyimsel" Clojure programlamayı mı takip ettim, yoksa sadece beceriksiz bir "kelimesi kelimesine transliterasyon" mu?
  • Farklı Clojure yapıları kullanılarak daha kompakt ve / veya anlaşılması daha kolay olabilecek alanlar var mı?

Paralel olarak öğrenmek ve pratik yapmak istediğim, programlamanın tartışmasız önemli bir parçası olduğu için, birim testi de dahil olmak üzere , kritik girdiniz çok takdir edilecektir .

Kod:

(ns hackerrank.breaking-records
  (:require [clojure.test :refer :all]))

(defrecord Record [min max countworse countbetter])

(defn recalc-record [rec newscore]
  (Record.
    (min newscore (:min rec))
    (max newscore (:max rec))
    (+ (:countworse rec) (if (> (:min rec) newscore) 1  0))
    (+ (:countbetter rec) (if (< (:max rec) newscore) 1  0))))

(defn accumulate [curr-record remaining-scores]
  (if (nil? (second remaining-scores))
    curr-record
    (recur (recalc-record curr-record (second remaining-scores)) (rest remaining-scores)))
)

(defn breaking-records [scores]
  (let [result (accumulate (Record. (first scores) (first scores) 0 0) scores)]
    (list (:countbetter result) (:countworse result))))

(deftest test-records
  (testing "edge cases"
    (is (= '(0 0) (breaking-records '())) "no games played yet")
    (is (= '(0 0) (breaking-records '(5))) "single game"))
  (testing "hackerrank examples"
    (is (= '(2 4) (breaking-records '(10 5 20 20 4 5 2 25 1))))
    (is (= '(4 0) (breaking-records '(3 4 21 36 10 28 35 5 24 42)))))
)

Yanıtlar

2 AlanThompson Oct 23 2020 at 02:14

Daha tipik Clojure özelliklerini kullanmak için çözümünüzü yeniden yazdım. Veriler üzerinde döngü oluşturduğunuzda ve birikmiş durumu takip etmeniz gerektiğinde, yenmek zordur loop/recur. İlk örnek:

(ns tst.demo.core
  (:use clojure.test))

(defn breaking-records
  [scores]
  ; this loop has 5 variables. Init all of them
  (loop [low         (first scores)
         high        (first scores)
         nworse      0
         nbetter     0
         score-pairs (partition 2 1 scores)]
    (if (empty? score-pairs)
      {:nbetter nbetter :nworse nworse}
      (let [curr-score-pair (first score-pairs)
            new-score       (second curr-score-pair)]
        ; start the next iteration with modified versions of the 5 loop vars
        (recur
          (min new-score low)
          (max new-score high)
          (if (< new-score low)
            (inc nworse)
            nworse)
          (if (< high new-score)
            (inc nbetter)
            nbetter)
          (rest score-pairs))))))

ve birim testleri:

(deftest test-records
  (testing "edge cases"
    (is (= (breaking-records []) {:nbetter 0 :nworse 0}) "no games played yet")
    (is (= (breaking-records [5]) {:nbetter 0 :nworse 0}) "single game"))
  (testing "hackerrank examples"
    (is (= (breaking-records [10 5 20 20 4 5 2 25 1]) {:nbetter 2 :nworse 4}))
    (is (= (breaking-records [3 4 21 36 10 28 35 5 24 42]) {:nbetter 4 :nworse 0}))))

; ***** NOTE: it's much easier to use vectors like [1 2 3] instead of a quoted list `(1 2 3)

Lütfen bu dokümantasyon listesine bakın , özellikle. Clojure CheatSheet. Ayrıca, şablon proje bir bütün olarak bir şeyleri nasıl yapılandırmayı sevdiğimi gösterir. :)

En çok yardımcı olan işlev partition. Dokümanlara bakın .


Hafif yeniden düzenleme

Sen bunu bir miktar basitleştirmek ve benzeri daha uzmanlaşmış işlevleri kullanarak biraz daha kompakt yapabilir reduceve cond->. Bu sürüm, durumu tutmak ve reducedöngüyü gerçekleştirmek için bir harita kullanır :

(defn breaking-records
  [scores]
  (let [state-init     {:low     (first scores)
                        :high    (first scores)
                        :nworse  0
                        :nbetter 0}
        accum-stats-fn (fn [state score-pair]
                         ; Use map destructuring to pull out the 4 state variables
                         (let [{:keys [low high nworse nbetter]} state 
                               new-score (second score-pair)
                               state-new {:low     (min new-score low)
                                          :high    (max new-score high)
                                          :nworse  (cond-> nworse
                                                     (< new-score low) (inc))
                                          :nbetter (cond-> nbetter
                                                     (< high new-score) (inc))}]
                           state-new))
        state-final    (reduce accum-stats-fn
                         state-init
                         (partition 2 1 scores))
        result         (select-keys state-final [:nworse :nbetter])]
    result))