1

I have an IEnumerable<float> containing distinct values found in a three dimensional array.

Given a test value, I want to take two elements from my distinct IEnumerable, the closest value which is greater than or equal to the test value, and the closest value which is less than the test.

In other words, if my test value is 80.5, and my list contains:

  • 1.0
  • 1.65
  • 2.345
  • 99.439

Then I want an IEnumerable<float> or a Tuple<float,float> back which contains 2.345 and 99.439.

Is there a LINQ statement or combination of such which will do that? An approach without LINQ?

Rob Perkins
  • 3,088
  • 1
  • 30
  • 51

5 Answers5

4

Without using LINQ and assuming that there are only values > 0 in an input collection. There's no need to sort the collection first.

public Tuple<float, float> GetClosestValues(IEnumerable<float> values, float target)
{
    float lower = 0;
    float upper = Single.MaxValue;
    foreach (var v in values)
    {
        if (v < target && v > lower) lower = v;
        if (v > target && v < upper) upper = v;
    }

    return Tuple.Create(lower, upper);
}
Ondrej Janacek
  • 12,486
  • 14
  • 59
  • 93
  • 1
    If all values are greater than zero that upper will never change – Selman Genç Feb 19 '14 at 19:38
  • If I'm reading this correctly, then the values in the list don't even have to be run through `Distinct` in order for this to work correctly, right? – Rob Perkins Feb 19 '14 at 21:10
  • This is by far the fastest based on the timing tests I'm doing today, which is a surprising result to me. – Rob Perkins Feb 19 '14 at 22:31
  • @RobPerkins - makes sense to me - it's one pass instead of two or more for most other solutions. – D Stanley Feb 19 '14 at 22:37
  • @OndrejJanacek, my thanks to you. This is simpler to read and faster than a parallelized LINQ query. – Rob Perkins Feb 19 '14 at 23:38
  • @RobPerkins Athough it does not answer your question about existence of a LINQ statement, I'm glad you have not gone crazy about LINQ and used a reasonable (and in my opinion more readable) solution. – Ondrej Janacek Feb 20 '14 at 07:37
  • There is irony; I ended up using an algorithm that doesn't require it! But, a question like this belongs on the SO site. Maybe I should post the timings as comments under each correct solution... (after further inspection, this and the `Aggregate` approach were similar in timing. All the others were slower.) – Rob Perkins Feb 20 '14 at 15:40
  • @RobPerkins You can and you don't have to :) Whoever will seek help from this question will likely also test it or will not be interested at all. – Ondrej Janacek Feb 20 '14 at 17:30
2

In a tuple:

var t = Tuple.Create(list.Where(x => x <= value).Max(),
                     list.Where(x => x >= value).Min()
                    );

Although you don't state what the output should be if the value is in the list - in this case it would be a tuple with the same value for both "nodes"

D Stanley
  • 149,601
  • 11
  • 178
  • 240
2
Tuple.Create(
    values.OrderBy(i => i)
          .SkipWhile(i => i < test)
          .FirstOrDefault(),
    values.OrderByDescending(i => i)
          .SkipWhile(i => i >= test)
          .FirstOrDefault());
  1. Sort (ascending), skip all values less than test, take the first value greater than or equal to test.
  2. Sort (descending), skip all values greater than or equal to test, take the first value less than test.
dcastro
  • 66,540
  • 21
  • 145
  • 155
1
double[] data = { .1,5.34,3.0,5.6 };
double test = 4.0;

var result = data.Aggregate(Tuple.Create(double.MinValue, double.MaxValue), 
(minMax, x) => Tuple.Create(
    x < test && x > minMax.Item1 ? x : minMax.Item1,
    x >= test && x < minMax.Item2 ? x : minMax.Item2));
Lorentz Vedeler
  • 5,101
  • 2
  • 29
  • 40
0

Assuming your list is sorted (and few other assumptions on the requirements (such as what happens if there is a direct hit).

float prev=0;
foreach(float item in YourIEnumerableFloatVar)
{
   if (item > target)
   {
       return new Tuple<float, float> (prev, item);
   }

   prev = item;
}
LB2
  • 4,802
  • 19
  • 35