Related
I am creating a Dungeons and Dragons Character Creator. There is a randomize feature that is going to create a complete character sheet. There is a part that I have gotten to and I am not quite sure the best way to proceed.
The way I have the racial modifiers set up is with if statements. Here is an example.
if (raceInt == 0 || raceInt == 2 || raceInt == 10)
{
raceStrMod = 2;
}
if (raceInt == 3 || raceInt == 4 || raceInt == 5 || raceInt == 11 || raceInt == 12)
{
raceDexMod = 2;
}
However there are races that have modifiers that let you select two stats to add a modifier to, such as Strength or Dexterity. What would be the best way to select two random ints for just those races?
For example, the half-elf race which would get +2 to Dex and then +1 to two other random stats. So I need to find a way to randomly select two of the remaining ints to make the value = 1.
My race mod ints are initialized as
int raceStrMod = 0;
int raceDexMod = 0;
int raceConMod = 0;
int raceIntMod = 0;
int raceWisMod = 0;
int raceChaMod = 0;
Then the if statements assign a value dependent on which race was randomly selected.
Thank you all for the input! This is how I ended up coding it
if (raceInt == 9)
{
int randomX = rnd.Next(1, 5);
int randomY = rnd.Next(1, 5);
int attempts = 0;
while (randomX == randomY && attempts < 10)
{
randomY = rnd.Next(1, 5);
attempts++;
}
//if they are still not unique after 10 attempts
if (randomX == randomY)
{
if (randomX == 5)
randomY = 1;
else
randomY = randomX + 1;
}
int[] randomNumbers = { randomX, randomY };
foreach (int i in randomNumbers)
{
switch (i)
{
case 1:
raceStrMod = 1;
break;
case 2:
raceDexMod = 1;
break;
case 3:
raceConMod = 1;
break;
case 4:
raceIntMod = 1;
break;
case 5:
raceWisMod = 1;
break;
}
}
}
Has your class introduced you to enum types yet? If not, is there any restriction on your final project with respect to using language features that weren't taught in the class?
Your question is arguably too broad, as there are many different ways to address this sort of thing even in real-world code, and the classroom context introduces potential roadblocks that while might constrain the question, being unknown they make it impossible to know what answer is actually going to work for you.
That said…
Ignoring the classroom aspect and focusing only on the problem itself, I would use enum types and dictionaries for this sort of thing. For example:
enum Attribute
{
Strength,
Dexterity,
Constitution,
Charisma,
Intelligence,
Wisdom,
Count, // must always be last
}
Dictionary<Attribute, int> modifiers = new Dictionary<Attribute, int>();
Then you can pick a random attribute like (assuming you have a random variable referencing a Random object…don't make the classic newbie mistake of creating a new Random object every time you want to pick a new random number):
Attribute attributeToModify = (Attribute)random.Next((int)Attribute.Count);
And you can store that selection like:
modifiers[attributeToModify] = 1;
This can be used to store however many modifiers you like. You can encapsulate that in an object representing the character itself, or you could put it into a separate AttributeModifiers class. One advantage of doing the latter would be that if you have modifiers that come from different sources, you can track that in the character object as a list of AttributeModifier instances, each in turn keeping track of what the actual source of those modifiers are.
This just barely scratches the surface. As I noted, the question itself is fairly broad. But I strongly recommend using the available language features to ensure that your variables represent things in a type-specific way, rather than just using int values for things that aren't really integers, and to use collection classes that more correctly represent the semantics of what your code is intended to do.
Note that this also means you probably should have an enum type for the races. E.g.:
enum Race
{
Dwarf,
Elf,
HalfElf,
Halfling,
HalfOrc,
Human,
// etc.
}
And your chain of if statements is probably better represented as a switch:
Attribute racialMod;
switch (race)
{
case Human:
case Halfling:
// etc.
racialMod = Attribute.Strength;
break;
case Elf:
case HalfElf:
// etc.
racialMod = Attribute.Dexterity;
break;
}
modifiers[racialMod] = 2;
Something like that. The point is to make sure the code reads more like what the original specification would say (if you actually had written one). This will make the code easier to understand, and it will be less likely for you to put bugs in the code (e.g. you accidentally type the wrong magic, unnamed integer).
I am creating a Dungeons and Dragons Character Creator.
That's a fun beginner project; I did the same when I was learning to program.
I need to find a way to randomly select two of the remaining...
You need to find two distinct values, call then x and y. The solution you've arrived at is:
Generate x
Try to generate y ten times
If no attempt succeeded to find a distinct y, hard-code a choice.
That works, and you almost never have to use the hard-coded choice. But I thought you might be interested to know that there is an easier way to generate two distinct numbers. Let's suppose we want two distinct numbers from 0, 1, 2, 3 or 4. (Obviously if you want a different range, say, 1 through 5, you can solve that problem by generating two distinct numbers 0->4 and then adding one to each.)
The improved algorithm is:
Choose x between 0 and 4 as usual.
Choose n between 1 and 4.
y = (x + n) % 5;
Think about it this way. Suppose we make a list like this:
0, 1, 2, 3, 4, 0, 1, 2, 3
We randomly choose x from the first five entries on the list, and then we choose y by stepping forwards between 1 and 4 steps. Since the list does not repeat in one to four steps, we know that we'll get two unique elements. The math does the equivalent of that.
You could similarly have used % in your program:
if (randomX == 5)
randomY = 1;
else
randomY = randomX + 1;
could be written
randomY = randomX % 5 + 1
If you're unfamiliar with %, it is the remainder operator. It is the complement of the / operator. The rule is:
int x = whatever;
int y = whatever;
int r = x % y;
is the same as:
int r = x - (x / y) * y;
That is, it is the remainder when x is divided by y. Keep in mind that the remainder can be negative!
(Disclaimer: I don't love this option, but couldn't think of another way other than reflection which is even nastier)
You could define a class that masks the fact that all of the mods are stored as an array and therefore can be indexed using a random number.
Something like the following:
public class StatMods
{
public int RaceStrMod { get { return this.mods[0]; } set { this.mods[0] = value; } }
public int RaceDexMod { get { return this.mods[1]; } set { this.mods[1] = value; } }
public int RaceConMod { get { return this.mods[2]; } set { this.mods[2] = value; } }
public int RaceIntMod { get { return this.mods[3]; } set { this.mods[3] = value; } }
public int RaceWisMod { get { return this.mods[4]; } set { this.mods[4] = value; } }
public int RaceChaMod { get { return this.mods[5]; } set { this.mods[5] = value; } }
private readonly int[] mods;
private static readonly Random rand = new Random();
public StatMods()
{
this.mods = new int[6];
}
public void ApplyRandomMod(int modification)
{
this.mods[rand.Next(0, 6)] += modification;
}
}
The output of the final merge is [0,0,0,0]. I think it's working right for the first merge of the left half of the array because it puts in order correctly. I would like help debugging this.
static public void Main(string[] args)
{
int[] input = { 4, 1, 3, 2};
MergeSort(input, 0, input.Length - 1);
for (int i = 0; i < input.Length; i++)
{
Console.WriteLine(input[i]);
}
Console.ReadLine();
}
static public void MergeSort(int[] input, int left, int right)
{
if (left < right)
{
int middle = (left + right) / 2;
MergeSort(input, left, middle);
MergeSort(input, middle + 1, right);
Merge(input, left, (middle + 1), right);
}
}
static public void Merge(int[] input, int left, int middle, int right)
{
int tempindex = left;
int[] tmp = new int[input.Length];
int rightpointer = middle + 1;
int leftpointer = left;
while (leftpointer <= middle && rightpointer <= right)
{
if (input[leftpointer] < input[rightpointer])
{
tmp[tempindex] = input[leftpointer];
leftpointer++;
}
else
{
tmp[tempindex] = input[rightpointer];
rightpointer++;
}
tempindex++;
}
while (leftpointer <= middle)
{
tmp[tempindex] = input[leftpointer];
leftpointer++;
tempindex++;
}
while (rightpointer <= right)
{
tmp[tempindex] = input[rightpointer];
right++;
tempindex++;
}
for (int i = 0; i < tmp.Length; i++)
{
input[i] = tmp[i];
}
}
Changed code slightly to make it work . Please refer some online resources for better understanding of the algorithm
private static void Merge(int[] input, int left, int middle, int right)
{
int[] temp = new int[input.Length];
int rightpointer = middle - 1;
int tempindex = left;
int num = right - left + 1;
while ((left <= rightpointer) && (middle <= right))
{
if (input[left] <= input[middle])
{
temp[tempindex] = input[left];
tempindex++;
left++;
}
else
{
temp[tempindex] = input[middle];
tempindex++;
middle++;
}
}
while (left <= rightpointer)
{
temp[tempindex] = input[left];
tempindex++;
left++;
}
while (middle <= right)
{
temp[tempindex] = input[middle];
tempindex++;
middle++;
}
for (int i = 0; i < num; i++)
{
input[right] = temp[right];
right--;
}
}
Rather than simply post a fixed version of your code, which I feel is a pointless and unhelpful exercise and would not benefit you in any important way, I will try to explain your mistakes from a higher-level point of view, with some advice on how to avoid similar mistakes in the future.
Your code has several problems in it. They fall into three categories:
Off-by-one. Classic programming error, something you should always be alert for any time you are dealing with indexes, and especially when dealing with the boundary values for those edge cases.
Simple typo. Another classic programming error. In this case, it's likely it happened when you copied and pasted a while loop, and then over-typed the wrong variable name. You should try to avoid copy/paste as much as possible; in the case of the classic merge sort, it's practically unavoidable, so you should follow the corollary rule: if you are copy/pasting where you have to change variable names in the pasted code, double- and triple-check your work!
Conceptual. This is "classic" in the sense that it's hard for programmers to learn good high-level conceptualization and abstraction skills, but it manifests itself in lots of different ways (unlike the first two), and so it's harder to provide general advice for. My best advice here is something a professor of mine years ago was fond of saying: KISS, or "keep it simple, stupid." Or the way I like to express the same thing: "lazy is good". Either way, the point is to put your effort into simplifying the problem as much as possible. Keep your data modeling close to how you'd want to solve the problem if you had to do it by hand, and make sure if you had to do it by hand, you'd do it in the easiest, least labor-intensive way.
So, where did those mistakes show up? From last to first…
Conceptually, the biggest issue in your code is that your Merge() method allocates an array that is the full size of the input array, even though you know for sure the method is supposed to consider only a subset of that array. (You know that, because that's the whole point of the other three parameters to the method.)
This caused a problem later in the method, because when you went to copy the tmp array back to input, you just blindly wrote a loop to copy all of the data. This is even though the method has only actually written values to a subset of the tmp array. This, ultimately, is why your output is all zeroes. Not all of the values in the tmp array have been written, so they still have their default value of 0, and then you copy those values into the input array, overwriting the original values you actually wanted.
Had you allocated for tmp an array only just large enough to contain the data you were actually going to manipulate during that particular call, it would have been painfully clear how to copy the data back at the end. Worst-case scenario, you would have gotten an IndexOutOfRangeException, which is a much nicer way to discover a bug than to just get the wrong output at the end (this is an example of the Fail-fast principle, another concept I'm fond of).
Now, about that typo. Frankly, that error you would have found eventually, once you had data that triggered it. As it happens, your input data set doesn't, because it never enters the loop where the bug was. But, your last while loop in the Merge() method is incrementing the right variable instead of the rightindex variable. This would lead to an IndexOutOfRangeException once the tempindex variable gets too large (so, at least it would have failed-fast :) ).
FWIW, in addition to simply making sure you double- and triple-check your work when you copy/paste, this is IMHO an example of why in simple loops like this, it's actually desirable to use the more concise syntax of putting the post-increment expression inside your array. Doing this ensures that the index you're incrementing is for sure the index you're using to index the array:
while (rightpointer <= right)
{
tmp[tempindex++] = input[rightpointer++];
}
Finally, as for your off-by-one error, this is avoidable not just by being very careful when dealing with boundary values for indexes, but also by always keeping in mind some mental representation (I like to visualize the data structures) of how the values relate.
In this particular case, the value you pass for the middle parameter of the Merge() method is handled inconsistently. That is, in the MergeSort() method, the middle value is clearly used as the upper bound of the "left" side partition. This can be seen in the recursive calls to the MergeSort() method. But then when you call Merge(), you add one to the value for that call's middle parameter.
Yet, in the Merge() method, you are still using the parameter to indicate the upper bound of the left partition. When you pass middle + 1 as that value, you are telling the Merge() method to treat a different section of the array as the left partition than the section that you actually sorted in the recursive call to MergeSort() before calling Merge(). This has obvious, and harmful, consequences in the outcome of the algorithm. :)
If you work harder to visualize those partitions, and the relationships of the variables that define them to the partitions, you would be less likely to write the wrong code in the first place.
Here is a corrected version of your code, with commented lines showing the changes I made:
static public void Main(string[] args)
{
int[] input = { 4, 1, 3, 2 };
MergeSort(input, 0, input.Length - 1);
for (int i = 0; i < input.Length; i++)
{
Console.WriteLine(input[i]);
}
Console.ReadLine();
}
static public void MergeSort(int[] input, int left, int right)
{
if (left < right)
{
int middle = (left + right) / 2;
MergeSort(input, left, middle);
MergeSort(input, middle + 1, right);
// was: Merge(input, left, (middle + 1), right);
Merge(input, left, middle, right);
}
}
static public void Merge(int[] input, int left, int middle, int right)
{
// was: int tempindex = left;
int tempindex = 0;
// was: int[] tmp = new int[input.Length];
int[] tmp = new int[right - left + 1];
int rightpointer = middle + 1;
int leftpointer = left;
while (leftpointer <= middle && rightpointer <= right)
{
if (input[leftpointer] < input[rightpointer])
{
tmp[tempindex] = input[leftpointer];
leftpointer++;
}
else
{
tmp[tempindex] = input[rightpointer];
rightpointer++;
}
tempindex++;
}
while (leftpointer <= middle)
{
tmp[tempindex] = input[leftpointer];
leftpointer++;
tempindex++;
}
while (rightpointer <= right)
{
tmp[tempindex] = input[rightpointer];
// was: right++;
rightpointer++;
tempindex++;
}
for (int i = 0; i < tmp.Length; i++)
{
// was: input[i] = tmp[i];
input[left + i] = tmp[i];
}
}
Finally, I have not written anything about debugging. But really, the first step in trying to fix code is to step through it with a debugger. When you wrote the code, you had in mind various actions and outcomes that you expected the code to take. When you debug the code, what you're looking for is any place those expectations are violated.
Had you stepped through the code, one of the first things you would have found that violated your expectations was the copying of the uninitialized 0 values from the tmp array into the input array. You may or may not have fixed that particular problem the same way I did — the most expedient way at that point would in fact have been simply to change the boundaries of the for loop, to copy only those elements you actually wrote values to (I fixed it the way I did both because that uses less memory, and because it's an example of writing the code so that when there are mistakes, it can fail faster) — but at least you would have found the problem.
Modifying a plugin for a Rust game server, and I'm not too great at C#. I currently have the system set up to print the votes to chat every time a new vote comes in, but want it based on percentage or every 5 votes. How would I do that? This is my current code.
public int tallyVotes() {
int yesVotes = 0;
foreach (var votes in votesReceived) {
if (votes.Value)
yesVotes = yesVotes + 1;
}
return yesVotes;
couldn't you return yesVotes/5 as an integer, essentially giving you 1 vote for every 5.
int voteWeighting = 5;
return (int)yesVotes/voteWeighting;
if you're looking for when to print it then the generic fizz buzz solution would fix it.
int printWeighting = 5;
if (yesVotes%printWeighting ==0)
{
//print stuff here
}
this works by using the modulus function "a%b" it will return the remainder when you divide a by b and as such return a number less than "b" but greater or equal to zero. as such if you only ever increment by 1 the number, then when a mod b is 0 you have incremented b times.
Fizzbuzz example
I am trying to make a practice program that calculates a persons overall grade. The test consists of three parts each with different weightings. Part 1 is worth 5 grades, part two 3 grades and part 3 is worth 2 grades.
So if a person got A B C they would receive 5A 3B and 2C.
Now, in order to receive and A/B/C overall requires a certain amount of each grade. For example in order to receive an A overall you need to have at least 5A's and 7 of the grades must be B or higher and all the grades need to be C or or higher.
B, C, D etc all have their own requirements too.
What is the best way to code this as at the moment I am using a counter for each grade and then doing if/else if statements to check the amount of each grade a person has got like so:
if (aGradeCount >= 5)
{
//Add total grade
}
}
}
//To receive a B
if(bGradeCount >= 3 && aGradeCount <5 && cGradeCount >=2)
{
if(bGradeCount + cGradeCount +dGradeCount + aGradeCount>= 7)
{
if(dGradeCount <= 3)
{
//Add new total grade
}
}
}
Now I understand this is terrible practice, but how can I code this better? Using a switch statement? If so how do I go about doing that?
how can I code this better?
Write a specification. Then for every concept mentioned in the specification, write a method. Here's part of a specification; you already wrote it:
in order to receive an A overall you need to have at least 5 A's and least 7 of the grades must be B or higher and all the grades need to be C or better.
Break it down
in order to receive an A overall
at least 5 A's AND
at least 7 of the grades must be B or higher AND
all the grades need to be C or better
OK, now we can start turning that into a method:
static bool QualifiesForA(int aCount, int bCount, int cCount, int dCount)
{
// In order to receive an A overall we require:
// TODO: at least 5 A's AND
// TODO: at least 7 of the grades must be B or higher AND
// TODO: all the grades need to be C or better
// If these conditions are not met then an A is not earned.
return false;
}
All right we have turned our specification into code. Wrong code, but code. Let's keep going. We have a line of a specification. Write a method:
static bool AtLeastFiveA(int aCount)
{
return aCount >= 5;
}
Hey, that was a correct method. We are making progress. Now use it:
static bool QualifiesForA(int aCount, int bCount, int cCount, int dCount)
{
// In order to receive an A overall we require:
// at least 5 A's AND
// TODO: at least 7 of the grades must be B or higher AND
// TODO: all the grades need to be C or better
bool atLeast5A = AtLeastFiveA(aCount);
// If these conditions are not met then an A is not earned.
return false;
}
Now we have another problem. At least 7 are B or higher. OK, write a method:
static bool AtLeastSevenB(int aCount, int bCount)
{
return aCount + bCount >= 7;
}
Another correct method! Use it!
static bool QualifiesForA(int aCount, int bCount, int cCount, int dCount)
{
// In order to receive an A overall we require:
// at least 5 A's AND
// at least 7 of the grades must be B or higher AND
// TODO: all the grades need to be C or better
bool atLeast5A = AtLeastFiveA(aCount);
bool atLeast7B = AtLeastSevenB(aCount, bCount);
// If these conditions are not met then an A is not earned.
return false;
}
Now we need the last bit:
static bool NoD(int dCount)
{
return dCount == 0;
}
Put it together.
static bool QualifiesForA(int aCount, int bCount, int cCount, int dCount)
{
// In order to receive an A overall we require:
// at least 5 A's AND
// at least 7 of the grades must be B or higher AND
// all the grades need to be C or better
bool atLeast5A = AtLeastFiveA(aCount);
bool atLeast7B = AtLeastSevenB(aCount, bCount);
bool noD = NoD(dCount);
if (atLeast5A && atLeast7B && noD)
return true;
// If these conditions are not met then an A is not earned.
return false;
}
Now, the question to ask yourself is:
Is this code correct? GET IT CORRECT FIRST. This code is very verbose but I'll tell you right now, it exactly matches the specification you gave.
Once the code is correct, can we make it more clear?
Yes; we could for instance say:
static bool QualifiesForA(int aCount, int bCount, int cCount, int dCount)
{
// In order to receive an A overall we require:
// at least 5 A's AND
// at least 7 of the grades must be B or higher AND
// all the grades need to be C or better
bool atLeast5A = AtLeastFiveA(aCount);
bool atLeast7B = AtLeastSevenB(aCount, bCount);
bool noD = NoD(dCount);
return atLeast5A && atLeast7B && noD;
}
And now maybe you will say, you know, some of these methods are unnecessary abstractions, maybe I can just replace them with their bodies:
static bool QualifiesForA(int aCount, int bCount, int cCount, int dCount)
{
// In order to receive an A overall we require:
// at least 5 A's AND
// at least 7 of the grades must be B or higher AND
// all the grades need to be C or better
bool atLeast5A = aCount >= 5;
bool atLeast7B = aCount + bCount >= 7;
bool noD = dCount == 0;
return atLeast5A && atLeast7B && noD;
}
The point is: we start from a very verbose, CLEARLY CORRECT program, and then we make small, simple, clearly correct transformations to make it more concise. When you think you have a good balance of concision and readability, stop.
OK, now you have solved the problem of "did we earn an A?" Now you do "did we earn a B?" and so on. Write a specification for every part, and then write code that clearly implements the specification.
This sounds like a heavyweight process, but this will pay huge dividends as you learn how to program. Your code will be better organized, it will be less buggy, it will be easier to read and understand and modify.
The point of this technique is to focus on obvious correctness of every part. Always concentrate on obvious correctness. A program which is correct but you cannot tell it is correct is a program that might not be correct! Always concentrate on correctness first. Making a wrong program more elegant, or faster, or more feature complete means that you have an elegant, fast, rich-featured bug farm.
Summing up:
Write clear specifications.
Write code to clearly match the spec.
Tiny methods are A-OK. You can always eliminate them later.
Correctness is more important than everything else; make the code better once you know it is correct.
I don't know if that's terrible practice. It's a little unnecessary, since there's nothing else in the block, but another if statement. You can use more && operators and parenthesis if you just want to use one if statement.
if ((bGradeCount >= 3 && aGradeCount <5 && cGradeCount >=2) &&
(bGradeCount + cGradeCount +dGradeCount + aGradeCount>= 7) &&
(dGradeCount <= 3))
{
char b = 'B';
person.TotalGrade = b.ToString();
}
For code clarity I would do this that way :
//Main function
{
///code
if(MethodWhichDescribesLogic(aGradeCOunt,bGradeCount,cGradeCount,dGradeCount){
char b = 'B';
person.TotalGrade = b.ToString();
}
}
Then in some place else :
bool MethodWhichDescribesLogic(type aGradeCount, type bGradeCount, type cGradeCount, type dGradeCount){
return
(PassingGrade(bGradeCount,aGradeCount,cGradeCount) &&
GoodGradesType(bGradeCount,cGradeCount,dGradeCount,aGradeCount) &&
dGradeCount <= 3);
}
bool PassingGradesCount(type bGradeCount,type aGradeCount,type cGradeCount)
{
return bGradeCount >= 3 && aGradeCount <5 && cGradeCount >=2;
}
bool GoodGradesCount(type cGradeCount,type bGradeCount,type aGradeCount,type dGradeCount)
{
return bGradeCount + cGradeCount +dGradeCount + aGradeCount>= 7;
}
Remember that every if-else switches can be replaced by conditional table.
So if overall grades count would be like 10. It could be
A B C Overall
5 7 10 A
4 7 10 B
Then you make array of it and find where you are in the array.
For example (I admit that I'm puzzled by your example so I might get it wrong here.):
var grades = new[]{
new { A = 5. B = 7, C = 10, Overall = "A"},
new { A = 4, B = 7, C = 10, Overall = "B"},
...
}
var myGrade = grades.FirstOrDefault(g => myA >= g.A && myB >= g.B && enoughC)
With proper formatting it looks much better than tons of if's. And you always have your choice table in front of you.
Firstly, make use of Boolean Algebra in the OverallGrade() to see which cases you don't need to consider. For example, if for checking an A grade you have already seen that gradeDistribution.A >= 5, don't test gradeDistribution.A < 5 when testing for grade 'B' as it is obvious that if you are testing the case for B, you have already tested gradeDistribution.A < 5 as true.
Next, put the grade calculation in another method and make that method return as early as possible.
Finally, in order to get the overall grade, you may write the method as:
private static char OverallGrade(int partA, int partB, int partC)
{
//Now in this method, check one by one which
//overall grade the provided values fall in
//Call another method to get count of individual As, Bs, Cs etc
var gradeDistribution = GetIndividualCount(partA, partB, partC);
//Now, first check for A and return immediately if true
if (gradeDistribution.A >= 5) return 'A';
//Now, check for B and again return if the values satisfy for B
if (gradeDistribution.B >=3
&& gradeDistribution.C <= 2
&& gradeDistribution.D <= 3
&& ...)
return 'B';
//Keep adding for each case and return as soon as you find the grade.
}
Now, the class GradeDistribution, whose variable we have used above can be created which will hold count of each grade:
public class GradeDistribution
{
public int A; //Count for Grade A
public int B; //Count for Grade B
public int C; //Count for Grade C
public int D; //Count for Grade D
}
The above code is an example of introducing a class for an entity that does not exist in real world.
Next, GetIndividualCount() can be written as:
private static GradeDistribution GetIndividualCount(int partA, int partB, int partC)
{
var gradeDistribution = new GradeDistribution();
/*
Calculate and assign values to gradeDistribution.A, gradeDistribution.B...
*/
return gradeDistribution;
}
I'm currently trying to create a program that estimates location based on signal strength. The signal strength value is an int and then i need a lookup dictionary with ranges.
So I would have something like:
Signal Strenth Position
0-9 1
10-19 2
20-29 3
and then I would want to look up what position a signal strength relates to, for example 15 would relate to position 2.
I know I can just have a load of if statements but is there a good way to do this using some sort of lookup dictionary?
If you have arbitrary but consecutive ranges you can use an array of the upper bounds and perform a binary search to get the position:
// Definition of ranges
int[] ranges = new int[] { 9, 19, 29 };
// Lookup
int position = Array.BinarySearch(ranges, 15);
if (position < 0)
position = ~position;
// Definition of range names
string[] names = new string[] { "home", "street", "city", "far away" };
Console.WriteLine("Position is: {0}", names[position]);
Array.BinarySearch returns the index of the item in the array if it exists (array must be sorted obviously) or the bitwise inverted index where the item should be inserted to keep the array sorted.
What about :
int position = signalStrength / 10 + 1;
Kindness,
Dan
When you want to use Dictionary, you need at least some special key type to deal with the ranges. KeyType can be abstract and two derived types KeyTypeRange(int int) and KEyTypeSearch( int). Some special comparison logic must be implemented to compare an KeyTypeSearch with an KeyTypeRange.
SortedDictionary<KeyType,int> lookup = new Dictionary<int,int>();
lookup.Add( new KeyTypeRange(1,10),1);
lookup.Add( new KeyTypeRange(11,20),2);
lookup.Add( new KeyTypeRange(21,30),3);
lookup.TryGetValue( new KeyTypeSearch(15) );
It shows a possible solution to use different esearch keys and key values in dictionaries. But this seems to be Overkill for this problem. This problem is solved best by the BinarySearch solution.
Good is a function of purpose. All of the above solutions work well presuming that any given range is a small number of integers. Otherwise you may want to use whatever the real world math function is to determine your group. For instance, for the example given, your answer function would be x % 10 + 1; That'll run much faster than a dictionary.
You could do a Dictionary, where the first int is the signal strength and the second int is the position. You would need to add an entry for every value in the range (so, one for signal strength 0, position 1, signal strength 1, position 1, etc.), but it would be a very quick, single line lookup.
Something like:
Dictionary<int, int> values;
values = new Dictionary<int, int>();
values[0] = 1;
values[1] = 1;
...
values[29] = 3;
and then, to access it:
Console.WriteLine(values[27].ToString());
For future expansion i would do 2 dictionaries.
Just in case those rates change
so a
dictionary<string,dictionary<int,int>>
or just use custom classes
the string would be static strings like low med, high, then you can change the ranges in your foreach initilixing the initial values
One solution would be to use a simple list, where each position in the list represents a different position that you're scanning for. In code, it might look something like this (assuming that all position numbers are sequential):
** Note: I have not actually run this code to make sure it works as-is... you might also need to implement an IEqualityComparer on Range in order for the IndexOf operation to return the proper position:
public class Controller
{
List m_positions = new List();
public void LoadPositions()
{
m_positions.Add(new Range(0, 9));
m_positions.Add(new Range(10, 19));
m_positions.Add(new Range(20, 29));
}
public int GetPosition (int signal)
{
Range range = m_positions.Single(a => IsBetween(signal, a.Min, a.Max));
return m_positions.IndexOf(range);
}
private static bool IsBetween (int target, int min, int max)
{
return min = target;
}
}
It's probably pretty self-explanatory, but to avoid any confusion, here's what the Range class might look like:
public class Range
{
public Range(int min, int max)
{
this.Min = min;
this.Max = max;
}
public int Min
{
get;
private set;
}
public int Max
{
get;
private set;
}
}
if there is a direct correlation between signal range and the position then use what #agileguy suggested.
If you have positions distributed non linearly across the signal strength then one way would be:
class SignalStrengthPositionMapper
{
private static readonly int[] signalStrength = { Int32.MinValue, 0, 5, 11, 15, 20, 27, 35 };
public static int GetPosition(int strength)
{
return StrengthSearch(0, signalStrength.Length, strength);
}
// modified binary search
private static int StrengthSearch(int start, int end, int strength)
{
int mid = 0;
while (start <= end)
{
mid = (start + end) / 2;
if (strength >= signalStrength[mid]) // lower bound check
{
start = mid + 1;
if (strength < signalStrength[start]) // upper bound check
return mid;
}
else if (strength < signalStrength[mid]) // upper bound check
{
end = mid - 1;
if (strength >= signalStrength[end]) // lower bound check
return mid;
}
}
return 0;
}
}
Try using generics:
Dictionary<int,int> lookup = new Dictionary<int,int>();
lookup.Add(0,1);
lookup.Add(1,1);
lookup.Add(2,1);
lookup.Add(3,1);
...
lookup.Add(9,1);
lookup.Add(10,2);
lookup.Add(11,2);
etc
Then, lookup[22] would return value of 3. I suggest using a set of loops to create your 'ranges'. With this method, you're guaranteed O(1) access time.