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

5

8

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.

Obligato answered 31/1, 2013 at 18:4 Comment(0)
T
1

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))
Tuyere answered 20/11, 2022 at 19:8 Comment(0)
B
8

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.

Brooklyn answered 31/1, 2013 at 18:58 Comment(3)
stamourv from the irc channel also recommended for/hash, and that's what I've ended up using. Thanks a bunch.Obligato
@TheoBelaire What kind of reasoning was used to recommend it? As it is right now, I don't see any advantages of either variation.Writhen
That was a long time ago, and I don't remember. I think it was that it doesn't have to construct a list to then construct a hash table, but that could be wrong.Obligato
M
2

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))
Moa answered 31/1, 2013 at 18:54 Comment(0)
I
2

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)))
Infertile answered 31/1, 2013 at 18:58 Comment(0)
D
2

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))
Dug answered 31/1, 2013 at 21:11 Comment(0)
T
1

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))
Tuyere answered 20/11, 2022 at 19:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.