1

I am setting up an unordered_map, in which the key is an enum of Direction defined as:

enum Direction : uint16_t {
    North = 1 << 0,
    East = 1 << 2,
    South = 1 << 3,
    West = 1 << 4,
    None = 1 << 5,
};

The goal is to check if at least 2 of these flags are set, but up to n flags set.

With my enum defined above, I attempt to create a number of unordered_maps

    std::unordered_map<Direction, std::vector<Tile*>>*      groundTiles;
    std::unordered_map<Direction, std::vector<Tile*>>*      northEastHillTiles;
    std::unordered_map<Direction, std::vector<Tile*>>*      southEastHillTiles;
    std::unordered_map<Direction, std::vector<Tile*>>*      platformTiles;
    std::unordered_map<Direction, std::vector<Tile*>>*      wallTiles;

I initialize them as

    groundTiles = new std::unordered_map<Direction, std::vector<Tile*>>();
    groundTiles->insert_or_assign(Direction::None, std::vector<Tile*>());
    groundTiles->insert_or_assign(Direction::East, std::vector<Tile*>());
    groundTiles->insert_or_assign(Direction::West, std::vector<Tile*>());
    groundTiles->insert_or_assign((Direction)(Direction::East|Direction::West),std::vector<Tile*>());

and so on for the other sets

However, when I try to pull a Tile from the ground ( or any other set ) I cannot check if at least Direction::East and Direction::West are set. I have tried:

Tile* westAndEastCapped = southEastHillTiles->at((Direction)(Direction::West | Direction::East)).at(0);

But it seems to just default to the Direction::East set.

How can I select a tile from an unordered_map, with BOTH East and West flags set, and no others?

Ian
  • 65
  • 1
  • 8
  • Can you boil this down to a [mre] please (note the word minimal). In particular, do we need all those different `unordered_maps`? - they're confusing me. – Paul Sanders Aug 06 '23 at 22:01
  • I'm not sure I quite grasp the nature of the difficulty. With this last call, the one assigning to `westAndEastCapped` - what outcome do you expect, and what do you observe instead? How was `southEastHillTiles` map set up? Preferably, show a [mcve]. – Igor Tandetnik Aug 06 '23 at 22:02
  • I am expecting the 1st tile stored in the vector associated with a key value that is an enum as a tile that ONLY East and West set, NOTHING ELSE – Ian Aug 06 '23 at 22:07
  • So, I tried to test your code, and it seems it behaves as expected: [Godbolt](https://godbolt.org/z/fEYe3443f) I resize vectors such that `size(map[EAST]) == 1`, `size(map[WEST]) == 2`, and `size(map[EAST OR WEST] == 3`, and then check it. The check succeeds. Can you clarify your question using my example, which is a bit simpler to understand? – miloserdow Aug 06 '23 at 22:19

2 Answers2

3

This is not how std::unordered_map::at() works. It only retrieves a value from the unordered map for a key that's equal to the passed in parameter. Equal as in "exactly". That's it. Nothing else.

Additionally, at() throws an exception if the specified key does not exist.

All that std::unordered_map knows about it's key is that it's some kind of an opaque value. std::unordered_map doesn't care about any interpretation or meaning of the key, except that the only thing that std::unordered_map requires from its key is that it has an equality comparison and that it has a hash function.

An enum meets those requirements. The End. Just because this particular enum is interpreted as a bitmask, of some sorts, means nothing to a std::unordered_map.

A std::unordered_map is not well-suited for the way you're trying to access its values, it's not going to work. You will likely need to design a custom container for your values that will implement the desired access pattern efficiently.

In the worst case you can always resort to range iteration:

for (auto &[key, value]: southEastHillTiles)
{
  // ...
}

and then inspect each present key in the map to determine if it qualifies for what you're searching for. This is horribly inefficient, but that's pretty much the only thing that can be done with an unordered_map.

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
  • Appreciate the insight, will definitely think about making a new container for this! – Ian Aug 06 '23 at 22:36
1

The unordered map is designed to find very efficiently elements corresponding to one specific key value. In your problem unfortunately, you don't know the key value you're looking for. You only know some constraint that the key must fulfil.

One way is to iterate through all existing keys and check if they match the constraints. But it is not very efficient.

If these consuming searches are frequent, and if the keys in your map are relatively stable, you could think of precomputing the research: a search map would associate every elementary Direction with a set of keys that contain this direction:

  • No need for a big iteration to find quick the few keys that are relevant. With these keys you'd then go to your full map, to obtain the tiles .
  • If you're looking for combinations, you could find the sets of keys containing each direction you're looking for, and perform an intersection of the two sets.

Considering that you have only a few elementary directions, you could even precompute the sets for every possible combination (and since the direction East|West is not very relevant, the combinatorics should stay under control).

The problem is that every new key inserted in your tile map would need to recompute the search maps. But you could tackle the problem at the source and define a special container that offers all these features: for each new key and tile value inserted in that container, the container would update the relevant sets used for the queries.

Caution: if your map is very dynamic with lots of keys added and deleted, or if the number of keys could grow very large, the efficiency gains of the proposed approach could be lost. In this case, you'd need to look for a different indexing scheme, such as bitmap indexing (or equivalent, e.g. some kind of trie - not a typo).

Christophe
  • 68,716
  • 7
  • 72
  • 138
  • A quick follow-up... I pre-allocate all these tiles, the unordered_map approach was to have a copy in memory of which tiles align with which cardinality, because my parser allows for any combination of 'orientations' for a sprite – Ian Aug 07 '23 at 23:09