@Hendekagon's answer is a good way to get the job done, but we can look at a from-scratch implementation. Though probably not the best solution, hopefully the sample design helps get you under way.
If you want to query your structure, in Clojure, you're going to be thinking about maps. Let's take as our goal something that looks like this:
{"1" {"a" "7", "b" "8", "c" "9"},
"2" {"a" "s", "b" "q", "c" "r"},
"3" {"a" "2", "b" "7", "c" "1"}}
Here, row names are keys into to maps of column names to table elements. With this structure, we can easily query an element of the table using get-in.
(get-in table ["2" "b"]) ; => "q"
Okay. How do we do it?
Let's pretend for a second we've already read in our file and have it as a string. Then, we need to transform it into our map-of-maps. Our function's going to look something like this:
(defn parse-table
[raw-table-data]
...)
The first step is to pull out all of the important bits of data - the row names, the column names, and the table elements. However, before we can grab them, we need to parse the raw-table-data string into a structure more easily traversed. We'll split the string on newlines, then tokenize the lines on whitespace using a helper function tokens.
(use '[clojure.string :only [split split-lines trim]])
(defn tokens
[s]
(-> s trim (split #"\s+")))
(defn parse-table
[raw-table-data]
(let [table-data (map tokens (split-lines raw-table-data))]
...
)
table-data looks something like this:
[["a", "b", "c"],
["1", "7", "8", "9"],
["2", "s", "q", "r"],
["3", "2", "7", "1"]]
This makes it easy to get to the good stuff:
(defn parse-table
[raw-table-data]
(let [table-data (map tokens (split-lines raw-table-data))
column-names (first table-data)
row-names (map first (next table-data))
contents (map next (next table-data))]
...
)
With the data teased out, we just need to stitch it together. An easy way to do this is to build all of our individual mappings of row-to-column-to-elements and then combine them. I'll mention that this isn't the most efficient way, but it's pretty clean.
Creating a helper function pairs that simply sticks elements of two collections side-by-side, we can get a sequence of mappings using a for comprehension.
(defn pairs
[coll1 coll2]
(map vector coll1 coll2))
(for [[row-name row-contents] (pairs row-names contents)
[column-name element] (pairs column-names row-contents)]
{row-name {column-name element}})
This gives a sequence of maps-to-maps. We just need to merge it into one big map and the function is complete.
(defn parse-table
[raw-table-data]
(let [table-data (map tokens (split-lines raw-table-data))
column-names (first table-data)
row-names (map first (next table-data))
contents (map next (next table-data))]
(apply merge-with merge
(for [[row-name row-contents] (pairs row-names contents)
[column-name element] (pairs column-names row-contents)]
{row-name {column-name element}}))))
Now, we can slurp up a table file and parse it.
(def table
(->
"file"
slurp
parse-table))
This gets us to our goal.
(println (get-in table ["2" "b"])) ; => "q"