Theo Belaire
Theo Belaire

Reputation: 3020

How to map over values of a hash table (racket)

I want to map a function over the values in a hash table, like so:

(hash-map add1 (hash "apple" 1 "pear" 2))
=> #hash(("apple" . 2) ("pear" . 3))

Is there a library function to do this? It'd be good to have one that worked on immutable hashetables too.

I looked on PlaneT, but didn't see anything there.

Now, if this really doesn't exist, I'll go ahead and write it. What would the etiquette for getting this into racket? I just fork it on github and add it to the standard library (and the docs!) and submit a pull request? Or should I make it a planeT first, and then ask for it to be moved in? I'd like to help, but I just don't know what's the 'proper' way to go about it.

Upvotes: 8

Views: 11424

Answers (5)

Shawn
Shawn

Reputation: 52344

Many years after this question was asked, Racket 8.6 added hash-map/copy to do this. It passes both keys and values of an existing hash table to a map function that returns two values - a new key and value to use in a newly returned table.

> (hash-map/copy (hash "apple" 1 "pear" 2) (lambda (k v) (values k (+ v 1))))
'#hash(("apple" . 2) ("pear" . 3))

Upvotes: 2

to_the_crux
to_the_crux

Reputation: 267

Since hash-map creates a list that is not needed here, I would rather use hash-for-each.

(define (hash-update-all! h func)
  (hash-for-each
    h
    (lambda (k v) (hash-set! h k (func v)))))

(define table (make-hash '((a . 1) (b . 2) (c . 3))))

(hash-update-all! table (curry * 100))

table

 ==> '#hash((c . 300) (a . 100) (b . 200))

Edit: I forgot that for can handle a hash-table:

(define (hash-update-all! h func)
  (for ([(k v) h])   (hash-set! h k (func v))))

(hash-update-all! table (curryr / 10))

table

 ==> '#hash((c . 30) (a . 10) (b . 20))

Upvotes: 2

Óscar López
Óscar López

Reputation: 236004

In Racket you can use the hash-map higher-order procedure, which normally returns a list with the values resulting from the application of the procedure received as parameter, but it can be adapted for modifying the map in-place. For example:

(define h (make-hash))
(hash-set! h "apple" 1)
(hash-set! h "pear" 2)

h
=> '#hash(("pear" . 2) ("apple" . 1))

The trick is passing a lambda with the appropriate functionality:

(hash-map h (lambda (key value)
              (let ((newval (add1 value)))
                (hash-set! h key newval)
                newval)))

h
=> '#hash(("pear" . 3) ("apple" . 2))

For a more general solution, try this implementation:

(define (mutable-hash-map! hash proc)
  (hash-map hash (lambda (key value)
                   (hash-set! hash key (proc value))))
  (void))

Upvotes: 2

dyoo
dyoo

Reputation: 12013

You can iterate through the values of a hash table using in-hash-values, and use regular for loops to map across them. The function in-hash-values takes a hash and returns a sequence of the values, which can then be traversed with a for loop.

Example:

(for/list ([elt (in-hash-values (hash "apple" 1 "pear" 2))])
  (add1 elt))

Similarly, you can use sequence-map, though what you get back is another sequence rather than a list:

(sequence-map add1 (in-hash-values (hash "apple" 1 "pear" 2)))

Upvotes: 2

stchang
stchang

Reputation: 2540

There is a hash-map, but it returns a list [1]

You would have to write your own hash-map to do exactly what you want.

#lang racket

(define (new-hash-map f h)
  (make-immutable-hash (hash-map h (λ (k v) (cons k (f v))))))

(new-hash-map add1 (hash "apple" 1 "pear" 2))

; => '#hash(("pear" . 3) ("apple" . 2))

Another form you might be interested in is for/hash [2]:

#lang racket

(define (new-hash-map2 f h)
  (for/hash ([(k v) (in-hash h)]) (values k (f v))))

(new-hash-map2 add1 (hash "apple" 1 "pear" 2))

; => '#hash(("pear" . 3) ("apple" . 2))

If you feel that this functionality should be included with Racket, patches are most welcome! The best way to contribute is to fork on github and submit a pull request.

Upvotes: 8

Related Questions