Hackerrank: Phá kỷ lục
Tôi đang học Clojure và có thứ hạng n00b về nó, cố gắng học hỏi từ sách và hướng dẫn trực tuyến (nhưng đôi khi tôi lo ngại rằng tôi đang mắc phải những thói quen xấu hoặc ít nhất là không phải tất cả những thói quen tốt). Đối với bài tập, tôi đã giải bài tập Phá kỷ lục trên Hackerrank.
TL; Mô tả sự cố DR:
Đối với danh sách điểm số (theo thứ tự lịch sử), hãy đếm số lần vượt quá điểm số tốt nhất trước đó cũng như điểm số kém nhất trước đó bị cắt giảm.
Trong một ngôn ngữ lặp đi lặp lại, khá dễ dàng chỉ cần lặp qua danh sách ; nhưng Clojure không lặp lại và tôi quyết định giải quyết vấn đề này cho bài tập xây dựng một giải pháp đệ quy (đuôi). Để làm cho nó dễ dàng hơn cho bản thân, đầu tiên tôi đã thực hiện một giải pháp đệ quy trong Java, sau đó tôi đã dịch. Nói chung, nó là một hàm đệ quy khá đơn giản mà không cần phẫu thuật tên lửa.
Rõ ràng là mã của tôi hoạt động, như được hiển thị bởi các bài kiểm tra đơn vị bao gồm. Tuy nhiên, mối quan tâm của tôi như sau:
- Khi đặt cạnh mã Java, cả hai trông khá giống nhau. Tôi đã làm theo chương trình Clojure "thành ngữ" hay chỉ là "chuyển ngữ từng từ" một cách vụng về?
- Có bất kỳ khu vực nào có thể nhỏ gọn hơn và / hoặc dễ hiểu hơn bằng cách sử dụng các cấu trúc Clojure khác nhau không?
Đầu vào quan trọng của bạn sẽ được đánh giá cao - bao gồm cả kiểm thử đơn vị , vì đó được cho là một phần quan trọng của lập trình mà tôi muốn học và thực hành song song.
Mã:
(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)))))
)
Trả lời
Tôi đã viết lại giải pháp của bạn để sử dụng các tính năng Clojure điển hình hơn. Khi bạn đang lặp lại dữ liệu và cần theo dõi trạng thái tích lũy, điều đó thật khó để đánh bại loop/recur. Một ví dụ đầu tiên:
(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))))))
và các bài kiểm tra đơn vị:
(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)
Vui lòng xem danh sách tài liệu này , đặc biệt. CheatSheet Clojure. Ngoài ra, dự án mẫu nói chung cho thấy cách tôi muốn cấu trúc mọi thứ. :)
Chức năng giúp ích nhiều nhất là partition. Xem tài liệu .
Tái cấu trúc nhẹ
Bạn có thể đơn giản hóa nó một lượng nhỏ và làm cho nó nhỏ gọn hơn một chút bằng cách sử dụng các chức năng chuyên biệt hơn như reducevà cond->. Phiên bản này sử dụng bản đồ để giữ trạng thái và reducethực hiện lặp:
(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))