Can you get the corresponding "value" of a given "key" using a JUCE HashMap?

Hi all, I’m working on my first project where I need to convert from from pair of ints (midinote,channel) to an arbitrary set of integer (period,generator) coordinates. (The mapping is read in from a file).

My (admittedly limited) experience with hashmaps in Java led me to believe that the JUCE HashMaps would be perfect for this case, since they store keys and corresponding values. So I made a bag class for the pairs of ints, created a HashMap of type <bag,bag> (both input and output are just a pair of ints), and then realized I don’t see a function for actually returning the corresponding value for a key that’s already been added to the map.

https://docs.juce.com/master/classHashMap.html

Functions provided are things like finding out whether the hashmap Contains() a key, or containsValue(), and doing things like set() another mapping, remove() items with a given key, or removeValue() to remove all keys with a given value (so obviously the object itself still knows the association)… It does provide an iterator for the values, but even then, it seems like I still wouldn’t be able to associate them to the keys.

Am I able to use this class for that? (Storing a mapping, and then being able to actually reproduce the stored value, given a key?) Have I… missed something embarrassingly obvious? xD Is there a different data structure I should be looking at?

Thanks very much for any help!

I think you are looking for ValueType& getReference (key) or ValueType operator[] (key)

Careful, the operator returns by copy. If you assign to an element received from operator[], it will only affect the copy.

Advantage of using by copy: you don’t have to check if the key exists, because you get a default element returned. Getting a reference that doesn’t exist will assert though, IIRC.

auto value = map [key];
// vs.
if (map.contains (key))
    auto& value = map.getReference (key);

TBH, I would go for std::map<ValueType, OtherType> anyway :wink:

1 Like

or std::unordered_map

1 Like

Thanks so much, my fault for judging a function by its name =P (I actually may have even read the description and just totally missed that it did what I’m asking for… Either way, many thanks.)

I’m now trying to figure out how to implement the hashing for my bag, and how to initialize the HashMaps themselves. Are there any good example projects that show how to do this? I might be having trouble here more because I’m a C++ noob and less because I’m a JUCE noob, but any help here would certainly be appreciated.

First, one of the template parameters is “TypeOfCriticalSectionToUse”. I’ve done a little research on mutexes and what “critical sections” are, and I’ve tried looking at the critical section JUCE class, but that hasn’t helped me figure out exactly what I’m supposed to be passing into the template.

Any examples out there where I can see someone actually using the JUCE HashMap class so I can see how it’s done?

Second, looks like I need to create a MyHashGenerator class, as per the documentation:

You can also specify a class to supply a hash function that converts a key value into an hashed integer. This class must have the form:

struct MyHashGenerator
{
     int generateHash (MyKeyType [key](https://docs.juce.com/develop/namespaceBlocksProtocol.html#a01895711c2ff4b9a8ed600aab5c6dd79af802cb6422dd92bb3cfae224c869b6da), int upperLimit) const
     {
          // The function must return a value 0 <= x < upperLimit
          return someFunctionOfMyKeyType ([key](https://docs.juce.com/develop/namespaceBlocksProtocol.html#a01895711c2ff4b9a8ed600aab5c6dd79af802cb6422dd92bb3cfae224c869b6da)) % upperLimit;
     }
};

And then I need to tell the HashMap to use that function, which seems to happen both in the template-syntax, and in the constructor?

HashMap< KeyType, ValueType, HashFunctionType, TypeOfCriticalSectionToUse >::HashMap	(	int 	numberOfSlots = defaultHashTableSize,
HashFunctionType 	hashFunction = HashFunctionType())	

I’ve specified the custom hash function here by just turning each int into a string, combining them, and then using the DefaultHashFunction for Strings:

    struct MyHashGenerator
    {
        int generateHash (TwoInts key, int upperLimit) const
        {
            String toHash;
            toHash+=key.getFirst();
            toHash+=key.getSecond();
            return juce::DefaultHashFunctions::generateHash (toHash,upperLimit);
        }
    };

But then… how exactly do I pass that into the template and constructor? My guess would be that it’s something like:

private:
HashMap<TwoInts,TwoInts,MyHashGenerator,/*theCriticalSectionStuffiDon'tUnderstandYet*/> translator;

With it initialized in the initialization list of the constructor for the class, similarly to this:

 RelayerMapper()
    :       translator(1152, MyHashGenerator::generateHash()),
    {}

But those are really just semi-educated guesses.

Thanks very much for any help.

The template parameter has a default value. you don’t have to provide anything:

template <typename KeyType,
          typename ValueType,
          class HashFunctionType = DefaultHashFunctions,
          class TypeOfCriticalSectionToUse = DummyCriticalSection>
class HashMap
{
private:

You can declare them like this:

HashMap<int, String> hashMap;

If you don’t supply a class for the HashFunctionType template parameter, the default one provides some simple mappings for strings and ints.

Do you need anything more specific than int or string? Otherwise the method @matkatmusic provided will work.

Lets say you want to hash after a MidiMessage’s noteNumber, it could look like:

struct FooHashGenerator
{
    int generateHash (MidiMessage key, int upperLimit) const
    {
        // The function must return a value 0 <= x < upperLimit
        return key.getNoteNuber() % upperLimit;
    }
};
FooHashGenerator fooHashGenerator;
HashMap<MidiMessage, ValueType> weirdMidiHash (256, fooHashGenerator);

The example probably makes no sense, but displays how to use it.

Thanks for the responses!

The template parameter has a default value. you don’t have to provide anything:

Ok I see, thanks! So I don’t have to worry about the criticalSection stuff. I think I do still have to do the custom hashGenerator function.

Do you need anything more specific than int or string?

What I’m doing with the HashMap is associating pairs of ints. (From midi note and channel to “period/generator coordinates”. It’s a tuning thing.) So the hash would be of the TwoInts bag object representing the midi note/channel combination, and the resulting key would be the TwoInts bag object representing the corresponding period/generator coordinates (which are initially read in from a file). The documentation shows a template of how a custom class can be made, so now my question becomes:
When I declare them, is it just the class name I pass into the template? So:

HashMap<TwoInts,TwoInts,MyHashGenerator> translator;

? And Is there a reason that there’s a place for the hashFunction in both the template and the constructor?

HashMap (int numberOfSlots=defaultHashTableSize, HashFunctionType hashFunction=HashFunctionType())

If I do one, do I need to do the other? Are they completely different things? (Seems like one should be a class, the other should be the actual function, maybe?) Right now My best guess is that the initialization would be something like:

:translator(1152, MyHashGenerator::generateHash())

I’m not sure whether you can pass a function as an argument in this way but it’s my best guess looking at the documentation. I realize I’m kind of just guessing at this point, so feel free to point me to a working example if you know of one and I can see how it’s supposed to be done.

(These best guesses of mine do, understandably, lead to an error. “no matching function for call to ‘RelayerMApper:MyHashGenerator::generateHash()’”. Not sure where that gets me either.)

Thanks again

Since you map int to int, using the defaultHashGenerator would be advisable.
TBH, I would nest it into:

std::unordered_map<int, std::unordered_map<int, int>> keyMapping;

// resolving into
keyMapping [channel][note] = 42;

I think it would work the same with juce’ HashMap, haven’t tried

Thanks for the suggestion. Would that perform better, or is it just a style preference? Since these are all ints, technically I could forget the “map” altogether and store it as a 3D vector of ints, right? (The first two would be for the midiNote and midiChannel, and then the last one would be of size 2, and would contain the period and generator.)

But since I found the JUCE HashMap and I’m learning C++ as well, I figure this is a good time to figure out how to use the template they’ve provided as they originally intended. (If that doesn’t work out, I’ll certainly try the unordered_map.)

If anyone has any examples of how to set up the JUCE HashMap for a custom class, I think understand the logic, but I just don’t know the syntax, and so would love to look over the code.

Thanks!