Hackerrank : 기록 깨기

Oct 22 2020

저는 Clojure를 배우고 있으며, 책과 온라인 튜토리얼에서 배우려고 노력하는 n00b 등급입니다. 연습을 위해 Hackerrank 에서 Breaking the Records 문제를 해결했습니다.

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)

이 문서 목록 , esp를 참조하십시오 . Clojure CheatSheet. 또한 템플릿 프로젝트 전체는 내가 사물을 구성하는 방법을 보여줍니다. :)

가장 도움이되는 기능은 partition. 문서를 참조하십시오 .


약간의 리팩토링

당신은 그에게 작은 금액을 단순화하고 같은보다 전문적인 기능을 사용하여 좀 더 컴팩트 만들 수 있습니다 reducecond->. 이 버전은 맵을 사용하여 상태를 유지 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))