Hackerrank: Rekorde brechen
Ich lerne Clojure und bin ein Rang n00b, versuche aus Büchern und Online-Tutorials zu lernen (aber ich mache mir manchmal Sorgen, dass ich schlechte Gewohnheiten oder zumindest nicht alle guten Gewohnheiten aufgreife). Zur Übung habe ich das Breaking the Records- Problem auf Hackerrank gemacht.
TL; DR Problembeschreibung:
Zählen Sie für eine Liste von Bewertungen (in historischer Reihenfolge), wie oft die vorherige beste Bewertung überschritten und die vorherige schlechteste Bewertung unterschritten wurde.
In einer iterativen Sprache ist es ziemlich einfach, die Liste einfach zu durchlaufen . Aber Clojure führt keine Iteration durch, und ich habe mich entschlossen, dieses Problem anzugehen, um eine (rekursive) rekursive Lösung zu erstellen. Um es mir einfacher zu machen, habe ich zuerst eine rekursive Lösung in Java erstellt, die ich dann übersetzt habe. Alles in allem ist es eine recht einfache rekursive Funktion ohne Raketenoperation.
Offensichtlich funktioniert mein Code, wie die enthaltenen Unit-Tests zeigen. Meine Bedenken lauten jedoch wie folgt:
- Wenn sie neben den Java-Code gestellt werden, sehen sich die beiden ziemlich ähnlich. Habe ich die "idiomatische" Clojure-Programmierung befolgt oder ist es nur eine ungeschickte "Wort-für-Wort-Transliteration"?
- Gibt es Bereiche, die durch die Verwendung verschiedener Clojure-Konstrukte kompakter und / oder verständlicher hätten sein können?
Ihr kritischer Beitrag wird sehr geschätzt - auch in Bezug auf das Testen von Einheiten , da dies wohl ein wichtiger Teil der Programmierung ist, den ich parallel lernen und üben möchte.
Code:
(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)))))
)
Antworten
Ich habe Ihre Lösung neu geschrieben, um typischere Clojure-Funktionen zu verwenden. Wenn Sie Daten durchlaufen und den akkumulierten Status verfolgen müssen, ist dies schwer zu übertreffen loop/recur. Ein erstes Beispiel:
(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))))))
und Unit-Tests:
(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)
Bitte beachten Sie diese Liste der Dokumentation , insb. das Clojure CheatSheet. Außerdem zeigt das Vorlagenprojekt als Ganzes, wie ich Dinge gerne strukturiere. :) :)
Die Funktion, die am meisten hilft, ist partition. Siehe die Dokumente .
Leichtes Refactoring
Sie können es ein wenig vereinfachen und etwas kompakter gestalten, indem Sie speziellere Funktionen wie reduceund verwenden cond->. Diese Version verwendet eine Karte, um den Status zu halten und reducedie Schleife durchzuführen:
(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))