Frage

I seem to have nasty problem with clojure I/O (or typesystem). The point is that this function, which I expect to consume collection of collections of strings and numbers or strings, and returns dictionary of strings associated with numbers, like

(costlist '( '("Milk" 4) '("Bread" 2) '("Milk")))

giving

{"Milk" 4, "Bread" 2 }

Defined by

(defn costlist [lst]
    ;returns list of costs and appropriate names
          (let  [snds (filter (fn [x] (not (identical? nil x))) (seconds lst))]
          (zipmap 
                   (firsts (take (count snds) (firsts lst))) 
                   (snds  lst))))

when consuming lists of type clojure.lang.PersistentList (which I converted from clojure.lang.LazySeq) throws error message

clojure.lang.LazySeq cannot be cast to clojure.lang.IFn

Which only confuses me, as any of its arguments don't seem to be LazySeq to me.

War es hilfreich?

Lösung

The problem is that snds is the lazy seq, so (snds lst) throws an error. filter always returns a lazy seq.

Your function also is too complicated. Try to make it simpler:

(defn costlist [lst]
  (zipmap (map first lst)
          (remove nil? (map second lst))))

Now you can do what you want:

(costlist (list '("Milk" 4) '("Bread" 2) '("Milk")))

I'm using list because quote prevents evaluation of an expression (see ToBeReplaced's answer):

=> '( '("Milk" 4) '("Bread" 2) '("Milk"))
((quote ("Milk" 4)) (quote ("Bread" 2)) (quote ("Milk")))

So, you should avoid using quote for building lists.

Your solution also suggests that nil values may occur only at the end of the list. You can easily fix it:

(defn costlist [lst]
  (->>  (filter (comp #{2} count) lst)
        (map vec)
        (into {})))

So, now

(costlist (list '("Milk" 4) '("Milk") '("Bread" 2)))

will work too.

Andere Tipps

It's hard to give you a precise answer because you are also using some of your own functions (seconds and firsts). However, you should consider whether you want costlist to be the quoted form you give above.

The above is equivalent to:

((quote ("Milk" 4)) (quote ("Bread" 2)) (quote ("Milk")))

I think you want something more like:

(list '("Milk" 4) '("Bread" 2) '("Milk"))

In your example, the outermost quote is causing the inner quotes to be quoted!

Don't use ' inside '(). You don't want to quote ' to get quote. Given you write your list as:

'(("Milk" 4)("Bread" 2)("Milk"))

The most elegant probably be:

(def costlist (comp (partial into {}) 
                    (partial map vec) 
                    (partial filter second))

similar to

(def costlist #(into {} (map vec (filter second %))))

Unfortunately this will create two lazy-seqs and one would be nicer for performance. You could (use '(clojure.core [reducers :as r]) and compose yourself a cool reducing function:

(def r-fn (comp (r/map vec) (r/filter second)))

Which leaves you to

(def costlist #(into {} (r-fn %)))

Thanks to into using reduce conj your list elements will only be allocated once.

Since order does not matter for hash-maps, you can also user r/fold. Then your list will be filtered and the elements converted to vectors in parallel (If your list has more than 512 elements).

(def constlist #(r/fold (r/monoid conj hash-map) (r-fn %)))

Depending on the size of the collection, reducers may be a bit too heavy. For a small size like the one from your example I recommend to write your own reducing function:

(def costlist #(reduce (fn [acc [k v]]
                         (if v
                           (assoc acc k v)
                           acc)) {} %)
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top