5

I'm trying to come up with the best way of doing some kind of switch style selection on a double to find out what range it is in. Something like this:

double a = 1.1;
switch(a)
{
case: (0.0, 1.6)
 return 1;
case: (1.6, 2.337)
 return 2;
case: (2.337, 3.2974)
 return 3;
default:
  return -1;
}

Obviously in this example, one value in the range would have to be non-inclusive, but you get my drift. Any Ideas?

Edit, the ranges are not necessarily integral.

EDIT 2: I'm actually dealing with radians and finding out which of 12 ranges a point is in. I ended up doing this:

double pi = Math.PI;
double[] zones = new double[] {
        0, pi/6, pi/3, pi/2,
        2*pi/3, 5*pi/6, pi, 7*pi/6,
        4*pi/3, 3*pi/2, 5*pi/3, 11*pi/6
    };

for (int i = 0; i < 11; i++)
{
    if (radians > zones[i] && radians <= zones[i + 1])
    {
        return i + 1;
    }
}

I started to do an binary search type if-else, but it was going to get too messy.

10
  • And what is the problem with multiple what ifs? Commented Mar 1, 2012 at 16:13
  • One obvious pitfall is that you're planning to perform a comparison for equality on a digital representation of floating-point numbers. Commented Mar 1, 2012 at 16:13
  • 1
    Are the intervals always the same size? Is there ever a situation where the intervals overlap? Commented Mar 1, 2012 at 16:17
  • 5
    If that's what you're doing then why not simply multiply the number by six, divide it by pi, and round off to an integer appropriately? Commented Mar 1, 2012 at 16:55
  • 1
    Once again illustrating a point I make frequently: try asking the question you actually need answered rather than asking a question about your assumed solution. By asking a question about evaluating ranges you might have missed out on a solution that doesn't involve ranges at all. Commented Mar 1, 2012 at 19:15

5 Answers 5

8

The following is ideally suited to adjacent ranges, since you only need to write the range limits once:

double a = 1.1;

if (a < 0.0) {
    // Too low
    return -1;
} else if (a < 1.6) {
    // Range [0.0, 1.6)
    return 1;
} else if (a < 2.337) {
    // Range [1.6, 2.337)
    return 2;
} else if (a < 3.2974) {
    // Range [2.337, 3.2974)
    return 3;
} else {
    // Too high
    return -1;
}
Sign up to request clarification or add additional context in comments.

3 Comments

+1 If the solution needs to be scalable, this approach could be modified to use a binary search, by storing the range definition in an array. Then you'd have O(log n) rather than O(n).
Indeed, I started coding my if-then as such but realized it would get messy.
Of course, I have answered according to the scale suggested by the question. This style should be fine for a handful of ranges. Anything more and you would be better off with a data-driven refactoring (allowing, for example, for the binary search suggested by @phoog)
3

This is probably over engineering but you could create a Range Class with Min and Max values and a Callback Function.

Then just create all the Ranges with respective min, and max values and callback and add them to an IEnumerable.

Use LINQ to find out the correct range:

range = Ranges.First(r => r.MinValue <= value and r.MaxValue > value);

Then just call the range callback.

Comments

1

Just round up

double a = 1.1;
if(d < 0.0)
  return -1;
else
  return (int)Math.Ceiling(a);

1 Comment

Ok, that changes the scenario a bit. I just assumed they were. But it seems Daniel has a nice solution.
1

Perhaps this will do what you want to do with a nice syntactic structure. Please adjust visibilities and comparisons according to your purposes:

public static class Range
{
    public interface ISwitchable
    {
        void SetDefault(Action defaultStatement);
        void Execute();
    }

    public interface ISwitchable<T>: ISwitchable
        where T: IComparable<T>
    {
        T Value { get; set; }
        void AddCase(ICase<T> caseStatement);
    }

    public class Switchable<T> : ISwitchable<T>
        where T: IComparable<T>
    {
        private readonly IList<ICase<T>> _caseStatements = new List<ICase<T>>();
        private Action _defaultStatement;

        #region ISwitchable<T> Members

        public T Value { get; set; }

        public void AddCase(ICase<T> caseStatement)
        {
            _caseStatements.Add(caseStatement);
        }

        public void SetDefault(Action defaultStatement)
        {
            _defaultStatement = defaultStatement;
        }

        public void Execute()
        {
            foreach (var caseStatement in _caseStatements)
                if ((caseStatement.Min.CompareTo(Value) <= 0) && (caseStatement.Max.CompareTo(Value) > 0))
                {
                    caseStatement.Action();
                    return;
                }

            _defaultStatement();
        }

        #endregion
    }

    public interface ICase<T>
        where T: IComparable<T>
    {
        T Min { get; set; }
        T Max { get; set; }

        Action Action { get; set; }
    }

    public sealed class Case<T>: ICase<T>
        where T: IComparable<T>
    {
        #region ICase<T> Members

        public T Min { get; set; }

        public T Max { get; set; }

        public Action Action { get; set; }

        #endregion
    }

    public static ISwitchable<T> Switch<T>(T value)
        where T: IComparable<T>
    {
        return new Switchable<T>();
    }
}

public static class SwitchableExtensions
{
    public static Range.ISwitchable<T> Case<T>(this Range.ISwitchable<T> switchable, T min, T max, Action action)
        where T: IComparable<T>
    {
        switchable.AddCase(new Range.Case<T>{ Min = min, Max = max, Action = action });
        return switchable;
    }
    public static void Default(this Range.ISwitchable switchable, Action action)
    {
        switchable.SetDefault(action);
        switchable.Execute();
    }
}

Usage:

class Program
{
    static void Main(string[] args)
    {
        Range.Switch(10)
            .Case(0, 3, () => Console.WriteLine("Between 0 and 3"))
            .Case(3, 7, () => Console.WriteLine("Between 3 and 7"))
            .Default(() => Console.WriteLine("Something else"));
    }
}

Comments

0

You can't do that with a switch.

if(a >= 0 && a < 1) return 1;
else if(a < 2) return 2;

etc.

Alternatively, you could write up a class to hold minval, maxval, and outputval, then just iterate over those possibilities and return the outputval that matches.

4 Comments

No, I mean your test ranges overlap. a==1 satisfies both your statements.
And now it returns 2 for a < 0 :)
No. Nothing you can do after those two lines will negate the fact that a < 0 will yield 2. You either need to make it else if (a > 1 && a < 2) return 2; or just get rid of a < 0 at the top, like I do :)
I get that, I was just saying "use this paradigm" rather than "this is the exact set of code you need"

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.