2

I am trying to pass the (lazy) sequence returned from a map operation to another map operation, so that I can look up elements in the first sequence. The code is parsing some football fixtures from a text file (in row/column format), cleaning it up, and then returning a map.

Here is the code:

(ns fixtures.test.lazytest
  (:require [clojure.string :as str])
  (:use [clojure.test]))

(defn- column-map
  "Produce map with column labels given raw data, return nil if not enough columns"
  [cols]
  (let [trimmed-cols (map str/trim cols)
        column-names {0 :fixture-type, 1 :division, 2 :home-team, 4 :away-team}]
    (if (> (count cols) (apply max (keys column-names)))
      (zipmap (vals column-names) (map trimmed-cols (keys column-names)))
      nil)))

(deftest test-mapping
  (let [cols '["L" "   Premier " "  Chelsea  " "v" "\tArsenal  "]
        fixture (column-map cols)]
    (is (= "Arsenal" (fixture :away-team)))
    (is (= "Chelsea" (fixture :home-team)))
    (is (= "Premier" (fixture :division)))
    (is (= "L" (fixture :fixture-type)))
  )
)

(run-tests 'fixtures.test.lazytest)

The approach I'm taking is:

  1. Clean up the vector of column data (remove leading/trailing spaces)
  2. Using zipmap, combine column name keywords with their corresponding element in the column data (note that not all columns are used)

The problem is, using the trimmed-cols in zipmap causes

java.lang.ClassCastException: clojure.lang.LazySeq cannot be cast to clojure.lang.IFn

I think I know why this is happening ... as trimmed-cols is a LazySeq, the map called from zipmap is objecting to receiving a non-function as its first argument.

To fix this I can change the let to:

trimmed-cols (vec (map str/trim cols))

But this doesn't feel like the "best" option.

So:

  1. Is there a good general solution for using the result of a map operation as the "function" argument to another map?
  2. Is there a better approach for deriving a map of {: value} pairs from a vector of raw value data, where not all of the vector elements are used?

(I hesitate to ask for an idiomatic solution, but imagine somewhere there must be a generally accepted way of doing this.)

Andrew Whitehouse
  • 2,738
  • 3
  • 31
  • 35

1 Answers1

3

I am not entirely sure what you're after, but I can see why your code fails - like you said, trimmed-cols is not a function. Wouldn't this simply work?

I'm using :_dummy keys for columns that should be skipped. You can use as many as you like in the column-names vector: zipmap will merge them into one, which is removed by dissoc.

(defn- column-map
  "Produce map with column labels given raw data, return nil if not enough columns"
  [cols]
  (let [trimmed-cols (map str/trim cols)
        column-names [:fixture-type :division :home-team :_dummy :away-team]]
    (if (>= (count cols) (count column-names))
      (dissoc (zipmap column-names trimmed-cols) :_dummy)
      nil)))

(column-map ["L" "   Premier " "  Chelsea  " "v" "\tArsenal  "])
; => {:away-team "Arsenal", :home-team "Chelsea", :division "Premier", :fixture-type "L"}
Gert
  • 3,839
  • 19
  • 22
  • Nice ... I like how your solution simplifies the zipmap call, and the keyword names make it clear(er) what is happening, so this addresses the second part of my question. On the first part, converting the map result to a vector with (vec ...) allows it to be accessed as a function (which should be OK for small list sizes). – Andrew Whitehouse Jan 09 '12 at 10:55