I started to look closer at Clojure after Rich Hickey's presentation at QCon London in March, this is my first post. I spend my days programming Java, but my journey as developer did actually start with an ML programming course many years ago. It has been great fun to re-discover the functional style of programming again! This list is a great source of knowledge.
A colleague of mine wrote an article about how to convert a piece of Java code to Groovy a few months ago. As an exercise I thought I should do the same, but to Clojure of course. I'd really appreciate some feedback on the solution. I hope you aren't too sick of these kind of posts just yet :-)
The code orders a list of characters, first by frequency and then alphabetically if two characters happen to have the same frequency.
public List<String> orderByFreq(List<String> in) { final Bag bag = new HashBag(in); List<String> out = new ArrayList<String>(bag.uniqueSet());
Collections.sort(out, new Comparator<String>() { public int compare(String a, String b) { int freq = Integer.valueOf( bag.getCount(b)).compareTo(bag.getCount(a) ); return freq != 0 ? freq : a.compareTo(b); } });
return out; }
}
My shot at a Clojure implementation, with inspiration from a previous post in this group on a similar problem:
> I started to look closer at Clojure after Rich Hickey's presentation
> at QCon London in March, this is my first post. I spend my days
> programming Java, but my journey as developer did actually start with
> an ML programming course many years ago. It has been great fun to
> re-discover the functional style of programming again! This list is a
> great source of knowledge.
> A colleague of mine wrote an article about how to convert a piece of
> Java code to Groovy a few months ago. As an exercise I thought I
> should do the same, but to Clojure of course. I'd really appreciate
> some feedback on the solution. I hope you aren't too sick of these
> kind of posts just yet :-)
> The code orders a list of characters, first by frequency and then
> alphabetically if two characters happen to have the same frequency.
The idea is to gradually, over a few steps, move from a Java
implementation to a pure Clojure implementation. A this step the
Clojure implementation is called by the Java JUnit-test (see complete
test below). In the last step, the Java-references will be removed
from the Clojure implementation and the JUnit test replaced by Clojure
tests.
>> I started to look closer at Clojure after Rich Hickey's presentation
>> at QCon London in March, this is my first post. I spend my days
>> programming Java, but my journey as developer did actually start with
>> an ML programming course many years ago. It has been great fun to
>> re-discover the functional style of programming again! This list is a
>> great source of knowledge.
>> A colleague of mine wrote an article about how to convert a piece of
>> Java code to Groovy a few months ago. As an exercise I thought I
>> should do the same, but to Clojure of course. I'd really appreciate
>> some feedback on the solution. I hope you aren't too sick of these
>> kind of posts just yet :-)
>> The code orders a list of characters, first by frequency and then
>> alphabetically if two characters happen to have the same frequency.
>> public List<String> orderByFreq(List<String> in) {
>> final Bag bag = new HashBag(in);
>> List<String> out = new ArrayList<String>(bag.uniqueSet());
>> Collections.sort(out, new Comparator<String>() {
>> public int compare(String a, String b) {
>> int freq = Integer.valueOf(
>> bag.getCount(b)).compareTo(bag.getCount(a)
>> );
>> return freq != 0 ? freq : a.compareTo(b);
>> }
>> });
>> return out;
>> }
>> }
>> My shot at a Clojure implementation, with inspiration from a previous
>> post in this group on a similar problem:
> The idea is to gradually, over a few steps, move from a Java
> implementation to a pure Clojure implementation. A this step the
> Clojure implementation is called by the Java JUnit-test (see complete
> test below). In the last step, the Java-references will be removed
> from the Clojure implementation and the JUnit test replaced by Clojure
> tests.
> >> I started to look closer at Clojure after Rich Hickey's presentation
> >> at QCon London in March, this is my first post. I spend my days
> >> programming Java, but my journey as developer did actually start with
> >> an ML programming course many years ago. It has been great fun to
> >> re-discover the functional style of programming again! This list is a
> >> great source of knowledge.
> >> A colleague of mine wrote an article about how to convert a piece of
> >> Java code to Groovy a few months ago. As an exercise I thought I
> >> should do the same, but to Clojure of course. I'd really appreciate
> >> some feedback on the solution. I hope you aren't too sick of these
> >> kind of posts just yet :-)
> >> The code orders a list of characters, first by frequency and then
> >> alphabetically if two characters happen to have the same frequency.
> >> public class JavaOrderer implements Orderer {
> >> public List<String> orderByFreq(List<String> in) {
> >> final Bag bag = new HashBag(in);
> >> List<String> out = new ArrayList<String>(bag.uniqueSet());
> >> Collections.sort(out, new Comparator<String>() {
> >> public int compare(String a, String b) {
> >> int freq = Integer.valueOf(
> >> bag.getCount(b)).compareTo(bag.getCount(a)
> >> );
> >> return freq != 0 ? freq : a.compareTo(b);
> >> }
> >> });
> >> return out;
> >> }
> >> }
> >> My shot at a Clojure implementation, with inspiration from a previous
> >> post in this group on a similar problem:
I've been playing along at home for awhile now, but this example hit
close to home -- I've written variations on this code countless times
in perl, and figured I'd take a stab at clojuring it while also taking
advantage of clojure.contrib.seq-utils's very useful to the matter at
hand group-by. I'd understand if the OP doesn't want to use this code
due to dependency on non-core or if the variations on making it more
general obscure the original intent...
Anyway, my style could use work and the code is littered with comments
getting in the way that ask all kinds of questions (I admit, I haven't
done my due diligence on finding out if any of these functions exist
beyond group-by), but hopefully this variation is helpful to someone
or there are answers to my various questions. I'd *love* feedback.
Thanks in advance,
--Benjy
PS. To the original poster, the use of destructuring binds for the
arguments to cmpr was rad -- it was something I overlooked when I was
doing this in my head over dinner and I was imagining a bunch of
inanity involving hash lookups and ifs for falling back to comparing
the keys as you'd have to do in other languages.
;; is there a better option for this?
(defn false-if-zero [t] (if (= t 0) nil t))
;; the body of this fn should probably be a macro that takes
;; any number of comparisons and or-chain them correctly such that
;; ties cascade to the next comparison and obviates the need for
;; explicit calls to false-if-zero. Does it already exist?
(defn cmpr [[key-a val-a] [key-b val-b]]
(or (false-if-zero (compare val-b val-a))
(compare key-a key-b)))
(defn count-hash-vals [coll]
;; apply hash-map mapcat was the first thing
;; I found that worked here... better options ++welcome.
(apply hash-map
(mapcat #(let [[k v] %]
(list k (count v)))
coll)))
(defn orderByFreq [coll]
(or
(keys (sort cmpr
(count-hash-vals (group-by identity coll)) ))) ;; as an
exercise for the reader, use this call to implement (majority? ...) or
(plurality? ...) etc.
'()) ;; I don't think this OR short circuiting is idiomatic in
clojure, but it sure is handy for "empty list if nil" scenarios.
;; Of course, (empty-list-or-nil ...) reads nice enough...
;; ;; If sort or compare don't do an implicit orcish maneuver
;; ;; or otherwise memoize pieces of this call, performance could
;; ;; suffer; I have not researched this yet.
;; ;; Also, not general: assumes val is countable; val may
;; ;; be comparable but not countable. The above isn't
;; ;; fully general either, of course, but it's better.
;; the body of this fn should probably be a macro that takes
;; any number of comparisons and or-chain them correctly such that
;; ties cascade to the next comparison and obviates the need for
;; explicit calls to false-if-zero. Does it already exist?
This could be done with a macro, but I think that it could be done
simply with closure. Here's my attempt. I'm going to define two
helper functions first
(defn <=>
"This creates a comparator that wraps the mapping fn f."
[f]
(fn [a b]
(compare (f a) (f b))))
(defn inv-<=>
"This creates an inverse comparator that wraps the mapping fn f."
[f]
(fn [a b]
(compare (f b) (f a))))
These functions take in a mapping function and return a comparator/
inverse comparator, respectively. Now, I'll define a chaining
function.
(defn chain-comp
"This takes a list of comparator functions, and
chains them together. It returns another comparator.
It behaves similar to comp, in that fns are applied
right to left."
[& comps]
(fn [a b]
(loop [remaining-comps (reverse comps)]
(let [iter-comp (first remaining-comps)
iter-result (iter-comp a b)]
(if (and (zero? iter-result) (next remaining-comps))
(recur (rest remaining-comps))
iter-result)))))
I don't quite like the use of <=> and inv-<=>, but it the best
solution I could come up with that:
* Works with a closure (frequent readers should notice a theme in my
posts...)
* Allows inverting a comparison
* Uses the minimum amount of comparisons
Sean Devlin wrote: > If you're running "edge" Clojure, and not 1.0, I'd recommend writing > the tests in Clojure next, using the clojure.test namespace.
I'd like to keep it 1.0 for now, so the tests are in clojure.contrib.test-is. But I'll make note, or add an alternative version for clojure.test. It's nice to see the test support being added to core.
I believe this is a place where Java and Clojure differs a bit. In Java I'd expect an empty list back, but in Clojure I guess nil makes more sense? Compare how Map.keySet() in Java returns an emply set for an empty map, but (keys map) in Clojure returns nill for an empty map.
So, for interoperability with Java, I belive an empty seq is the way to go, but in a Clojure-only implementation nil would be a better fit. But in that case I guess I could just do: (keys (sort cmpr (count-words coll)))
Benjy wrote:
> I've been playing along at home for awhile now, but this example hit > close to home -- I've written variations on this code countless times > in perl, and figured I'd take a stab at clojuring it while also taking > advantage of clojure.contrib.seq-utils's very useful to the matter at > hand group-by. I'd understand if the OP doesn't want to use this code > due to dependency on non-core or if the variations on making it more > general obscure the original intent...
It's great with an alternative solution, and the dependency to clojure.contrib is not a problem.
[...]
> PS. To the original poster, the use of destructuring binds for the > arguments to cmpr was rad -- it was something I overlooked when I was > doing this in my head over dinner and I was imagining a bunch of > inanity involving hash lookups and ifs for falling back to comparing > the keys as you'd have to do in other languages.
Yes, it's nice. My first few iterations used (val e) and (key e) but destruction works nice here I think.
> ;; is there a better option for this? > (defn false-if-zero [t] (if (= t 0) nil t))
> ;; the body of this fn should probably be a macro that takes > ;; any number of comparisons and or-chain them correctly such that > ;; ties cascade to the next comparison and obviates the need for > ;; explicit calls to false-if-zero. Does it already exist? > (defn cmpr [[key-a val-a] [key-b val-b]] > (or (false-if-zero (compare val-b val-a)) > (compare key-a key-b)))
> (defn count-hash-vals [coll] > ;; apply hash-map mapcat was the first thing > ;; I found that worked here... better options ++welcome. > (apply hash-map > (mapcat #(let [[k v] %] > (list k (count v))) > coll)))
> (defn orderByFreq [coll] > (or > (keys (sort cmpr > (count-hash-vals (group-by identity coll)) ))) > '()) > ;; I don't think this OR short circuiting is idiomatic in > clojure, but it sure is handy for "empty list if nil" scenarios. > ;; Of course, (empty-list-or-nil ...) reads nice enough...
Yes, as you write, it may not be super-pretty, but it is handy if you need to guard against nil, as in this case.
> (defn count-hash-vals [coll] > ;; apply hash-map mapcat was the first thing > ;; I found that worked here... better options ++welcome. > (apply hash-map > (mapcat #(let [[k v] %] > (list k (count v))) > coll)))
> (defn orderByFreq [coll] > (or > (keys (sort cmpr > (count-hash-vals (group-by identity coll)) ))) ;; as an > exercise for the reader, use this call to implement (majority? ...) or > (plurality? ...) etc. > '()) ;; I don't think this OR short circuiting is idiomatic in > clojure, but it sure is handy for "empty list if nil" scenarios. > ;; Of course, (empty-list-or-nil ...) reads nice enough...
Here is a version without the apply/hash-map/mapcat that uses a plain map instead. The function no longer returns a map, so the (keys map) call has to be replaced as well: