Gishu
Gishu

Reputation: 136613

Why doesn't this keyword function lookup work in a hashmap?

I guess I need some eyeballs on this to make some sense of this

    (println record)
    (println (keys record) " - " (class record) " : " (:TradeId record) (:Stock record))
    (doall  (map #(println "Key " % "Value " (% record)) (keys record)))

Output:

{:Stock ATT, :AccountId 1, :TradeId 37, :Qty 100, :Price 117, :Date 2011-02-24T18:30:00.000Z, :Notes SPLIT 1:10, :Type B, :Brokerage 81.12}

(:Stock :AccountId :TradeId :Qty :Price :Date :Notes :Type :Brokerage)  -  clojure.lang.PersistentHashMap  :  nil ATT

Key  :Stock Value  ATT
Key  :AccountId Value  1
Key  :TradeId Value  37
...

The issue is (:TradeId record) doesn't work even though it exists as a key. Iterating through all keys and values - Line 3 - yields the right value. Tried renaming the column in the csv but no change in behavior. I see no difference from the other columns (which work) except that this is the first column in the csv.

The hashmap is created from code like this - reading records from a CSV. Standard code from the clojure.data.csv package.

(->> (csv/read-csv reader)
           csv-data->maps
           (map #(my-function some-args %))
           doall))


(defn csv-data->maps 
  "Return the csv records as a vector of maps"
  [csv-data]
  (map zipmap
       (->> (first csv-data) ;; First row is the header
            (map keyword) ;; Drop if you want string keys instead
            repeat)
       (rest csv-data)))

Upvotes: 2

Views: 149

Answers (3)

jas
jas

Reputation: 10865

The "first column" thing is definitely suspicous and points to some invisible characters such as a BOM quietly attaching itself to your first keyword.

To debug try printing out the hex of the names of the keywords. And/or maybe you'll see something if you do a hex dump, e.g., with head -n 2 file.csv | od -x, of the first few lines of the input file.

Upvotes: 2

Gishu
Gishu

Reputation: 136613

Once I reached the point of trying anything, I copied the keyword from the REPL and pasted it into VSCode and sure enough - there was this weird looking character :?Id within the keyword. Using the weird keyword, the lookup worked.

Workaround: Added a dummy column as the first column.

Then things started to click into place, I remembered reading something on BOM in the csv reader project docs. https://github.com/clojure/data.csv#byte-order-mark

Downloaded a hexdump file viewer which confirmed the problem bytes at the start of the file.

o;?Id,AccountId,...

Final solution: Before passing the reader to the data.csv read function, skip over the unwanted bytes.

(.skip reader 1)

The world makes sense again.

Upvotes: 1

amalloy
amalloy

Reputation: 91907

I would try two things. First, print the type of each key. They should all be clojure.lang.Keyword, if the creation code you included is accurate and my-function preserves their type; but if you created it in some other way and misremembered, you might discover that the key is a symbol, or a string or something like that. In general, don't use println on anything but strings, because it's pretty low-fidelity. prn is better at conveying an accurate picture of your data - it's not perfect, but at least you can tell a string from a keyword with it.

Second, look at the printed values more carefully, e.g. with od -t x1 - or you could do it in process with something like:

(let [k (key (first m)), s (name k)]
  (clojure.string/join " " 
                       (for [c s] 
                         (format "%02x" (int c)))))

If the result isn't "53 74 6f 63 6b", then you have some weird characters in your file - maybe nonprinting characters, maybe something that looks like a capital S but isn't, whatever.

Upvotes: 1

Related Questions