Get Test score through range values - c#

I have a requirement where I take an int (score in the example) below and return a decimal value by comparing it against various range conditions. If score is between 1 and 10 then the test is automatically failed and a decimal is not returned (in this case I return the string "FAILED". Higher than then 10 and the corresponding decimal value is returned based on what range score matches. This feels wrong doing it this way and was wondering if there is a better method. I considered using a dictionary with each range e.g. between 1 and 10 stored in a key and then querying this to return the value. However, I'm unsure how to do this. Can anyone suggest a better method?
Thank you
public decimal GetTestScore(int score, out string status)
{
decimal score = 0m;
string status = string.Empty;
if(score >= 1 && score <= 10)
status = FAILED;
else if(score >= 10 && score <= 20)
score = 1.0;
else if(score >= 20 && score <= 30)
score = 2.0;
else if(score >= 30 && score <= 40)
score = 3.0;
return score;
}

If you predict that your solution may have to expand to accommodate more score definitions, or have user configurable score definitions, you could use something like the following example:
public class ScoreResult
{
public int Low { get; set; }
public int High { get; set; }
public string Status { get; set; }
public double ReplacementScore { get; set; }
public ScoreResult(int low, int high, string status,
double replacementScore)
{
Low = low;
High = high;
Status = status;
ReplacementScore = replacementScore;
}
}
public class ScoreCalculator
{
private List<ScoreResult> _scores = new List<ScoreResult>();
public ScoreCalculator()
{
/*These are easy to change and could be
loaded from a database/service*/
_scores.Add(new ScoreResult(1, 10, "FAILED", 0));
_scores.Add(new ScoreResult(10, 20, string.Empty, 1));
_scores.Add(new ScoreResult(20, 30, string.Empty, 2));
_scores.Add(new ScoreResult(30, 40, string.Empty, 3));
}
public ScoreResult GetScoreResult(int score)
{
//Will return null if no match found
return _scores.FirstOrDefault
(s => score >= s.Low && score <= s.High);
}
}
Example usage:
var result = GetScoreResult(9);
Console.WriteLine(result.Status); //FAILED
Console.WriteLine(result.ReplacementScore); //0.0
Each score can be represented by an instance of ScoreResult, this makes it very easy to find a match using Linq and very easy to add/remove/modify your scores. You could even store the ScoreResult data in a database so that it is easily configurable.

I think this may work:
public decimal GetTestScore(int score, out string status)
{
status = string.Empty;
if (score < 10)
{
status = "FAILED";
return 0m;
}
return Math.Ceiling(score / 10m);
}

Well first return a nullable decimal and if validation failed, simply return null.
public decimal? GetTestScore(int score)
The other question is: why does it feel wrong? For simple application this will be enough.
Don't overengineer the problem.
A dictionary is not space efficient in your situation as you would need many redundant values for the ranges to map to one single score value.
Your solution is actually wrong as you are comparing to score <=20 and then for the next score value >=20 which is redundant (even if it behaves correctly)
Edit: You can of course simplify your check if the constraints are projectable
public decimal? GetTestScore(int score)
{
if (score >= 1 && score <= 10)
{
return null;
}
if (score < 1 || score > 40)
{
return 0;
}
return (score-1) / 10;
}
Edit again: Added boundary conditions

Related

Finding largest product in c#

Currently I am trying to find the thirteen adjacent digits in a 1000-digit number that will have the greatest product.Now I written a function which is supposed to multiply the desired number of adjacent digits to be multiplied and later store the product in a list . The two parameters the functions takes are the desired number of adjacent digits and string which contains the number. But for some reason it wont stop running.
public static void giveProduct(int quantity, string word)
{
int product = 1;
int place1 = 0;
int place2 = quantity - 1;
int temp = 1;
string temp2;
while (place2 < word.Length)
{
for (int i = place1; i < place2; i++)
{
temp2 = word[i].ToString();
temp = Int32.Parse(temp2);
product = product * i;
}
products.Add(product);
product = 1;
place1 += quantity;
place2 += quantity;
}
}
Can't reproduce your issue, the method terminates "correctly" for any sensible input.
But anyway, that is far from the only issue in your implementation. Your method is not calculating correctly the maximum product because you are skipping through the string quantity characters at a time. You should be skipping one character at a time and taking the quantity long substring starting at that position.
For string 123456 and quantity 3 you are evaluating 123 and 456. You should be checking 123, 234, 345, etc.
Also, get into the habit of:
Validating inputs
Writing helper methods. The shorter a method, the harder to introduce a bug in it.
Consider all possible values that word can represent? Have you considered { 1234 }? (note the leading and trailing spaces). How about -1234?
Prepare for the worse. Make your code robust so its able to handle incorrect data; your program will crash if the input is 123$5.
With all that in mind, consider the following implementation:
First a simple helper method that evaluates the product of all the digits of a given string representing a number.
private static bool TryMultiplyDigits(string number, out int product)
{
Debug.Assert(number != null && number.Length > 0);
product = 1;
foreach (var c in number)
{
int digit;
if (int.TryParse(c.ToString(), out digit))
{
product *= digit;
}
else
return false;
}
return true;
}
Ok great, this method will give us the correct product or simply tell us it can't evaluate it for any input.
Now, a method that will create all the possible subtrings and return the maximum product found:
public static int GetMaximumProduct(string number, int quantity)
{
if (number == null)
throw new ArgumentNullException(nameof(number));
if (quantity < 1)
throw new ArgumentOutOfRangeException(nameof(quantity));
if (quantity > number.Length)
throw new ArgumentException($"{nameof(quantity)} can not be greater than the length of {nameof(number)}.");
var normalizedNumber = number.Trim();
normalizedNumber = normalizedNumber.StartsWith("-") ? normalizedNumber.Substring(1) : normalizedNumber;
if (string.IsEmpty(normalizedNumber))
{
product = 0;
return true;
}
var maximumProduct = 0;
for (var i = 0; i < normalizedNumber.Length - (quantity - 1); i++)
{
int currentProduct;
if (TryMultiplyDigits(normalizedNumber.Substring(i, quantity), out currentProduct))
{
if (currentProduct > maximumProduct)
{
maximumProduct = currentProduct;
}
}
else
{
throw new FormatException("Specified number does not have the correct format.");
}
}
return maximumProduct;
}
And we're done!

How to elegantly assign values in function of number range?

I am searching of an elegant way to assign values in function of a number belonging to a specific range.
For example, having the number X, the elegant way would return:
'a' - if X is between 0 and 1000
'b' - if X is between 1000 and 1500
and so on (but a fixed number of defined intervals)
By elegant I mean something more appealing than
if ((x => interval_1) && (x < interval_2))
class_of_x = 'a';
else if ((x => interval_2) && (x < interval_3))
class_of_x = 'b';
...
or
if(Enumerable.Range(interval_1, interval_2).Contains(x))
class_of_x = 'a';
else if(Enumerable.Range(interval_2 + 1, interval_3).Contains(x))
class_of_x = 'b';
...
I hate seeing so many IFs.
Also, the interval values can be stored in a collection (maybe this would help me eliminate the ISs?), not necessary as interval_1, interval_2 and so on.
Somewhat inspired by this question How to elegantly check if a number is within a range? which came out while looking for a solution for the problem described above.
You can create extention method:
public static class IntExtensions
{
// min inclusive, max exclusive
public static bool IsBetween(this int source, int min, int max)
{
return source >= min && source < max
}
}
and then
// Item1 = min, Item2 = max, Item3 = character class
IList<Tuple<int, int, char>> ranges = new List<Tuple<int, int, char>>();
// init your ranges here
int num = 1;
// assuming that there certainly is a range which fits num,
// otherwise use "OrDefault"
// it may be good to create wrapper for Tuple,
// or create separate class for your data
char characterClass = ranges.
First(i => num.IsBetween(i.Item1, i.Item2)).Item3;
If my comment is correct then your first if statement has a lot of unnecessary checks, if its not less than interval 2 then it must be greater than or equal to, therefore:
if((x => i1) && (x < i2))
else if(x < i3)
else if(x < i4)...
When a "true" argument is found then the rest of the if statement is irrelevant, as long as your conditions are in order this should suit your needs
Create an Interval class and use LINQ:
public class Interval
{
public string TheValue { get; set; }
public int Start { get; set; }
public int End { get; set; }
public bool InRange(int x)
{
return x >= this.Start && x <= this.End;
}
}
public void MyMethod()
{
var intervals = new List<Interval>();
// Add them here...
var x = 3213;
var correctOne = intervals.FirstOrDefault(i => i.InRange(x));
Console.WriteLine(correctOne.TheValue);
}
Firstly, define a little class to hold the inclusive maximum value, and the corresponding value to use for that band:
sealed class Band
{
public int InclusiveMax;
public char Value;
}
Then declare an array of Band which specifies the value to use for each band and loop to find the corresponding band value for any input:
public char GetSetting(int input)
{
var bands = new[]
{
new Band {InclusiveMax = 1000, Value = 'a'},
new Band {InclusiveMax = 1500, Value = 'b'},
new Band {InclusiveMax = 3000, Value = 'c'}
};
char maxSetting = 'd';
foreach (var band in bands)
if (input <= band.InclusiveMax)
return band.Value;
return maxSetting;
}
Note: In real code, you would wrap all this into a class which initialises the bands array only once, and not every single time it's called (as it is in the code above).
Here you could also use the static System.Linq.Enumerable's Range() method that implements
IEnumerable<T>
with the Contains() method (again from System.Linq.Enumerable), to do something like:
var num = 254;
if(Enumerable.Range(100,300).Contains(num)) { ...your logic here; }
This looks more elegant in my eyes at least.

Get the day of the month (not using .NET DateTime)

I'm working on a date-time system for a game. In the interest of reusability, I decided not to use the DateTime included in .NET. It reflects Earth time, whereas I'd like to be able to have arbitrary values for things like hoursPerDay, and have, say, 10 months in a year, with any number of days each. For now I don't care about things like leap years, or time zones.
I'm having trouble figuring out how to get the current day of the month (MM-DD-YYYY). The code in question is the DayOfMonth property's get method.
public sealed class Time
{
int _ticks = 0;
int _ticksPerSecond = 30;
int _secondsPerMinute = 60;
int _minutesPerHour = 60;
int _hoursPerDay = 24;
readonly List<string> _days;
readonly Dictionary<string, int> _months;
// I haven't decided on ctor parameters yet, but we'd define base units
public Time()
{
// What we call the days of the week.
_days = new List<string> { "Monday", "Tuesday", "Wednesday" };
// What we call the months of the year, and the number of days each.
_months = new Dictionary<string, int>
{
{"January", 31},
{"February", 28},
{"March", 31}
};
}
public void Advance(int ticks)
{
_ticks += ticks;
}
// Number of ticks elapsed since epoch start
public int TotalTicks { get { return _ticks; } }
// Number of ticks elapsed during the current second
public int CurrentTicks { get { return _ticks % _ticksPerSecond; } }
public int TotalSeconds { get { return _ticks / _ticksPerSecond; } }
public int CurrentSeconds { get { return TotalSeconds % _secondsPerMinute; } }
public int TotalMinutes { get { return TotalSeconds / _secondsPerMinute; } }
public int CurrentMinutes { get { return TotalMinutes % _minutesPerHour; } }
public int TotalHours { get { return TotalMinutes / _minutesPerHour; } }
public int CurrentHours { get { return TotalHours % _hoursPerDay; } }
public List<string> Days { get { return _days; } }
public int TotalDays { get { return TotalHours / _hoursPerDay; } }
public int CurrentDay { get { return TotalDays % _days.Count; } }
public string DayOfWeek { get { return _days[CurrentDay]; } }
public int DayOfMonth
{
get
{
var d = 0;
while (d < TotalDays)
{
foreach (var month in Months)
{
var daysInMonth = month.Value;
while (daysInMonth > 0 && d < TotalDays)
{
d++;
daysInMonth--;
}
}
}
return d;
}
}
public Dictionary<string, int> Months { get { return _months; } }
public int TotalMonths { get { return TotalDays / _months.Values.Sum(); } }
public int CurrentMonth { get { return TotalMonths % Months.Count; } }
…
}
A. The code above doesn't work. It always returns a fixed value.
B. The only way I can think of to accomplish this is to iterate through each month, adding up days until we reach each month's day count, until we hit the total days elapsed. That doesn't seem very efficient to me, especially with the redundant checking if (d < TotalDays) above.
C. It seems like the more time that elapses, the longer it will take to find the DayOfMonth (and perhaps other) value(s).
I guess I'm looking for a paradigm shift, because I think I might be painting myself in a corner.
Update
For anyone who is interested in the (somewhat) working algorithms, I got this fixed by changing the DayOfMonth property to the following.
public int DayOfMonth
{
get
{
var d = TotalDays;
var found = false;
while(!found)
{
foreach (var month in _months)
{
if (d > month.Value) d -= month.Value;
else
{
found = true;
break;
}
}
}
return d;
}
}
The TotalMonths property needed a similar fix.
public int TotalMonths
{
get
{
var d = TotalDays;
var found = false;
var m = 0;
while(!found)
{
foreach (var month in _months)
{
if (d > month.Value)
{
d -= month.Value;
m++;
}
else
{
found = true;
break;
}
}
}
return m;
}
}
which enables MonthOfYear:
public string MonthOfYear { get { return _months.Keys.ToList()[CurrentMonth]; } }
So far things seem to be in order. I suspect there's a more elegant solution, perhaps involving Yield (which I don't yet understand), so if anyone comes up with something interesting I'd love to hear about it.
If you don't have leap years, it's relatively easy:
You can work out how many ticks are in a year, and use the % operator to work out the tick within the year for a given value
Divide by the number of ticks in a day to get the day within the year
You could then have a statically-constructed array for the "start day-of-year of each month", do a binary search to find the right month, and then subtract the start day-of-year to find the day within the month. Alternatively, just iterate over the months and subtract the number of days in each month until you find your value is less than the number of days within the month you're considering.
One option: use Noda Time and implement your own CalendarSystem... which probably isn't too hard, except for understanding the system to start with :) (Bear in mind that Noda Time isn't production-quality yet, but you could have fun with it...)
I've not used it myself in the past, but the System.Globalization.Calendar class may help you.
It looks as though you can create your own "calendars" for use with DateTime etc. Their example implementations include things like "ChineseLunisolarCalendar, so perhaps instead of doing all the hard work yourself, you can create some sort of "calendar specification" so you can still use the built-in types?
this is how the DateTime does it when you look at the decompiled assembly:
private int GetDatePart(int part)
{
long internalTicks = this.InternalTicks;
int num1 = (int)internalTicks / 864000000000L;
int num2 = num1 / 146097;
num1 = num1 - (num2 * 146097);
int num3 = num1 / 36524;
if (num3 == 4)
{
num3 = 3;
}
num1 = num1 - (num3 * 36524);
int num4 = num1 / 1461;
num1 = num1 - (num4 * 1461);
int num5 = num1 / 365;
if (num5 == 4)
{
num5 = 3;
}
if (part == 0)
{
return ((((num2 * 400) + (num3 * 100)) + (num4 * 4)) + num5) + 1;
}
num1 = num1 - (num5 * 365);
if (part == 1)
{
return num1 + 1;
}
bool flag = num5 == 3 && ((num4 == 24 && num3 == 3) || !(num4 == 24));
int[] daysToMonth366 = DateTime.DaysToMonth366;
int num6 = num1 >> 6;
while (num1 >= daysToMonth366[num6])
{
num6++;
}
if (part == 2)
{
return num6;
}
return (num1 - daysToMonth366[(num6 - 1)]) + 1;
}
internal long InternalTicks
{
get
{
return this.dateData & 4611686018427387903L;
}
}
public int Day
{
get
{
return this.GetDatePart(3);
}
}

Ideas wanted for analyzing near-realtime data over specific intervals with memory/cpu efficiency

I have some environmental sensors and I want to detect sudden changes in temperature, and slow trends over time... however I'd like to do most of the math based on what's in memory with parameters that may look like this: (subject to change)
(note: Items in parens are computed in realtime as data is added)
5 minute (derivative, max, min, avg) + 36 datapoints for most current 3 hours
10 minute (derivative, max, min, avg) + 0 datapoints, calc is based on 5min sample
30 minute (derivative, max, min, avg) + 0 datapoints, calc is based on 5 min sample
Hourly (derivative, max, min, avg) + 24 datapoints for most current 1 day
Daily (derivative, max,min,avg) + 32 datapoints for most current month
Monthly (derivative, max,min,avg) + 12 datapoints for past year
Each datapoint is a two byte float. So each sensor will consume up to 124 Floats, plus the 24 calculated variables. I'd like to support as many sensors as the .NET embededd device will permit.
Since I'm using an embedded device for this project, my memory is constrained and so is my IO and CPU power.
How would you go about implementing this in .NET? So far, I've created a couple of structs and called it a "TrackableFloat" where the insertion of a value causes the old one to drop off the array and a recalculation is done.
The only thing that makes this more
complicated than it would be, is that
for any sensor does not report back
data, then that datapoint needs to be
excluded/ignored from all subsequent
realtime calulations.
When all is said and done, if any of the values: (derivative, max,min,avg) reach a pre defined setting, then a .NET event fires
I think someone out there will think this is an interesting problem, and would love to hear how they would approach implementing it.
Would you use a Class or a Struct?
How would you trigger the calculations? (Events most likely)
How would the alerts be triggered?
How would you store the data, in tiers?
Is there a library that already does something like this? (maybe that should be my first question )
How would you efficiently calculate the derivative?
Here is my first crack at this, and it doesn't completely hit the spec, but is very efficient. Would be interested in hearing your thoughts.
enum UnitToTrackEnum
{
Minute,
FiveMinute,
TenMinute,
FifteenMinute,
Hour,
Day,
Week,
Month,
unknown
}
class TrackableFloat
{
object Gate = new object();
UnitToTrackEnum trackingMode = UnitToTrackEnum.unknown;
int ValidFloats = 0;
float[] FloatsToTrack;
public TrackableFloat(int HistoryLength, UnitToTrackEnum unitToTrack)
{
if (unitToTrack == UnitToTrackEnum.unknown)
throw new InvalidOperationException("You must not have an unknown measure of time to track.");
FloatsToTrack = new float[HistoryLength];
foreach (var i in FloatsToTrack)
{
float[i] = float.MaxValue;
}
trackingMode = unitToTrack;
Min = float.MaxValue;
Max = float.MinValue;
Sum = 0;
}
public void Add(DateTime dt, float value)
{
int RoundedDTUnit = 0;
switch (trackingMode)
{
case UnitToTrackEnum.Minute:
{
RoundedDTUnit = dt.Minute;
break;
}
case UnitToTrackEnum.FiveMinute:
{
RoundedDTUnit = System.Math.Abs(dt.Minute / 5);
break;
}
case UnitToTrackEnum.TenMinute:
{
RoundedDTUnit = System.Math.Abs(dt.Minute / 10);
break;
}
case UnitToTrackEnum.FifteenMinute:
{
RoundedDTUnit = System.Math.Abs(dt.Minute / 15);
break;
}
case UnitToTrackEnum.Hour:
{
RoundedDTUnit = dt.Hour;
break;
}
case UnitToTrackEnum.Day:
{
RoundedDTUnit = dt.Day;
break;
}
case UnitToTrackEnum.Week:
{
//RoundedDTUnit = System.Math.Abs( );
break;
}
case UnitToTrackEnum.Month:
{
RoundedDTUnit = dt.Month;
break;
}
case UnitToTrackEnum.unknown:
{
throw new InvalidOperationException("You must not have an unknown measure of time to track.");
}
default:
break;
}
bool DoRefreshMaxMin = false;
if (FloatsToTrack.Length < RoundedDTUnit)
{
if (value == float.MaxValue || value == float.MinValue)
{
// If invalid data...
lock (Gate)
{
// Get rid of old data...
var OldValue = FloatsToTrack[RoundedDTUnit];
if (OldValue != float.MaxValue || OldValue != float.MinValue)
{
Sum -= OldValue;
ValidFloats--;
if (OldValue == Max || OldValue == Min)
DoRefreshMaxMin = true;
}
// Save new data
FloatsToTrack[RoundedDTUnit] = value;
}
}
else
{
lock (Gate)
{
// Get rid of old data...
var OldValue = FloatsToTrack[RoundedDTUnit];
if (OldValue != float.MaxValue || OldValue != float.MinValue)
{
Sum -= OldValue;
ValidFloats--;
}
// Save new data
FloatsToTrack[RoundedDTUnit] = value;
Sum += value;
ValidFloats++;
if (value < Min)
Min = value;
if (value > Max)
Max = value;
if (OldValue == Max || OldValue == Min)
DoRefreshMaxMin = true;
}
}
// Function is placed here to avoid a deadlock
if (DoRefreshMaxMin == true)
RefreshMaxMin();
}
else
{
throw new IndexOutOfRangeException("Index " + RoundedDTUnit + " is out of range for tracking mode: " + trackingMode.ToString());
}
}
public float Sum { get; set; }
public float Average
{
get
{
if (ValidFloats > 0)
return Sum / ValidFloats;
else
return float.MaxValue;
}
}
public float Min { get; set; }
public float Max { get; set; }
public float Derivative { get; set; }
public void RefreshCounters()
{
lock (Gate)
{
float sum = 0;
ValidFloats = 0;
Min = float.MaxValue;
Max = float.MinValue;
foreach (var i in FloatsToTrack)
{
if (i != float.MaxValue || i != float.MinValue)
{
if (Min == float.MaxValue)
{
Min = i;
Max = i;
}
sum += i;
ValidFloats++;
if (i < Min)
Min = i;
if (i > Max)
Max = i;
}
}
Sum = sum;
}
}
public void RefreshMaxMin()
{
if (ValidFloats > 0)
{
Min = float.MaxValue;
Max = float.MinValue;
lock (Gate)
{
foreach (var i in FloatsToTrack)
{
if (i != float.MaxValue || i != float.MinValue)
{
if (i < Min)
Min = i;
if (i > Max)
Max = i;
}
}
}
}
}
}
You should consider looking at a CEP library like Nesper.

How would I search a range of ranged values using C#

I have a list of values like this
1000, 20400
22200, 24444
The ranges don't overlap.
What I want to do is have a c# function that can store (loaded values from db then cache it locally) a relatively large list of these values then have a method for finding if a supplied value is in any of the ranges?
Does this make sense?
Need the quickest solution
You've specified values, but then talked about ranges.
For just values, I'd use a HashSet<int>. For ranges it gets more complicated... Let us know if that's actually what you're after and I'll think about it more. If they are ranges, do you have any extra information about them? Do you know if they'll overlap or not? Are you just interested in the existence of a range, or do you need to find all the ranges that a value belongs to?
EDIT: With the edits to the question, Barry's answer is exactly right. Just sort (by lower bound is good enough) at initialization time and then do a binary search to find the range containing the value, or the lack thereof.
EDIT: I've found the code below in my answer to a similar question recently.
The ranges will need to be sorted beforehand - List<Range>.Sort will work fine assuming you have no overlap.
public class Range : IComparable<Range>
{
private readonly int bottom; // Add properties for these if you want
private readonly int top;
public Range(int bottom, int top)
{
this.bottom = bottom;
this.top = top;
}
public int CompareTo(Range other)
{
if (bottom < other.bottom && top < other.top)
{
return -1;
}
if (bottom > other.bottom && top > other.top)
{
return 1;
}
if (bottom == other.bottom && top == other.top)
{
return 0;
}
throw new ArgumentException("Incomparable values (overlapping)");
}
/// <summary>
/// Returns 0 if value is in the specified range;
/// less than 0 if value is above the range;
/// greater than 0 if value is below the range.
/// </summary>
public int CompareTo(int value)
{
if (value < bottom)
{
return 1;
}
if (value > top)
{
return -1;
}
return 0;
}
}
// Just an existence search
public static bool BinarySearch(IList<Range> ranges, int value)
{
int min = 0;
int max = ranges.Count-1;
while (min <= max)
{
int mid = (min + max) / 2;
int comparison = ranges[mid].CompareTo(value);
if (comparison == 0)
{
return true;
}
if (comparison < 0)
{
min = mid+1;
}
else if (comparison > 0)
{
max = mid-1;
}
}
return false;
}
A binary search will do just fine. Keep the list of ranges in sorted order, making sure that none of them intersect (if they do, merge them). Then write a binary search which, rather than testing against a single value, tests against either end of the range when looking to choose above or below.
I'd try the simplest option first, and optimize if that doesn't meet your needs.
class Range {
int Lower { get; set; }
int Upper { get; set; }
}
List<Range>.FirstOrDefault(r => i >= r.Lower && i <= r.Upper);
As previously mentioned, if the set of ranges are big and non-overlapping, it is best to do a binary search. One way to do this is to use SortedDictionary, which implements a red-black tree to give a O(log(n)) search time. We can use the ranges as keys, and do a dictionary lookup by converting the single value we want to match into a range of a single point. If we implement the CompareTo method so that ranges that are overlapping are considered equal/matching, the dictionary lookup will find the matching range for use.
public struct Range : IComparable<Range>
{
public int From;
public int To;
public Range(int point)
{
From = point;
To = point;
}
public Range(int from, int to)
{
From = from;
To = to;
}
public int CompareTo(Range other)
{
// If the ranges are overlapping, they are considered equal/matching
if (From <= other.To && To >= other.From)
{
return 0;
}
// Since the ranges are not overlapping, we can compare either end
return From.CompareTo(other.From);
}
}
public class RangeDictionary
{
private static SortedDictionary<Range, string> _ranges = new SortedDictionary<Range, string>();
public RangeDictionary()
{
_ranges.Add(new Range(1, 1000), "Alice");
_ranges.Add(new Range(1001, 2000), "Bob");
_ranges.Add(new Range(2001, 3000), "Carol");
}
public string Lookup(int key)
{
/* We convert the value we want to lookup into a range,
* so it can be compared with the other ranges */
var keyAsRange = new Range(key);
string value;
if (_ranges.TryGetValue(keyAsRange, out value))
{
return value;
}
return null;
}
}
As an example, running the following code
var ranges = new RangeDictionary();
var value = ranges.Lookup(1356);
value will in this case contain the string "Bob", since 1356 matches the range 1001-2000.
In your case, if you are interested in fetching the range itself, you can use the range as both key and value in the dictionary. The example code can be easily extended to holding generic values instead.
As a sidenote, this trick can also be done using a SortedList with virtually the same code, which uses less memory (array instead of tree) but have slower insertion/deletion time for unsorted data. They both use the default comparator for the key type (or a specified one) to compare values. The normal C# Dictionary on the other hand uses GetHashCode and Equals to compare values.
class Ranges
{
int[] starts = new[] { 1000, 22200 };
int[] ends = new[] { 20400, 24444 };
public int RangeIndex(int test)
{
int index = -1;
if (test >= starts[0] && test <= ends[ends.Length - 1])
{
index = Array.BinarySearch(ends, test);
if (index <= 0)
{
index = ~index;
if (starts[index] > test) index = -1;
}
}
return index;
}
}
Obviously, how you instantiate the class is up to you. Maybe pass in a DataTable and construct the arrays from that.
Assuming your ranges don't overlap:
-> Put all your range numbers in an array.
-> Sort your array.
-> Also keep a HashSet for your startvalues.
-> Now do a binary search on your number. Two possiblities:
--> Array range left of (smaller then) your number is a start value: your
number is in range.
--> Array range left of (smaller then) your number is not a start value: your
number is not in range.
Is this functionally what you're after? If so, and you just want it to be more performant, than change the foreach in the ValueRangeCollection to a binary search..
public struct ValueRange
{
public int LowVal;
public int HiVal;
public bool Contains (int CandidateValue)
{ return CandidateValue >= LowVal && CandidateValue <= HiVal; }
public ValueRange(int loVal, int hiVal)
{
LowVal = loVal;
HiVal = hiVal;
}
}
public class ValueRangeCollection: SortedList<int, ValueRange>
{
public bool Contains(int candValue)
{
foreach ( ValueRange valRng in Values)
if (valRng.Contains(candValue)) return true;
return false;
}
public void Add(int loValue, int hiValue)
{
Add(loValue, new ValueRange(loValue, hiValue));
}
}
class Range
{
public int Start { get; set; }
public int End { get; set; }
static Dictionary<int, Range> values;
static int[] arrToBinarySearchIn;
public static void BuildRanges(IEnumerable<Range> ranges) {
values = new Dictionary<int, Range>();
foreach (var item in ranges)
values[item.Start] = item;
arrToBinarySearchIn = values.Keys.ToArray();
Array.Sort(arrToBinarySearchIn);
}
public static Range GetRange(int value)
{
int searchIndex = Array.BinarySearch(arrToBinarySearchIn, value);
if (searchIndex < 0)
searchIndex = ~searchIndex - 1;
if (searchIndex < 0)
return null;
Range proposedRange = values[arrToBinarySearchIn[searchIndex]];
if (proposedRange.End >= value)
return proposedRange;
return null;
}
}

Categories