Inherit from struct data - c++

Imagine the following situation:
I want to create various monster factories. These monster factories create monsters based on the data provided by a struct array. The monsters only differ in these stats, therefore creating a subclass for each monster is overkill.
struct monster_data
{
int HP;
int strength;
int speed;
// even more attributes
};
A class monster can handle all the behavior of a monster based on a monster_data:
class monster
{
public:
monster(monster_data* initial_stats, int length);
void attack();
void walk();
void die();
// and so forth
};
So far, so good. Now I have a class monster_factory that creates monsters based on a hard coded monster_data array:
const monster_data district1_monsters[]
{
{ 500, 20, 4 }, // monster1
{ 550, 5, 12 }, // monster2
{ 420, 8, 10 }, // monster3
{ 310, 30, 7 }, // monster4
// 100 more monsters
};
class monster_factory
{
public:
monster_factory(monster_data* monster_to_create) ;
monster* create_random_monster();
};
My problem is that I have to support several monster_factories for several districts with with minor differences in the lists:
const monster_data district1_monsters[]
{
{ 500, 20, 4 }, // monster1
{ 550, 5, 12 }, // monster2
{ 420, 8, 10 }, // monster3
{ 310, 30, 7 }, // monster4
// 100 more monsters
};
const monster_data district2_monsters[]
{
{ 500, 20, 4 }, // monster1
{ 750, 5, 12 }, // MONSTER2B <<
{ 420, 8, 10 }, // monster3
{ 310, 30, 7 }, // monster4
// monsters 5 - 80 from district 1
};
const monster_data district3_monsters[]
{
{ 500, 20, 4 }, // monster1
{ 550, 5, 12 }, // monster2
{ 720, 80, 10 }, // MONSTER3B <<
{ 310, 30, 7 }, // monster4
// monsters 8 - 90 from district 1
};
Instead of copying and pasting the array data, I would like to somehow inherit from it, because the data stays mostly the same between the various versions. Copying the whole struct array declaration just to have a slightly different variant seems like the wrong way. Too bad that district 2 and 3 just don't append data, they modify and omit existing entries. Of course they change more than one monster, too.
In addition changes on the monster data of district 1 should apply to district 2 and 3 as well.
Another problem is that there are districts that will have monster data completely unrelated to districts 1,2 and 3.
const monster_data district4_monsters[]
{
{ 100, 20, 10 }, // monster 401
{ 200, 50, 20 }, // monster 402
{ 300, 40, 5 }, // monster 403
{ 400, 30, 30 }, // monster 404
// 20 more monsters unrelated to district 1,2 & 3
};
Now to the question: How can the outlined design be changed, so that redundant monster_data declarations are avoided and that districts can be added that either derive their monster_data from an existing declaration or use a completely new one?
Bonus points, if your design ensures that there can only be one factory instance for every variant of the monster stats list.

This can be solved elegantly by the decorator pattern by decorating the "default" table with the changes in each layer:
class MonsterTable
{
public:
virtual monster_data const* getMonsterForIndex(int i)=0;
};
class DefaultMonsterTable : public MonsterTable
{
public:
monster_data const* getMonsterForIndex(int i)
{
return district1_monsters+i;
}
};
class OverlayMonsterTable : public MonsterTable
{
public:
//public for brevity, should be private in real code - can also be std::map
std::unordered_map<int, monster_data> OverlayData;
// Init this with the "previous layer", which is always the Default table in your examples
MonsterTable* Decorated;
monster_data const* getMonsterForIndex(int i)
{
typedef std::unordered_map<VGLindex, monster_data>::const_iterator Iterator;
Iterator Overlay=OverlayData.find(i);
if (Overlay!=OverlayData.end()) // Monster data was changed in this layer
return &Overlay->second;
return Decorated->getMonsterFromIndex(i); // Defer to next layer
}
};
You would then add all "changes" in higher districts to the OverlayData and let the OverlayMonsterTable refer to the default table (district1).
To support omitting of data, you can either add another decorator "layer" that remaps indices (for example, maps [0...80] to [0...10], [30...100]), or integrate this functionality into the existing OverlayMonsterTable. Either way, you have full flexibility. For example:
class OmitMonsterTable : public MonsterTable
{
public:
int OmitBegin, OmitEnd;
MonsterTable* Decorated;
monster_data const* getMonsterForIndex(int i)
{
if (i > OmitBegin)
i += OmitEnd;
return Decorated->getMonsterForIndex(i);
}
};
Your factory would just take a MonsterTable pointer/reference.

You keep using the word "inherit" but I would definitely not consider inheritance here, you only have one type of behaviour, i.e. one type of factory class, you just want to initialize the factories with different data.
I would have one large array with all the distinct monster_data values:
const monster_data all_data[] = {
// district1_monsters
{ 500, 20, 4 }, // monster1
{ 550, 5, 12 }, // monster2
{ 420, 8, 10 }, // monster3
{ 310, 30, 7 }, // monster4
// 100 more monsters
// ...
// district 2 monsters (index 104)
{ 750, 5, 12 }, // MONSTER2B <<
// district 3 monsters (index 105)
{ 720, 80, 10 }, // MONSTER3B <<
// district4 monsters (index 106)
{ 100, 20, 10 },
{ 200, 50, 20 },
{ 300, 40, 5 },
{ 400, 30, 30 },
// 20 more monsters unrelated to district 1,2 & 3
// ...
};
Then create sequences containing the right ones:
typedef std::vector<monster_data> data_seq;
data_seq district1_data(all_data, all_data + 104);
data_seq district2_data(all_data, all_data + 80);
district2_data[2] = all_data[104];
data_seq district3_data(all_data, all_data + 3);
district3_data.push_back( all_data[105] );
district3_data.insert(district3_data.end(), all_data+8, all_data+90);
data_seq district4_data(all_data+106, all_data + 126);
Then create factories from those sequences:
class monster_factory
{
public:
monster_factory(const data_seq& monsters) ;
monster* create_random_monster();
};
monster_factory district1_factory(district1_data);
monster_factory district2_factory(district2_data);
monster_factory district3_factory(district3_data);
monster_factory district4_factory(district4_data);
If the monster_data type is only three integers that should be fine. If it's a bigger class then you could make data_seq a vector<const monster_data*> so it only holds pointers to the elements of the all_data array. That avoids copying the monster_data objects, they just live in the master all_data array and everything else refers to those master copies through pointers. That would take a little more work to create the vector objects, as you'd need to fill it with addresses of the array elements, not simple copies of the elements, but that's something you'd only need to do once at program startup, so writing a little more code to do it right is worth it:
struct address_of {
const monster_data* operator()(const monster_data& m) const
{ return &m; }
};
// ...
typedef std::vector<const monster_data*> data_seq;
data_seq district1_data;
std::transform(all_data, all_data + 104,
std::back_inserter(district1_data), address_of());
data_seq district2_data;
std::transform(all_data, all_data + 80,
std::back_inserter(district2_data), address_of());
district2_data[2] = &all_data[104];
data_seq district3_data;
std::transform(all_data, all_data + 3,
std::back_inserter(district3_data), address_of());
district3_data.push_back( all_data[105] );
std::transform(all_data+8, all_data + 90,
std::back_inserter(district3_data), address_of());
data_seq district4_data;
std::transform(all_data+106, all_data + 126,
std::back_inserter(district4_data), address_of());
An alternative, probably more maintainable, way to initialize the sequence for each district would be to have arrays of indices for each district:
int district1_indices[] = { 0, 1, 2, 3, 4, ... 103 };
int district2_indices[] = { 0, 1, 104, 3, 4, ... 79 };
int district3_indices[] = { 0, 1, 2, 105, 7, 8, 9, 10 ... 89 };
int district4_indices[] = { 106, 107, 108, 109 ... 125 };
Then construct a factory with one of those arrays (and its length). The factory can just pick an index from the list and then use it to index into all_data to get a monster_data.

Storing data in a binary is often bad practice and does not scale, especially if it is going to be a huge amount of data. You shouldn't have much trouble to define your own mini-language that supports simple inheritance of data and then parse it into a class that contains an unordered_map. That would enable you to also implement simple data sharing and a more complex property system, should you ever require it.

I'd have one factory where I pass the district when I ask for a monster.
Then I can do something like (pseudo code only)
getMonster(int district)
{
monster_data dat = getRandomBaseMonster();
// dat has to be a copy so we don't stomp in the base data
if (district == 2) {
dat.HP += 10;
}
return dat;
}

One solution may be to have a base table that contains the "standard" monster data, and then for each district you have a table containing only a list of the modified monsters.
something like this:
const monster_data base_monsters[] = {
{ 500, 20, 4 }, // monster1
{ 550, 5, 12 }, // monster2
{ 420, 8, 10 }, // monster3
{ 310, 30, 7 }, // monster4
// 100 more monsters
};
struct monster_change_data
{
int monster; /* Index into base table */
struct monster_data data; /* Modified monster data */
};
const struct monster_change_data district2_monsters[] = {
{ 1, { 750, 5, 12 } }, // MONSTER2B
};
const struct monster_change_data district3_monsters[] = {
{ 2, { 720, 80, 10 } }, // MONSTER3B
};
This way you only have to list the changed monsters.

For completeness' sake I'll post the design I came up with before ltjax posted his answer, although mine is inferior. As it has a different approach it might be of interest to others nevertheless.
It combines the factory with its table as the table by itself has little meaning.
The filling of the table is done in the factory's constructor. This way other factories can inherit the constructor and make changes to the table.
The drawback is that every factory creates its own full table therefore storing redundant data during runtime. At least maintenance becomes easier.
This might be improved by moving the helper methods add, replace and remove to a separate table class to encapsulate them properly. But monster_factory_abstract would be basically empty in this case, IMO.
class monster_factory_abstract
{
private:
monster_data* table; // or map with sequential indices
int table_length;
protected:
// add monster to table
void add(int HP, int strength, int speed, etc.);
// index starts with one to match monster names in this example
void replace(int index, int HP, int strength, int speed, etc.);
void remove(int index); // nulls an entry
void remove(int from, int to);
public:
virtual monster* create_random_monster();
}
class monster_factory_district1 : public monster_factory_abstract
{
public:
monster_factory_district1()
{
table_length = 0;
add( 500, 20, 4 ); // monster1
add( 550, 5, 12 ); // monster2
add( 420, 8, 10 ); // monster3
add( 310, 30, 7 ); // monster4
// add 100 more monsters
}
};
class monster_factory_district2 : public monster_factory_district1
{
public:
monster_factory_district2() : monster_factory_district1
{
replace( 2, 750, 5, 12 ); // MONSTER2B <<
remove(81, 100);
}
};
class monster_factory_district3 : public monster_factory_district1
{
public:
monster_factory_district3() : monster_factory_district1
{
replace( 3, 720, 80, 10 ); // MONSTER3B <<
remove(5, 8);
remove(91, 100);
}
};
class monster_factory_district4 : public monster_factory_abstract
{
public:
monster_factory_district4() : monster_factory_abstract
{
table_length = 0;
add( 100, 20, 10 ); // monster 401
add( 200, 50, 20 ); // monster 402
add( 300, 40, 5 ); // monster 403
add( 400, 30, 30 ); // monster 404
}
};

Related

How to assign value to dart enum like in c++

I'm trying to adapt a c++ to dart, and I ran into this situation with enum, assigning default values ​​I think. follow the code
enum skills_t : uint8_t {
SKILL_FIST = 0,
SKILL_CLUB = 1,
SKILL_SWORD = 2,
SKILL_AXE = 3,
SKILL_DISTANCE = 4,
SKILL_SHIELD = 5,
SKILL_FISHING = 6,
SKILL_CRITICAL_HIT_CHANCE = 7,
SKILL_CRITICAL_HIT_DAMAGE = 8,
SKILL_LIFE_LEECH_CHANCE = 9,
SKILL_LIFE_LEECH_AMOUNT = 10,
SKILL_MANA_LEECH_CHANCE = 11,
SKILL_MANA_LEECH_AMOUNT = 12,
SKILL_MAGLEVEL = 13,
SKILL_LEVEL = 14,
SKILL_FIRST = SKILL_FIST,
SKILL_LAST = SKILL_MANA_LEECH_AMOUNT
};
}
uint32_t skillBase[SKILL_LAST + 1] = {50, 50, 50, 50, 30, 100, 20};
Is it possible to adapt this code to dart/flutter?
I would like to replicate the same operation in dart, it seems that he assigned these values ​​to each enum in a range
Yes, it is possible to adapt this code to Dart/Flutter.
In Dart, you can use the enum keyword to define an enumeration. The syntax is similar to C++, but there is no need to specify a type like uint8_t.
Regarding the default values, you can initialize the enum members with a value like in C++.
Here is an example of how the C++ code could be adapted to Dart:
enum Skills {
FIST,
CLUB,
SWORD,
AXE,
DISTANCE,
SHIELD,
FISHING,
CRITICAL_HIT_CHANCE,
CRITICAL_HIT_DAMAGE,
LIFE_LEECH_CHANCE,
LIFE_LEECH_AMOUNT,
MANA_LEECH_CHANCE,
MANA_LEECH_AMOUNT,
MAGLEVEL,
LEVEL,
FIRST = FIST,
LAST = MANA_LEECH_AMOUNT,
}
final List<int> skillBase = [
50, 50, 50, 50, 30, 100, 20
];
You can also use a Map to assign the default values to each enum member.
enum Skills {
FIST,
CLUB,
SWORD,
AXE,
DISTANCE,
SHIELD,
FISHING,
CRITICAL_HIT_CHANCE,
CRITICAL_HIT_DAMAGE,
LIFE_LEECH_CHANCE,
LIFE_LEECH_AMOUNT,
MANA_LEECH_CHANCE,
MANA_LEECH_AMOUNT,
MAGLEVEL,
LEVEL,
FIRST = FIST,
LAST = MANA_LEECH_AMOUNT,
}
final Map<Skills, int> skillBase = {
Skills.FIST: 50,
Skills.CLUB: 50,
Skills.SWORD: 50,
Skills.AXE: 50,
Skills.DISTANCE: 30,
Skills.SHIELD: 100,
Skills.FISHING: 20,
// Add the rest of the skills
};
Both the above examples will work fine in dart/flutter.

How to access the values of a map<string, vector<int>> in C++?

I am working on my version of a genetic algorithm to solve the knapsack problem in C++. I have a map of string to vector like
map<string, vector<int>> objects
{
{"Object 1", {7, 20, 15}},
{"Object 2", {3, 50, 10}},
{"Object 3", {5, 80, 12}},
{"Object 4", {4, 80, 8}},
{"Object 5", {2, 40, 11}}
};
and a vector of vectors
vector<vector<int>> population;
where I will store information such as
population[0] = {0, 0, 1, 1, 0};
population[1] = {1, 0, 0, 0, 1};
population[2] = {1, 0, 1, 0, 1};
...
Each vector is called an individual, and each element of a given individual indicates the presence (1) or the absence (0) of the corresponding object. So, for example, the third individual (population[2]) has Object 1, Object 3 and Object 5.
What I want to do is write a function which will receive an index from population and return the sum of the corresponding values from objects. In the case of population[2] I'd like to have another vector containing {14, 140, 38} (7+5+2, 20+80+40, 15+12+11).
But I'm struggling to access the values of the objects map.
map<string, vector<int>> objects {/*...*/}
vector<vector<int>> population;
void initializePopulation() {/*...*/}
void getScore(vector<int> individual, vector<int>& sum)
{
for(int i = 0; i < 3; i++)
{
sum.push_back(0);
for(int j = 0; j < 5; j++)
{
if(individual[j] == 1)
{
sum[i] += ???;
}
}
}
int main()
{
/*...*/
initializePopulation();
vector<int> sum;
getScore(population[2], sum);
}
So, as you can see, I'm not sure how to proceed with sum[i]. Any suggestions? I'm not very fluent in C++, so a more detailed answer would be appreciated!
For both vector of vectors as well as a map, you can use for each loop!
When your map is filled with value
for(auto x: objects){
cout<<x.first<<" "<<x.second<<endl;
}
This will print key-value pairs in a map with space in between!
In this problem you'll have to iterate values (i.e. the second) in map too!
{"Object 1", {7, 20, 15}}
{"Object 2", {3, 50, 10}}
{"Object 3", {5, 80, 12}}
{"Object 4", {4, 80, 8}}}
{"Object 5", {2, 40, 11}}
For something like this the following code should work:
for(auto x: objects){
cout<<x.first<<" ";
for(auto y: x.second){
cout<<y<<" ";
}
cout<<endl;
}
For vector of vectors, you can use the same concept!
Try renaming Object 1, Object 2, .... to just 1,2, ....
This will allow you to access values in map by using j from your for loop!
For a more simplified version consider this prototype instead, since you are using words like population and genetic, I assume your data to be humungous, so you are better off using const reference while passing data around (const&, they won't be copied and will become read-only). global variable is a bad idea in general.
void getScore(map<string, vector<int>> const& objects, vector<int> const& individual, vector<int>& sum)
{
// iterate over each object for the individual
for(int i = 0; i < 5; i++)
{
// are you sure you want sum as {14, 140, 38} (7+5+2, 20+80+40, 15+12+11)
// not {14,0, 140,0, 38} (7+5+2, 0, 20+80+40, 0, 15+12+11)
// use else part for later
if(individual[i] == 1)
{
// compute sum for each object
// retrieve object vector
auto it = objects.find("KEY"); // KEY generation discussed later
if(it!=objects.end()){ // validate key :::: important
vector<int> ob = objects["KEY"]; //it->second
sum.push_back(std::accumulate(ob.begin(),ob.end(),0) ); // https://www.cplusplus.com/reference/numeric/accumulate/
}
} /*else {
sum.push_back(0);
}*/
}
}
KEY generation:
1). generating "Object 1" :
string key = "Object " + to_string(i+1)
auto it = objects.find(key);
2). suggested :
use integers as key
or
go with an enum like
enum ObjList{
OBJECT_1,
OBJECT_2,
OBJECT_3
}
auto it = objects.find(i); //mind your indexes
hope it helps, happy coding XD
I think that with some little linear algebra your problem has an easy solution: indeed, if you store the numerical data related to your objects into a matrix A, then for each population vector p your desired result is simply p^T A (or equivalently, A^T p) where ^T denotes the transpose of a matrix or of a vector.
If you are not planning to employ any linear algebra library, you could implement the scalar product by yourself. Down below there is the code implementing the above idea.
#include <iostream>
#include <vector>
#include <map>
#include <string>
#include <numeric>
std::vector<int> SP(std::vector<std::vector<int>> const &M, std::vector<int> const &P){
std::vector<int> sums(3,0);
// perform p^T * A operation
for (int j=0;j<3;j++)
for (int i=0;i<5;i++)
sums[j] += M[i][j] * P[i];
return sums;
}
int main(){
std::map<std::string, std::vector<int>> objects {
{"Object 1", {7, 20, 15}},
{"Object 2", {3, 50, 10}},
{"Object 3", {5, 80, 12}},
{"Object 4", {4, 80, 8}},
{"Object 5", {2, 40, 11}}
};
std::vector<std::vector<int>> population(3);
population[0] = {0, 0, 1, 1, 0};
population[1] = {1, 0, 0, 0, 1};
population[2] = {1, 0, 1, 0, 1};
std::vector<std::vector<int>> A;
// Extract the numerical data from the map
for (auto const& [key, val] : objects)
A.push_back(val);
// vector in which the desired values are stored for the 3rd element of the population
std::vector<int> s = SP(A,population[2]);
// Just for checking
for (int it=0; it<s.size(); it++)
std::cout << s[it] << std::endl;
return 0;
}

Enum or array with structs inside

I have (constant) data like this:
(index) Width Height Scale Name
0 640 360 1 "SD"
1 1080 720 2 "HD"
2 1920 1080 3 "FHD"
So far - I have created structure like this:
struct Resolution
{
int Width;
int Height;
int Scale;
std::string Name;
};
Now I need an object that will let me to do something like this:
int index = 0;
int width = Resolutions[index].Width; // 360
I need enum or some array that will be constant, and accessible without initialization (static?).
For a start as it is constant data I would not use std::string.
But I would do the following:
struct Resolution
{
int Width;
int Height;
int Scale;
const char * Name;
};
struct Resolution Resolutions[] = {
{640, 360, 1, "SD"},
{ 1080, 720, 2, "HD"},
{ 1920, 1080, 3, "FHD"}
};
but on another note I would use lowercase variation for the variable.
You need either std::vector if the elements in Resolutions are not compile-time-constant, or std::array if they are and the collection doesn't need to grow. For example:
#include <array>
…
const std::array<Resolution, 3> Resolutions =
{{ /* Width Height Scale Name */
{ 640, 360, 1, "SD" },
{ 1080, 720, 2, "HD" },
{ 1920, 1080, 3, "FHD" }
}};
If you want the indices to have meaningful names instead of 0, 1, 2, you can make an enum:
enum ResolutionIndex { SD, HD, FHD };
And use it as the array index:
ResolutionIndex index = SD;
int width = Resolutions[index].Width;
This makes the code safer as you can't now do:
ResolutionIndex index = 4;
which would be an invalid index. The valid index values are hard-coded in the enum and the compiler enforces that. If you used int:
int index = 4;
then the compiler can't help you if you give an invalid index.
You can create a class (it's better in C++), and in your main class, a vector of this class, like that:
class Resolution {
public:
Resolution(unsigned int, unsigned int, unsigned int, std::string const &);
~Resolution();
private:
unsigned int Width;
unsigned int Height;
unsigned int Scale;
std::string Name;
};
And in your main class:
class MainClass {
public:
...
private:
...
std::vector<Resolution *> m_res;
};
And in the cpp file:
MainClass::MainClass() {
this->m_res.push_back(new Resolution(640, 360, 1, SD));
this->m_res.push_back(new Resolution(1080, 720, 2, HD));
this->m_res.push_back(new Resolution(1920, 1080, 3, FHD));
}
You can access to an element like that (Sure, you need the getter):
this->m_res[index].getValue();

Struct C++ array in function parameters not working at all

hello i have to do a program using an array of structures.. and i have to initialize it in a function. below i am trying, but my prototype keeps getting error "Expected primary expression".. i have followed tutorials but cant figure out what im doing wrong please help. i cant use pointers or vectors.. just basic stuff thank you for your time
struct gameCases{
bool flag = false;
int casenum;
double value;
};
int initialize(gameCases cases); //prototype
--- main()
gameCases cases[26];
initialize(cases); //call
int initialize(gameCases cases) //definition
{
double values[26] = {.01, 1, 5, 10, 25, 50,
75, 100, 200, 300, 400, 500, 750, 1000,
5000, 10000 , 25000, 50000, 75000, 100000,
200000 , 300000, 400000, 500000,
1000000, 2000000};
for (int i = 0; i < 26; i++)
{
array[i].value = values[i];
}
}
Declare the function like
int initialize( gameCases *array, size_t n );
and call it like
initialize( cases, 26 );
Or you could pass the array by reference. For example
int initialize( gameCases ( &cases )[26] );
Take into account that the function is declared as having return type int but it acrually returns nothing.
int initialize(gameCases cases[26]); //prototype
int initialize(gameCases cases[26]) //definition
{
double values[26] = {.01, 1, 5, 10, 25, 50,
75, 100, 200, 300, 400, 500, 750, 1000,
5000, 10000 , 25000, 50000, 75000, 100000,
200000 , 300000, 400000, 500000,
1000000, 2000000};
for (int i = 0; i < 26; i++)
{
cases[i].value = values[i];
}
}
and to call:
initialize(cases);

Rectangle approximation algorithm

I have an enumeration of just under 32 absolute rectangle sizes and I need to given dimensions and find the best approximation among my enumeration.
Is there any better (ie more readable and maintainable) way than the spaghetti code I am formulating out of lots of nested if's and else's?
At the moment I have just:
enum imgOptsScale {
//Some relative scales
w005h005 = 0x8,
w010h010 = 0x9,
w020h020 = 0xA,
w040h040 = 0xB,
w070h070 = 0xC,
w100h100 = 0xD,
w150h150 = 0xE,
w200h200 = 0xF,
w320h320 = 0x10,
w450h450 = 0x11,
w200h010 = 0x12,
w200h020 = 0x13,
w200h070 = 0x14,
w010h200 = 0x15,
w020h200 = 0x16,
w070h200 = 0x17
};
imgOptsScale getClosestSizeTo(int width, int height);
and I thought I'd ask for help before I got too much further into coding up. I should emphasise a bias away from too elaborate libraries though I am more interested in algorithms than containers this is supposed to run on a resource constrained system.
I think I'd approach this with a few arrays of structs, one for horizontal measures and one for vertical measures.
Read through the arrays to find the next larger size, and return the corresponding key. Build the final box measure from the two keys. (Since 32 only allows 5 bits, this is probably not very ideal -- you'd probably want 2.5 bits for the horizontal and 2.5 bits for the vertical, but my simple approach here requires 6 bits -- 3 for horizontal and 3 for vertical. You can remove half the elements from one of the lists (and maybe adjust the << 3 as well) if you're fine with one of the dimensions having fewer degrees of freedom. If you want both dimensions to be better represented, this will probably require enough re-working that this approach might not be suitable.)
Untested pseudo-code:
struct dimen {
int x;
int key;
}
struct dimen horizontal[] = { { .x = 10, .key = 0 },
{ .x = 20, .key = 1 },
{ .x = 50, .key = 2 },
{ .x = 90, .key = 3 },
{ .x = 120, .key = 4 },
{ .x = 200, .key = 5 },
{ .x = 300, .key = 6 },
{ .x = 10000, .key = 7 }};
struct dimen vertical[] = { { .x = 10, .key = 0 },
{ .x = 20, .key = 1 },
{ .x = 50, .key = 2 },
{ .x = 90, .key = 3 },
{ .x = 120, .key = 4 },
{ .x = 200, .key = 5 },
{ .x = 300, .key = 6 },
{ .x = 10000, .key = 7 }};
/* returns 0-63 as written */
int getClosestSizeTo(int width, int height) {
int horizontal_key = find_just_larger(horizontal, width);
int vertical_key = find_just_larger(vertical, height);
return (horizontal_kee << 3) & vertical_key;
}
int find_just_larger(struct dimen* d, size) {
int ret = d.key;
while(d.x < size) {
d++;
ret = d.key;
}
return ret;
}
Yes ... place your 32 different sizes in a pre-built binary search tree, and then recursively search through the tree for the "best" size. Basically you would stop your search if the left child pre-built rectangle of the current node's rectangle is smaller than your input rectangle, and the current node's rectangle is larger than the input rectangle. You would then return the pre-defined rectangle that is "closest" to your input rectangle between the two.
One nice addition to the clean code the recursive search creates is that it would also be logarithmic rather than linear in search time.
BTW, you will want to randomize the order that you insert the initial pre-defined rectangle values into the binary search tree, otherwise you will end up with a degenerate tree that looks like a linked list, and you won't get logarithmic search time since the height of the tree will be the number of nodes, rather than logarithmic to the number of nodes.
So for instance, if you've sorted the tree by the area of your rectangles (provided there are no two rectangles with the same area), then you could do something like the following:
//for brevity, find the rectangle that is the
//greatest rectangle smaller than the input
const rec_bstree* find_best_fit(const rec_bstree* node, const rec& input_rec)
{
if (node == NULL)
return NULL;
rec_bstree* return_node;
if (input_rec.area < node->area)
return_node = find_best_fit(node->left_child, input_rec);
else if (input_rec.area > node->area)
return_node = find_best_fit(node->right_child, input_rec);
if (return_node == NULL)
return node;
}
BTW, if a tree is too complex, you could also simply do an array or std::vector of instances of your rectangles, sort them using some type of criteria using std::sort, and then do binary searched on the array.
Here is my proposed solution,
enum imgOptsScale {
notScaled = 0x0,
//7 relative scales upto = 0x7
w010h010, w010h025, w010h060, w010h120, w010h200, w010h310, w010h450,
w025h010, w025h025, w025h060, w025h120, w025h200, w025h310, w025h450,
w060h010, w060h025, w060h060, w060h120, w060h200, w060h310, w060h450,
w120h010, w120h025, w120h060, w120h120, w120h200, w120h310, w120h450,
w200h010, w200h025, w200h060, w200h120, w200h200, w200h310, w200h450,
w310h010, w310h025, w310h060, w310h120, w310h200, w310h310, w310h450,
w450h010, w450h025, w450h060, w450h120, w450h200, w450h310, w450h450,
w730h010, w730h025, w730h060, w730h120, w730h200, w730h310, w730h450
};
//Only call if width and height are actually specified. else 0=>10px
imgOptsScale getClosestSizeTo(int width, int height) {
static const int possSizes[] = {10, 25, 60, 120, 200, 310, 450, 730};
static const int sizesHalfways[] = {17, 42, 90, 160, 255, 380, 590};
int widthI = 6;
while (sizesHalfways[widthI - 1] > width && --widthI>0);
int heightI = 6;
while (sizesHalfways[heightI - 1] > height && --heightI>0);
return (imgOptsScale)(8 + 7 * widthI + heightI);
}