查找包含最接近属性值的对象的 List 索引



如何找到包含最接近属性值的对象的 List 索引?

示例,类 MyData 包含一个属性 Position.class MyDataHandler 有一个 MyData 列表,位置为:1、3、14、15、22。

MyDataHandler 有一个名为 GetClosestIndexAt 的方法,如果输入值为 13,则该方法必须返回索引 2。

示例代码:

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

您可以使用 LINQ 执行此操作,如下所示:

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

这个想法是将位置与其在列表中的索引配对,按与特定位置的距离排序,抓取第一项,然后获取其索引。

您需要单独处理myDataList中没有元素的情况。这是关于 ideone 的演示。

使用重载的 Enumerable.Select 方法,该方法通过合并元素的索引将序列的每个元素投影到新形式中:

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();

MoreLinq的MinBy运算符的更好解决方案(可从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;
}

如果您不想使用库,您可以创建自己的 MinBy 扩展:

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;
    }
}
<</div> div class="one_answers">

正如我在评论中所说,我认为最有效的方法是避免仅仅为了获得第一个元素而对整个数据进行不必要的排序。我们可以通过搜索差异最小的元素来选择它,单独计算。它需要两次列表迭代,但没有排序。鉴于:

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;

你可以写:

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;

最新更新