Hackerrank: ทำลายสถิติ

Oct 22 2020

ฉันกำลังเรียน Clojure และอยู่ในอันดับที่ n00b พยายามเรียนรู้จากหนังสือและแบบฝึกหัดออนไลน์ (แต่บางครั้งฉันก็กังวลว่าฉันจะเลือกนิสัยที่ไม่ดีหรืออย่างน้อยก็ไม่ใช่นิสัยที่ดีทั้งหมด) สำหรับการออกกำลังกายฉันทำปัญหาBreaking the Recordsบน Hackerrank

คำอธิบายปัญหา TL; DR:

สำหรับรายการคะแนน (ตามลำดับประวัติศาสตร์) ให้นับจำนวนครั้งที่คะแนนที่ดีที่สุดก่อนหน้านี้เกินรวมทั้งคะแนนที่แย่ที่สุดก่อนหน้านี้ถูกตัดราคา

ในภาษาที่ซ้ำมันค่อนข้างง่ายที่จะเพียงแค่ย้ำผ่านรายการ ; แต่ Clojure ไม่ได้ทำซ้ำและฉันตัดสินใจที่จะจัดการกับปัญหานี้เพื่อออกกำลังกายในการสร้างวิธีแก้ซ้ำ (ปลายหาง) เพื่อให้ง่ายขึ้นสำหรับตัวเองฉันได้ทำการแก้ปัญหาซ้ำใน Java ก่อนซึ่งฉันก็แปลแล้ว สรุปแล้วมันเป็นฟังก์ชั่นเรียกซ้ำที่เรียบง่ายโดยไม่ต้องผ่าตัดจรวด

เห็นได้ชัดว่ารหัสของฉันใช้งานได้ดังที่แสดงในการทดสอบหน่วยที่รวมอยู่ อย่างไรก็ตามข้อกังวลของฉันมีดังนี้:

  • เมื่อวางถัดจากโค้ด Java ทั้งสองมีลักษณะคล้ายกัน ฉันทำตามการเขียนโปรแกรม Clojure แบบ "สำนวน" หรือเป็นเพียง "การทับศัพท์คำต่อคำ" ที่เงอะงะ
  • มีพื้นที่ใดบ้างที่อาจมีขนาดกะทัดรัดและ / หรือเข้าใจง่ายขึ้นโดยใช้โครงสร้าง Clojure ที่แตกต่างกันหรือไม่?

ข้อมูลที่สำคัญของคุณจะได้รับการชื่นชมอย่างมาก - รวมถึงเกี่ยวกับการทดสอบหน่วยเนื่องจากเนื้อหานั้นเป็นส่วนสำคัญของการเขียนโปรแกรมซึ่งฉันต้องการเรียนรู้และฝึกฝนควบคู่กันไป

รหัส:

(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)))))
)

คำตอบ

2 AlanThompson Oct 23 2020 at 02:14

ฉันเขียนโซลูชันของคุณอีกครั้งเพื่อใช้คุณสมบัติ Clojure ทั่วไป เมื่อคุณวนลูปข้อมูลและต้องการติดตามสถานะสะสมก็ยากที่จะเอาชนะloop/recurได้ ตัวอย่างแรก:

(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))))))

และการทดสอบหน่วย:

(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)

โปรดดูรายการเอกสารนี้โดยเฉพาะ Clojure CheatSheet นอกจากนี้โครงการเทมเพลตโดยรวมยังแสดงให้เห็นว่าฉันชอบจัดโครงสร้างสิ่งต่างๆอย่างไร :)

ฟังก์ชันที่ช่วยได้มากที่สุดคือpartition. ดูเอกสาร


การปรับโครงสร้างเล็กน้อย

คุณสามารถลดความซับซ้อนของมันเป็นจำนวนเงินที่มีขนาดเล็กและทำให้มันเป็นบิตขนาดกะทัดรัดมากขึ้นโดยใช้ฟังก์ชั่นพิเศษเพิ่มเติมเช่นและreduce cond->เวอร์ชันนี้ใช้แผนที่เพื่อเก็บสถานะและreduceดำเนินการวนซ้ำ:

(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))