So I have this problem where a specific ValuesA
enum value is to be mapped to a respective ValuesB
enum value, The trick is there can be multiple ValuesA
mapping to a single ValuesB
enum.
So for the following example:
- I have randomly chosen a max range of 3
ValuesA
enum values mapping to a singleValuesB
enum value. - Each set of values are 'categorized' under a
BASE_i
value - Each range of values map to a single
ValuesB
valueValuesA
[0x01, 0x03] map toValuesB::ONE
[0x01]ValuesA
[0x04, 0x06] map toValuesB::TWO
[0x02]
- Each
BASE_x
values may not have ALL the values that could be mapped to a saidValueB
. For instance in this example:BASE_0
only containsBASE_0_TEN
that maps toValueB::FOUR
whereBASE_1
doesn't contain any such value.BASE_0
doesn't contain any values that map toValuesB::THREE
whereBASE_1
contains values that map toValuesB::THREE
enum class ValuesA : uint32_t
{
BASE_0 = 0x00'00'00'00,
// < maps to ValuesB::ONE -> 0x01 >
BASE_0_ONE = 0x00'00'00'01,
BASE_0_TWO = 0x00'00'00'02,
BASE_0_THREE = 0x00'00'00'03,
// !!! WHAT if in the future there would be a NEW BASE_0 value here? !!!
// < maps to ValuesB::TWO -> 0x02 >
BASE_0_FOUR = 0x00'00'00'04,
BASE_0_FIVE = 0x00'00'00'05,
BASE_0_SIX = 0x00'00'00'06,
/*
< no values that map to ValuesB::THREE -> 0x03 >
BASE_0_SEVEN = 0x00'00'00'07
BASE_0_EIGHT = 0x00'00'00'08
BASE_0_NINE = 0x00'00'00'09
*/
// < only one value maps to ValuesB::FOUR -> 0x04 >
BASE_0_TEN = 0x00'00'00'0A,
/*
< no values >
MAP_0_ELEVEN = 0x00'00'00'0B
MAP_0_TWELVE = 0x00'00'00'0C
*/
BASE_1 = 0x00'01'00'00,
// < maps to ValuesB::ONE -> 0x01 >
BASE_1_ONE = 0x00'01'00'01,
BASE_1_TWO = 0x00'01'00'02,
BASE_1_THREE = 0x00'01'00'03,
/*
< no values that map to ValuesB::TWO -> 0x02 >
BASE_1_FOUR = 0x00'00'00'04,
BASE_1_FIVE = 0x00'00'00'05,
BASE_1_SIX = 0x00'00'00'06,
*/
// maps to ValuesB::THREE -> 0x03
BASE_1_SEVEN = 0x00'01'00'07,
BASE_1_EIGHT = 0x00'01'00'08,
BASE_1_NINE = 0x00'01'00'09,
};
enum class ValuesB : uint16_t
{
ONE = 1,
TWO = 2,
THREE = 3,
FOUR = 4
};
Approaches
- Perhaps one easy a way to go about is to create a map with
ValuesB
as a key andValuesA
as values. As we know the mapping at compile time, we can go about populating the map.
std::unordered_map<ValuesB, std::vector<ValuesA>> mapValuesBToValuesA;
- A rather "efficient" approach that doesn't require hashing could include:
- get the last 16 bits of
ValuesA
and get theValuesB
based on the hardcoded range checks
- get the last 16 bits of
ValuesB MapValueAToValueB(ValuesA valueA)
{
/*
[0x01, 0x03] -> [0x01]
[0x04, 0x06] -> [0x02],
*/
uint16_t value = static_cast<uint32_t>(valueA) & 0xFFFF;
if (value >= 0x01 && value <= 0x03)
{
return ValuesB::ONE;
}
if (value >= 0x04 && value <= 0x06)
{
return ValuesB::TWO;
}
// ...
}
ISSUE
I am inclined towards the Approach 2 but only if it's a feasible one. Following are the shortcomings I see and whether we can overcome it:
- Range to separate out the what
ValueB
would be mapped to:- For this example I randomly selected 3 as a MAX range to identify what
ValuesB
would be mapped to but in the production, you probably don't want to be in a situation where you feel the need to makeValuesA:: BASE_0_FOUR
that maps toValuesB::ONE
as well.
- For this example I randomly selected 3 as a MAX range to identify what
Question
- Can Approach 2 be made more scalable? If so, what would you do differently based on what's described above?
Hopefully I'm not overthinking it
3 Answers 3
By trying to encode mapping information into the representation of ValueA, you are just needlessly complicating stuff for yourself. Just write ValueA as you would if there was no mapping concern to ValueB and let the compiler optimize the mapping logic.
For example:
enum ValueA {
FOO,
BAR,
BAZ,
QUUX,
FOOBAR
};
enum ValueB {
ONE = 1,
TWO,
THREE,
};
ValueB mapValueAToValueB(ValueA src)
{
switch (src)
{
case FOO:
case BAR:
case FOOBAR:
return ONE;
case BAZ:
case QUUX:
return THREE;
}
}
-
Thanks. This works but ideally I was mainly looking for something that only involves modifying
ValueA
enum class rather than modifying the map function.xyf– xyf2023年07月24日 18:24:39 +00:00Commented Jul 24, 2023 at 18:24
The most critical question to consider is whether the mapping to B is inherently dependent on the value of the instance of A being mapped.
If there is a clear relationship between the two, you can opt for an algorithm or heuristic to implement the mapping. On the other hand, if the mapping is unrelated to the numeric value of A, you'll need a mechanism to check each value, such as a mapping table or using switch/ifs.
The problem with the example you provide, is that its an abstract example, which obscures the fundamental design question.
The key aspect to understand here is the nature of the problem domain and to what extent the mapping is influenced by the value. If the mapping is indeed a function of the value, it is appropriate to utilize it. However, if it's not a function, using a function would be an assumption waiting for a bug that would eventual expose it. Therefore, a thorough analysis of the relationship between A and B is critical for a reliable and robust implementation.
A Turing machine is a mathematical model of computation describing an abstract machine that manipulates symbols on a strip of tape according to a table of rules. Despite the model's simplicity, it is capable of implementing any computer algorithm.
Definition from the Wikipedia page.
To summarise the plenty of Turing machine implementations available on the internet it's a tremendous work. What it's feasible instead is to describe the Turing machine to reveal it's suitable to the discussed topic that is about source values routed to destination values. The source values are described by ValuesA
enum, the destination values are described by ValuesB
enum and the question is: "who describes the routes". There could be a map with keys of type ValuesA
and values of type ValuesB
, there could be a chain of responsibility with each strategy exposing couple of methods one to test whether the source value is supported and one to route the source value to the destination value (approach 2 without the issue? probably), to go wild the routing could be retrieved from an external storage - from a database, from a file system, from a web service.
-
Why do you start talking about Turing machines?Dominique– Dominique2023年07月24日 12:00:24 +00:00Commented Jul 24, 2023 at 12:00
map<ValueA, ValueB>
(ordered or not) is what corresponds to what you do in 2). (And is IMO much better than 2.)ValueB
) out of a value (ValueA
) hence the unordered_mapValueB
. You can have [0, 3] map to [1] but what in the future you want to have [0, 4] map to [1] instead. What happens to originalValueA
values that were categorized from[4, 6]
, and so on...