How do I do this in clojure?

8 views
Skip to first unread message

Jesse Aldridge

unread,
Feb 16, 2009, 9:18:09 PM2/16/09
to Clojure

I'm trying to port some Python code to Clojure. I'm new to functional
programming and I hit a function that is causing my brain to melt.
The python function looks something like this:


def build_table():
num_cols = 3
selected_row = 0
selected_col = 0
strings = ["cat", "dog", "", "rabbit", "frog", "elephant",
"gorilla"]

row, col = 0, 0
table_contents = "<table cellpadding=30 bgcolor=#6666aa>\n<tr>\n"
for string in strings:
if string == "":
continue

if col >= num_cols:
row += 1
col = 0
table_contents += "</tr>\n<tr>"

def new_cell():
bg_color = "cfcfdf"
if(col == selected_col and row == selected_row):
bg_color = "d0e0ff"
return "<td bgcolor=#" + bg_color + ">" + \
"<font color=#0000cc>" + string[0] + \
"</font>" + string[1:] + "</td>\n"
table_contents += new_cell()

col += 1

table_contents += "</tr>\n</table>"
return table_contents

If someone could please demonstrate how to do this in Clojure, I think
it would greatly help my understanding of the language and functional
programming in general.

Mark Engelberg

unread,
Feb 16, 2009, 10:39:18 PM2/16/09
to clo...@googlegroups.com
Suggestion: Provide a statement of purpose as to what this function
is supposed to do. What are its inputs and what is its output? Can
you break it down into smaller functions?

Right now you have a complicated function that takes no inputs and
always produces the same string. It seems somewhat nonsensical.
Surely you want this function to take some inputs, and produce
different strings depending on the input.

Check out htdp.org as a resource for learning more about functional
programming and good program design principles.

GS

unread,
Feb 16, 2009, 10:55:38 PM2/16/09
to Clojure
On Feb 17, 1:18 pm, Jesse Aldridge <JesseAldri...@gmail.com> wrote:
> I'm trying to port some Python code to Clojure.  I'm new to functional
> programming and I hit a function that is causing my brain to melt.
> The python function looks something like this:
> [..........]
>
> If someone could please demonstrate how to do this in Clojure, I think
> it would greatly help my understanding of the language and functional
> programming in general.

You'll probably get a decent answer from someone, but if not, try
rephrasing your question:

"How do I do this in Clojure? [Insert concise, precise English
description here]"

Reason being: your code has too much low-level detail in it for me to
parse effectively. Others may be more willing to slog away at it and
work out what specifically is causing you trouble.

Cheers,
Gavin

Jeffrey Straszheim

unread,
Feb 16, 2009, 11:01:09 PM2/16/09
to clo...@googlegroups.com
Minus the bad html, you'll want something like this:

(defn make-table
  "Make an html table n rows wide from collection col."
  [col n]
  (let [make-row (fn [row]
                   (let [cont (map #(str "<td>" % "</td>") row)]
                     (apply str "<tr>" (conj (vec cont) "</tr>"))))
        cont (map make-row (partition n col))]
    (apply str "<table>" (conj (vec cont) "</table>"))))

(make-table ["fred" "mary" "sally" "joan"] 2)

Dan

unread,
Feb 16, 2009, 11:25:32 PM2/16/09
to clo...@googlegroups.com
On Mon, Feb 16, 2009 at 9:18 PM, Jesse Aldridge <JesseA...@gmail.com> wrote:

You should probably rework your Python logic too, you can make it a lot more concise and understandable:

def make_table(cells, cols):
        spam = ""
    for i in xrange(0, len(cells), cols):
        spam += "<tr><td>" + "</td><td>".join(cells[i:i+cols]) + "</td></tr>
        return "<table>" + spam + "</table>"

Jesse Aldridge

unread,
Feb 17, 2009, 1:19:08 AM2/17/09
to Clojure
Alright, I got it working. Thanks for the replies.

Timothy Pratley

unread,
Feb 17, 2009, 2:25:11 AM2/17/09
to Clojure
I found this question interesting for two reasons:

(1) A selected item has been specified which needs to be handled
differently - at first glance suggests an iterative solution.
(2) There is good support for html in clojure via libraries, so you
should avoid string concatenation of tags.

So here is my solution:
http://groups.google.com/group/clojure/web/layout.clj

Notably it bolds the selected item without any iteration, and returns
a list representation of the html instead of a string. I'm not
familiar with the html libraries to render this representation so
forgive any inconsistencies there, but the output is:

(:table (:tr (:item (:bold "cat")) (:item "dog") (:item "")) (:tr
(:item "rabbit") (:item "frog") (:item "elephant")))
Which will be validated by the rendering library, giving an additional
security that it is correct.

In order to deal with the 'selected item' I had to jump through some
hoops to treat collections as vectors and use assoc on that. Is there
perhaps a more elegant way? Overall though I hope it is a good example
of how and why it is a good idea to avoid string catting and instead
think of the problem in terms of creating a structure.

Really you are building a tree based upon some inputs. I'd be
interested in other ways the tree could be constructed.


Regards,
Tim.

Laurent PETIT

unread,
Feb 17, 2009, 4:05:34 AM2/17/09
to clo...@googlegroups.com
Hello,

I wanted to try it too, so I grabbed Timothy's version, and did some improvements (I hope they are, feedback welcome ! :-) over it.

Something I corrected is the removal of null or blank items, as in the first python version.
I also got rid of the assoc by using update-in.
And I felt that there was too much use of apply, but this may be subjective.

Question: In order to use update-in (or assoc in the previous version), one has to transform the seqs returned by partition into a vector of vectors, thus the use of 'into.
It still seems a little bit ugly to me. Is there a more concise (if not idiomatic) way to do that ?

Here is the new version:

(ns html.table)

(defn make-table [column-count selected-row selected-column words]
  (let [remove-blank (partial remove #(or (nil? %) (-> % .trim .isEmpty)))
        table (into [] (map (partial into []) (partition column-count (remove-blank words))))
        table-with-selected (update-in table [selected-row selected-column] (partial list :bold))]
    (cons :table
          (map (fn [r] (cons :tr (map (fn [i] (list :item i)) r)))
               table-with-selected))))

(defn test-mt []
    (make-table 3 1 0 ["cat" "dog" "rabbit" "cat" "dog" "rabbit" "frog" "elephant" "gorilla"]))

Regards,

--
Laurent



2009/2/17 Timothy Pratley <timothy...@gmail.com>

Michael Wood

unread,
Feb 17, 2009, 4:23:43 AM2/17/09
to clo...@googlegroups.com
On Tue, Feb 17, 2009 at 11:05 AM, Laurent PETIT <lauren...@gmail.com> wrote:
> Hello,
>
> I wanted to try it too, so I grabbed Timothy's version, and did some
> improvements (I hope they are, feedback welcome ! :-) over it.
>
> Something I corrected is the removal of null or blank items, as in the first
> python version.
> I also got rid of the assoc by using update-in.
> And I felt that there was too much use of apply, but this may be subjective.
>
> Question: In order to use update-in (or assoc in the previous version), one
> has to transform the seqs returned by partition into a vector of vectors,
> thus the use of 'into.
> It still seems a little bit ugly to me. Is there a more concise (if not
> idiomatic) way to do that ?
>
> Here is the new version:
>
> (ns html.table)
>
> (defn make-table [column-count selected-row selected-column words]
> (let [remove-blank (partial remove #(or (nil? %) (-> % .trim .isEmpty)))

I got an exception here:
java.lang.IllegalArgumentException: No matching field found: isEmpty
for class java.lang.String (NO_SOURCE_FILE:0)

I assume you're using Java 1.6?

Changing it to the following fixes it for me:
[...]
(let [remove-blank (partial remove #(or (nil? %) (empty? (.trim %))))
[...]

> table (into [] (map (partial into []) (partition column-count
> (remove-blank words))))
> table-with-selected (update-in table [selected-row selected-column]
> (partial list :bold))]
> (cons :table
> (map (fn [r] (cons :tr (map (fn [i] (list :item i)) r)))
> table-with-selected))))
>
> (defn test-mt []
> (make-table 3 1 0 ["cat" "dog" "rabbit" "cat" "dog" "rabbit" "frog"
> "elephant" "gorilla"]))

--
Michael Wood <esio...@gmail.com>

Laurent PETIT

unread,
Feb 17, 2009, 4:28:21 AM2/17/09
to clo...@googlegroups.com
Thanks Michael

2009/2/17 Michael Wood <esio...@gmail.com>

Timothy Pratley

unread,
Feb 17, 2009, 7:44:21 AM2/17/09
to Clojure
Hi Laurent,

Thanks for those improvements. I agree that it would be nice in this
case to be able to 'update-in' an arbitrary tree. I played around
using zippers to achieve this with some success, however it was very
verbose. Next I tried implementing update-in-list which seemed a bit
more obvious, though is still quite verbose. However it make the core
program itself quite nice (excluding the helpers):

(uploaded new version http://clojure.googlegroups.com/web/layout.clj,
you may need to F5 reload it to see the new version)


Regards,
Tim.

Emeka

unread,
Feb 17, 2009, 7:51:13 AM2/17/09
to clo...@googlegroups.com
Jesse,
Could I see your own version.

Emeka



Jesse Aldridge

unread,
Feb 17, 2009, 9:03:31 AM2/17/09
to Clojure
> Jesse,
> Could I see your own version.

Haha, I was afraid someone would say this.
Here is my embarrassingly bad (but working) version:

(defn build-table []
(def num-cols 3)
(def selected-row 0)
(def selected-col 0)
(def all-strings ["apple" "cat" "dog" "" "frog" "elephant"
"gorilla"])

(defn new_cell [string row col]
(defn bg-color []
(if (and (= col selected-col)
(= row selected-row))
"d0e0ff"
"cfcfdf"))
(str "<td bgcolor=#" (bg-color) ">" "<font color=#0000cc>"
(first string) "</font>" (.substring string 1 (.length
string))
"</td>"))

(loop [row 0 col 0
table_contents "<table cellpadding=30 bgcolor=#6666aa>\n<tr>
\n"
strings all-strings]
(if strings
(if (not= (first strings) "")
(if (>= col num-cols)
(recur (inc row) 0 (str table_contents "</tr>\n<tr>")
strings)
(recur row (inc col)
(str table_contents (new_cell (first strings) row col))
(rest strings)))
(recur row col table_contents (rest strings)))
(str table_contents "</tr>\n</table>"))))

I actually got it working before reading any of the replies here. So
I'll probably take some of these suggestions and use them to improve
the code.

Emeka

unread,
Feb 17, 2009, 10:04:33 AM2/17/09
to clo...@googlegroups.com
>Haha, I was afraid someone would say this.
>Here is my embarrassingly bad (but working) version:

I won't say that, you ported Python to Clojure while maintaining Python spirit. That's great!


Emeka

Laurent PETIT

unread,
Feb 17, 2009, 10:07:37 AM2/17/09
to clo...@googlegroups.com

2009/2/17 Emeka <emeka...@gmail.com>

>Haha, I was afraid someone would say this.
>Here is my embarrassingly bad (but working) version:

I won't say that, you ported Python to Clojure while maintaining Python spirit. That's great!

Are you serious, or is this a joke somewhat bashing the python language ?
 



Emeka






Dan

unread,
Feb 17, 2009, 10:24:01 AM2/17/09
to clo...@googlegroups.com
I won't say that, you ported Python to Clojure while maintaining Python spirit. That's great!

Are you serious, or is this a joke somewhat bashing the python language ?
 

I guess he meant maintaining the spirit of the original python code. The original code was overtly long and non-idiomatic and the clojure translation is faithful to what the code was actually doing.

Laurent PETIT

unread,
Feb 17, 2009, 10:27:54 AM2/17/09
to clo...@googlegroups.com
Yeah, I think you're right. That's "Python spirit" in place of "original code spirit" that surprised me.


2009/2/17 Dan <redal...@gmail.com>

Michael Wood

unread,
Feb 17, 2009, 10:41:53 AM2/17/09
to clo...@googlegroups.com
Hi

On Tue, Feb 17, 2009 at 4:03 PM, Jesse Aldridge <JesseA...@gmail.com> wrote:
>
>> Jesse,
>> Could I see your own version.
>
> Haha, I was afraid someone would say this.
> Here is my embarrassingly bad (but working) version:
>
> (defn build-table []
> (def num-cols 3)
> (def selected-row 0)
> (def selected-col 0)
> (def all-strings ["apple" "cat" "dog" "" "frog" "elephant"
> "gorilla"])

let would be better than def here. def creates basically a global
variable (although I'm sure someone will complain about me calling
them that :)

> (defn new_cell [string row col]
> (defn bg-color []

There's no point using nested defns, since defn is defined in terms of
def. i.e. these are also global.

--
Michael Wood <esio...@gmail.com>

James Reeves

unread,
Feb 17, 2009, 3:21:52 PM2/17/09
to Clojure
(ns html-table
(:use clojure.contrib.prxml)
(:use clojure.contrib.seq-utils))

(defn print-table [grid selected]
(prxml
[:table {:cellpadding 30, :bgcolor "#6666aa"}
(for [[x row] (indexed grid)]
[:tr
(for [[y cell] (indexed row)]
[:td {:bgcolor (if (= selected [x y])
"#d0e0ff"
"#cfcfdf")}
[:font {:color "#0000cc"} (subs cell 0 1)]
(subs cell 1)])])]))

(defn make-grid [col-size data]
(partition col-size
(filter #(not= % "") data)))

(print-table
(make-grid 3 ["cat" "dog" "" "rabbit" "frog" "elephant" "gorilla"])
[0 0])

(println)

Jesse Aldridge

unread,
Feb 17, 2009, 9:24:18 PM2/17/09
to Clojure
Oh wow, this is perfect. Thanks.
Reply all
Reply to author
Forward
0 new messages