proposed additions to contrib: map-utils

已查看 17 次
跳至第一个未读帖子

Sean Devlin

未读,
2009年8月22日 14:24:432009/8/22
收件人 Clojure Dev
Well, as promised here is round two of my proposed additions to
contrib. Today's thread is about the map-utils namespace.

Higher Order Functions
All the higher order functions in clojure accept and return a seq. It
is common to transform the resulting seq into a hash map.
These are a few functions that do this for you automatically.

Let's create a map for example purposes.

user=> (def abc123 {"a" 1 "b" 2 "c" 3})
#'user/abc123

* map-vals
This is like the `map` operator, but it applies `f` to every value of
the hash map instead of the entry. It returns a hash map.

user=> (map-vals #(* 2 %) abc123)
{"c" 6, "b" 4, "a" 2}

* map-keys
This is like the `map` operator, but it applies `f` to every key of
the hash map instead of the entry. It returns a hash map.

user=> (map-keys keyword abc123)
{:c 3, :b 2, :a 1}

* filter-map
This behaves just like `filter`. `pred` is applied to each entry of
the hash-map, and the resulting collection is transformed into a hash
map.

user=> (filter-map (comp even? second) abc123)
{"b" 2}

* remove-map
This behaves just like `remove`. `pred` is applied to each entry of
the hash-map, and the resulting collection is transformed into a hash
map.

user=> (remove-map (comp even? second) abc123)
{"a" 1, "c" 3}

#Tranforming a map

This section explores the `trans` closure, which is used to modify a
map.

* trans

I defined a function trans

(defn trans [& params]...)

Let me show an example:

user=> ((trans :count count) abc123)
{:count 3, "a" 1, "b" 2, "c" 3}

Notice the call to trans first, and then the result it applied to
test-
map. This is because trans generates a closure. In this case, it
applies the count function to the map, and associates it with the
key :count.

user=> ((trans "a" (comp inc #(get % "a"))) abc123)
{:a 1, :b "B", :c "C"}

* deftrans

trans is a little cumbersome, generating a closure. I also wrote a
deftrans macro. It creates a trans and stores it in the provided
name:

user=> (deftrans counter :count count)
#'user/counter

user=> (counter abc123)
{:count 3, "a" 1, "b" 2, "c" 3}

user=> (deftrans inc-a :a (comp inc :a))
#'user/inc-a

user=> (inc-a abc123)
{:a 1, :b "B", :c "C"}

* Using a closure

Let's revisit the fact that trans generates a closure. We can use
the
resulting transform anywhere we'd use a function.

### In a map

user=> (map counter (repeat 5 abc123))
({:count 3, "a" 1, "b" 2, "c" 3}
{:count 3, "a" 1, "b" 2, "c" 3}
{:count 3, "a" 1, "b" 2, "c" 3}
{:count 3, "a" 1, "b" 2, "c" 3}
{:count 3, "a" 1, "b" 2, "c" 3})


### In a comp
user=> ((comp counter counter) abc123)
{:count 4, "a" 1, "b" 2, "c" 3}

### In the STM

This is my favorite use of trans so far

user=> (def test-ref (ref abc123))
#'user/test-ref

user=> (dosync (alter test-ref counter))
{:count 3, "a" 1, "b" 2, "c" 3}

user=> @test-ref
{:count 3, "a" 1, "b" 2, "c" 3}

* trans*

The trans function associates its values after all the functions have
been evaluated

user=> ((trans :c1 count :c2 count :c3 count) abc123)
{:c3 3, :c2 3, :c1 3, "a" 1, "b" 2, "c" 3}

I have also defined the trans* closure, which associates the value in
the map between each iteration. I believe
this mimic the distinction between let & let* in CL, but I am unsure.

user=> ((trans* :c1 count :c2 count :c3 count) abc123)
{:c3 5, :c2 4, :c1 3, "a" 1, "b" 2, "c" 3}

That completes today's suggestions. Any thoughts?
Sean

Richard Newman

未读,
2009年8月22日 14:39:472009/8/22
收件人 cloju...@googlegroups.com
> * map-keys
> This is like the `map` operator, but it applies `f` to every key of
> the hash map instead of the entry. It returns a hash map.
>
> user=> (map-keys keyword abc123)
> {:c 3, :b 2, :a 1}

You'll have to define what happens when keywords collide. For example,
what is the result of (map-keys (constantly :foo) abc123)?

This suggests an additional argument, as for merge-with, or a
companion function (map-keys-with).

> * filter-map
> This behaves just like `filter`. `pred` is applied to each entry of
> the hash-map, and the resulting collection is transformed into a hash
> map.
>
> user=> (filter-map (comp even? second) abc123)
> {"b" 2}
>
> * remove-map
> This behaves just like `remove`. `pred` is applied to each entry of
> the hash-map, and the resulting collection is transformed into a hash
> map.
>
> user=> (remove-map (comp even? second) abc123)
> {"a" 1, "c" 3}

Isn't remove-map simply equivalent to filter-map with (complement pred)?

Sidenote:

I'd be interested to see if other people would find some combination
of map-vals/map-keys and filtering on the transformed value useful:

;; Bad written-in-email implementation!
(defn map-filter-vals [f m]
(into {}
(mapcat (fn [[k v]]
(when-let [r (f v)]
(list [k r])))
m)))

;; Example function.
(defn inc-if-even [x]
(let [y (inc x)]
(when (even? y) y)))

user=> (map-filter-vals inc-if-even {:x 1 :y 2 :z 3})
{:x 2, :z 4}


This isn't drawn from any code I've written -- it merely came to mind
reading your email.


> That completes today's suggestions. Any thoughts?

trans seems useful!

Sean Devlin

未读,
2009年8月22日 16:35:192009/8/22
收件人 Clojure Dev
1. Good point about map-keys. I've tweaked the fn. to accommodate a
merging fn
2. I included remove because it's in core. Just trying to be
symmetric.
3. Your (filter/remove)-map-(vals/keys) fns are easy to define

(defn filter-map-keys
[f a-map]
(filter-map (comp f first) a-map))

(defn filter-map-vals
[f a-map]
(filter-map (comp f second) a-map))

(defn remove-map-keys
[f a-map]
(remove-map (comp f first) a-map))

(defn remove-map-vals
[f a-map]
(remove-map (comp f second) a-map))

How else could this be improved?
Sean
回复全部
回复作者
转发
0 个新帖子