Вопрос

I am a university instructor trying to have a little Clojure fun and calculate my grades at the same time. I have made a list of all of my students numbers with their corresponding grades into something that looks like this:

(def grades-1 (let [s18129  [100    70  85  71  85]  
                    s18121  [80 75  85  81  85]
                    r18131  [75 60  80  56  75] ...])
                    ;; r before the number is shorthand for repeater 
                    ;; and not important to this question

I would like the grades to be adusted so that the first, second, third, fourth and fifth grades in these vectors are weighted to 10%, 20%, 15%, 25% and 30%, respectively. To help me accomplish this task, I created a helper function:

(defn percentify
  "adjust raw score to weighted percentile"
  [raw percentile]
  (* (/ raw 100) percentile))

I want to create another function that will map over the grades list and apply the percentify function to each student's grades at a specfic weight for each element in the vector, based on its position. This is what I am working with right now, but I can't make it work in the repl. I think it has to do with how I've structured my class data or perhaps, I am confused about the use of println.

(defn finalize [grades-list]
(let [[[student] [a b c d e]] grades-list]
  (println
   (percentify a 10.0)
   (percentify b 20.0)
   (percentify c 15.0)
   (percentify d 25.0)
   (percentify e 30.0))))

I then want to call on this function to return the final grades with the numbers of the students in a readable form. Could someone please help to put me on the right track?

Это было полезно?

Решение 2

The data is naturally a map from student-number (a string) to a vector of grades:

(def grades-1 {"s18129"  [100 70  85  71  85]  
               "s18121"  [80 75  85  81  85]
               "r18131"  [75 60  80  56  75]})

... and a vector of weights to be applied to the above:

(def grade-weights [10 20 15 25 30])

... turning out a map from student-number to final grade. How can we factor this?

A function to calculate the average according to a given weights vector is

(fn [grades] (/ (reduce + 0.0 (map * weights grades)) (reduce + weights)))

... where

  • the initial value of 0.0 forces the use of floating point arithmetic and
  • the weights need not add up to 1.0 or 100 or any number in particular. Scaling is included.

The following converts each value of a-map into a function f of that value:

(fn [f a-map] (into {} (map (fn [[k v]] [k (f v)]) a-map)))

So a function to deliver a map of final grades from a map of grade vectors and a weighting vector is ...

(defn av-grades [gt weights]
  (let [weight-av (fn [grades]
                    (/ (reduce + 0.0 (map * weights grades)) (reduce + weights)))
        convert-map (fn [f a-map] 
                      (into {} (map (fn [[k v]] [k (f v)]) a-map)))]
    (convert-map weight-av gt)))

And, sure enough, ...

=>(av-grades grades-1 grade-weights)
{"r18131" 68.0, "s18121" 81.5, "s18129" 80.0}

Другие советы

Firstly, you seem to be assigning each student's grade vector to a separate local in a let form. To map a function across all grade vectors, you'll need to put them in a single data structure first:

(def grades-1 [[100    70  85  71  85]  
               [80 75  85  81  85]
               [75 60  80  56  75]])

Now, a function to apply a vector of weights to a grade vector will be useful (percentify here is your original function):

;; taking weights first for convenient use with partial
(defn percentify-vector [pvec rvec]
  (mapv percentify rvec pvec))

Finally, we can map the above across the collection of grade vectors:

(mapv (partial percentify-vector [10.0 20.0 15.0 25.0 30.0]) grades-1)

I can't improve on Michał Marczyk's answer, but I'll suggest a possible alternative. It's probably overkill for your needs at the moment. What you want to do is essentially matrix multiplication: You're multiplying a matrix by a vector in order to produce a vector. This can be done directly using the core.matrix library if desired. Let's say that the individual item grades are for quizzes. First you would need to run Clojure with the core.matrix library loaded, e.g. using Leiningen. Then in the REPL or in a file, you could do this:

(use 'clojure.core.matrix)

;; Define quiz grade matrix: Each row is a student, each column is a quiz.
(def quiz-grades [[100 70  85  71  85]
                  [ 80 75  85  81  85]
                  [ 75 60  80  56  75]])

;; Define weights representing contributions of each quiz to the final grade.
(def quiz-weights [0.10 0.20 0.15 0.25 0.30])

Here I defined the weights for quizzes as decimals representing fractions of 1, rather than whole numbers, but it's easy to convert between the two representations using map, mapv, or an analogous core.matrix function, emap. Now you can get the final grades for each student like this:

user=> (mmul quiz-grades quiz-weights)
[80.0 81.5 68.0]

(Note: core.matrix has different underlying implementations of vectors and matrices. In my example here, I used the default persistent-vector implementation, in which vectors and matrices are equivalent to regular Clojure vectors and vectors of vectors. In order to switch between different implementations easily, it would be better to embed the Clojure vectors above in calls to the function matrix, but that's not necessary with the persistent-vector implementation.)

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top