Find nearest value in List array with linq?
I have a list like this:
public static List<int[]> list = new List<int[]>();
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.
source to share
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
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);
}
}
}
source to share
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.
source to share