jokarl
jokarl

Reputation: 2245

Java map with tuple as key, remove/get by tuple key

I have scenario where I would like to have a sorted map with a tuple as key:

var scheduledRunnables = new TreeMap<Tuple<Integer, String>, Runnable>(Comparator.comparing(Tuple::getKey));

When adding, I want to add a tuple:

scheduledRunnables.put(new Tuple<>(order, taskName), task);

When sorting, I want it to be sorted by the order integer, since that determines order of execution of the runnables. I do not necessarily know the value of the variable taskName. When calling get, I want to only supply the order integer, because of the same reason as previously stated. When calling put, I want to take the entire pair into consideration. So something like this:

scheduledRunnables.put(new Tuple<>(1, "Some task"), () -> System.out.println("asdf"));
scheduledRunnables.get(1).run(); // Should output 'asdf'
scheduledRunnables.put(new Tuple<>(1, "Some task"), () -> System.out.println("qwerty"));
scheduledRunnables.get(1).run(); // Should output 'qwerty'
scheduledRunnables.remove(1).run(); // Should output 'qwerty' and remove from map

The Tuple class is just a data holder that looks like this:

@Data
public class Tuple<K, V> {

    private final K key;
    private V value;

    public Tuple(K key, V value) {
        this.key = key;
        this.value = value;
    }

}

How would this be implemented? Is is possible without making a custom implementation of the SortedMap interface? Is there a better way to achieve what I am after?

Upvotes: 2

Views: 15620

Answers (4)

Nestor Milyaev
Nestor Milyaev

Reputation: 6595

Another option is to use a String as a key, where String will be a concatenation of Integer + String (perhaps add a "_ " as a spacer in case String has some digits.) This way the order will be handled automatically, just implement similar mechaninsm to look up the values.

var scheduledRunnables = new TreeMap<String, Runnable>();

Then, if you need to get all values with "1" in the key, you can use scheduledRunnables.keys() and get those starting with "1".

Upvotes: 1

Michael
Michael

Reputation: 44200

Honestly, just maintain two maps.

Map<Integer, Runnable> idToRunnable;
Map<Integer, String> idToName;

Or use the tuple for the value rather than the key:

Map<Integer, Tuple<String, Runnable>>

Upvotes: 3

Jordan
Jordan

Reputation: 2283

When calling get, I want to only supply the order integer

That has some hidden assumptions that aren't being enforced by your code. If you're using a Tuple as your key, then getting an entry out of the map by just one part of that Tuple has the possibility of returning multiple entries. By way of example, consider this simplified code:

Map<Tuple<Integer, String>, String> map = new TreeMap<>();
map.put(new Tuple<>(1, "Foo"), "First");
map.put(new Tuple<>(1, "Bar"), "Second");

? = map.get(1); // What should this return?

That get(), if you only cared about part of the Tuple, would be expected to return multiple values.

If you don't care about part of your Tuple, then you shouldn't be using Tuples as keys to your map, since keys are expected to be distinct, but parts of keys are not. Without knowing more about your use case, I'm not sure what the best option is for you, though.

Upvotes: 1

Lino
Lino

Reputation: 19926

You can just make you value not relevant (its just a meta information). So its not needed for initializing the class (optional parameter) and not taken into account when using hashCode or equals:

public class Tuple<K extends Comparable<K>, V> implements Comparable<Tuple<K, V>> {
    private final K key;
    private V value;

    public Tuple(K key) {
        this.key = key;
    }

    public Tuple(K key, V value) {
        this.key = key;
        this.value = value;
    }

    public int hashCode() {
        return key.hashCode();
    }

    public boolean equals(Object obj) {
        return obj == this || (obj instanceof Tuple) && key.equals(((Tuple) obj).key);
    }

    public int compareTo(Tuple<K, V> other) {
        return key.compareTo(other.key);
    }
}

I've also made Tuple comparable, which will just compare the keys, so you can create your TreeMap without a custom Comparator like this:

var scheduledRunnables = new TreeMap<Tuple<Integer, String>, Runnable>();

You then can call your get with:

Runnable runnable = scheduledRunnables.get(new Tuple<>(1));

Upvotes: 3

Related Questions