Find the list index of the object containing the closest property value

advertisements

How can I find the List index of the object containing the closest property value?

Sample, class MyData contains a property Position. class MyDataHandler has a List of MyData and the positions are: 1, 3, 14, 15, 22.

MyDataHandler has a method called GetClosestIndexAt, If the input value is 13, the method must return index 2.

Sample code:

public class MyData
{
    public double Position { get; set; }
    public string Name { get; set; }
}

public class MyDataHandler
{
    private List<MyData> myDataList = new List<MyData>();

    public MyDataHandler()
    {
        FillMyData(myDataList);
    }

    public int GetClosestIndexAt(double position)
    {
        int index = -1;
        //How to get the index of the closest MyDataList.Position to position value.
        //index = ?????
        return index;
    }

    private void FillMyData(List<MyData> MyDataList)
    {
        //fill the data...
    }
}


Use overloaded Enumerable.Select method which projects each element of a sequence into a new form by incorporating the element's index:

myDataList.Select((d,i) => new { Position = d.Position, Index = i })
          .OrderBy(x => Math.Abs(x.Position - position))
          .Select(x => x.Index)
          .DefaultIfEmpty(-1) // return -1 if there is no data in myDataList
          .First();

Better solution with MinBy operator of MoreLinq (available from NuGet):

public int GetClosestIndexAt(double position)
{
    if (!myDataList.Any())
        return -1;

    return myDataList.Select((d,i) => new { Position = d.Position, Index = i })
          .MinBy(x => Math.Abs(x.Position - position))
          .Index;
}


You can create your own MinBy extension if you don't want to use library:

public static TSource MinBy<TSource, TKey>(
    this IEnumerable<TSource> source, Func<TSource, TKey> selector)
{
    using (IEnumerator<TSource> sourceIterator = source.GetEnumerator())
    {
        if (!sourceIterator.MoveNext())
            throw new InvalidOperationException("Empty sequence");

        var comparer = Comparer<TKey>.Default;
        TSource min = sourceIterator.Current;
        TKey minKey = selector(min);

        while (sourceIterator.MoveNext())
        {
            TSource current = sourceIterator.Current;
            TKey currentKey = selector(current);

            if (comparer.Compare(currentKey, minKey) >= 0)
                continue;

            min = current;
            minKey = currentKey;
        }

        return min;
    }
}