Find nearest value in List array with linq?

I have a list like this:

    public static List<int[]> list = new List<int[]>();

      

enter image description here

In addition, I also have a variable named X. X can take on any value. I want to find the nearest and smaller value of X in list[?][1]

. For example:

If X is 1300, I want to take the index of the list: 1. Or, if X is 700, I want to take the index: 0. How can I do this via linq? Or, is there any other solution?

Thanks in advance.

+3


source to share


3 answers


You can do it like this (the snippet assumes this list is not empty)



var x = 700;
var result = list.Select((subList, idx) => new { Value = subList[1], Idx = idx })
                 .Where(elem => elem.Value < x)
                 .Select(elem => new { Diff = Math.Abs(x - elem.Value), elem.Idx })
                 .OrderBy(elem => elem.Diff).FirstOrDefault();

if (result != null)
{
    return result.Idx;
}

// case - there is no such index

      

+4


source


I know you asked for a Linq solution, but I think a non Linq solution is also good.

If you're interested in a non-Linq solution, here's one (it uses Linq in one place, but it actually stretches the point!).

The main method of interest FindClosestSmaller()

returns Tuple

, where .Item1

is the index of the outer list that contains the closest value that is less than or equal to the target value, as .Item2

well as the index of that match in the inner array.



If a value less than or equal to the target value is not found .Item1

and .Item2

will be zero.

Note that it FindClosestSmaller()

takes a type parameter IEnumerable<IEnumerable<int>>

, which means you can use it for most collection types, and you're not limited to, say List<int[]>

.

using System;
using System.Collections.Generic;
using System.Linq;

namespace Demo
{
    public static class Program
    {
        private static void Main()
        {
            var ints1 = new [] { 1,  480,  749, 270 };
            var ints2 = new [] { 1,  810, 1080, 271 };
            var ints3 = new [] { 1, 7680, 7949, 271 };

            var intLists = new List<int[]> {ints1, ints2, ints3};

            test(intLists, 1300);
            test(intLists,  700);
            test(intLists,  480);
            test(intLists,    0);
        }

        private static void test(List<int[]> values, int target)
        {
            var result = FindClosestSmaller(values, target);
            Console.WriteLine("Target {0} found: Outer index = {1}, Inner index = {2}", target, result.Item1, result.Item2);
        }

        public static Tuple<int, int> FindClosestSmaller(IEnumerable<IEnumerable<int>> sequences, int target)
        {
            int closest = int.MaxValue;

            int closestInner = 0; // Setting these to zero means we take the first element of the
            int closestOuter = 0; // first list if no smaller element is found.

            int outer = 0;

            foreach (var sequence in sequences)
            {
                int inner = 0;

                foreach (int distance in sequence.Select(value => target - value))
                {
                    if ((distance >= 0) && (distance < closest))
                    {
                        closest      = distance;
                        closestInner = inner;
                        closestOuter = outer;
                    }

                    ++inner;
                }

                ++outer;
            }

            return new Tuple<int, int>(closestOuter, closestInner);
        }
    }
}

      

+1


source


You can start by aligning items to a new anonymous type, where index

is the index in the outer array and item is the value in the inner array:

Suppose the input and the desired target is:

var target = 20;
var input = (new int[][]{new int[]{1,2,3}, new int[]{4,7,8}, new int[]{5,4}});

      

Then the smoothing will be

var tmp = input.SelectMany((x, y) => x.Select(item => 
         new {index = y, item = item, delta = Math.Abs(target - item)}));

      

Now you can find the optimal delta:

var bestDelta = tmp.Min(x => x.delta);

      

And from here it is easy to find the best match:

var result = tmp.FirstOrDefault(x => x.delta == bestDelta);

      

Or, if you prefer to just get the index:

var index = tmp.Where(x => x.delta == bestDelta).Select(x => x.index).First();

      

This could be rewritten in the oneliner:

var result = input.SelectMany((x, y) => 
     x.Select(item => new {index = y, item = item, delta = Math.Abs(target - item)}))
     .OrderBy(x => x.delta).Select(x => x.index).First();

      

But I tend to find another solution more readable.

0


source







All Articles