Pedro77
Pedro77

Reputation: 5294

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

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...
    }
}

Upvotes: 3

Views: 1851

Answers (3)

Konrad Kokosa
Konrad Kokosa

Reputation: 16898

As I said in the comments, I believe the most efficient way is to avoid unnecessary sorting of whole data just to get the first element. We can just select it by searching for the element with minimum difference, calculated separately. It requires two list iteration but no sorting. Given:

var myDataList = new List<MyData>()
    {
        new MyData() { Name = "Name1", Position = 1.0 },
        new MyData() { Name = "Name3", Position = 3.0 },
        new MyData() { Name = "Name14", Position = 14.0 },
        new MyData() { Name = "Name15", Position = 15.0 },
        new MyData() { Name = "Name22", Position = 22.0 },
    };
double position = 13.0;

you can write:

var result =
    myDataList.Select((md, index) => new
    {
        Index = index,
        Diff = Math.Abs(md.Position - position)
    })
    .Where(a => a.Diff == myDataList.Min(md => Math.Abs(md.Position - position)))
    .First()
    .Index;

Upvotes: 1

Sergey Kalinichenko
Sergey Kalinichenko

Reputation: 726839

You can do it using LINQ, like this:

var res = myDataList
    .Select((v, i) => new {Position = v.Position, Index = i}) // Pair up the position and the index
    .OrderBy(p => Math.Abs(p.Position - position))            // Order by the distance
    .First().Index;                                           // Grab the index of the first item

The idea is to pair the position with its index in the list, order by the distance from the specific position, grab the first item, and get its index.

You need to deal with the situation when there's no elements in myDataList separately. Here is a demo on ideone.

Upvotes: 2

Sergey Berezovskiy
Sergey Berezovskiy

Reputation: 236268

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;
    }
}

Upvotes: 1

Related Questions