2014-01-12 3 views
3

Как найти индекс списка объекта, содержащий самое близкое значение свойства?Найти индекс списка объекта, содержащего самое близкое значение свойства

Образец, класс MyData содержит собственность Позиция. Класс 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... 
    } 
} 

ответ

1

Использование перегружен 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(); 

Лучшее решение с MinBy оператора MoreLinq (доступный от 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; 
    } 
} 
+0

Почему использование MinBy - лучшее решение? Спасибо за ответ. – Pedro77

+0

@ Pedro77 Расширение MinBy находит ответ в линейном времени, а 'OrderBy' требует O (N * LogN) времени. Когда вход очень большой (много тысяч элементов), эта разница может стать проблемой, особенно если вы несколько раз вызываете 'GetClosestIndexAt'. – dasblinkenlight

+0

@ Pedro77 OrderBy перечисляет источник и создает упорядоченную копию (т.он сначала сортирует все предметы и сохраняет их, и только затем возвращает min item для вас). MinBy, с другой стороны, хранит только один элемент, который намного лучше с точки зрения производительности (я считаю его читаемым). А также dasblinkenlight правильно указал на использование времени –

2

Вы можете сделать это с помощью 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 

идея заключается в том, чтобы соединить позицию со своим индексом в списке, заказ на расстояние фр om конкретную позицию, захватить первый элемент и получить его индекс.

Вам нужно иметь дело с ситуацией, когда в myDataList нет элементов отдельно. Вот demo on ideone.

+0

+1 из-за использования 'Min', чтобы избежать возможно дорогой сортировки с' OrderBy'. –

+0

Я отредактировал свой комментарий соответственно! –

+0

@ KonradKokosa 'Min' даст вам минимальное значение расстояния, которое является' double'. Вы не можете использовать его здесь. См. Мой ответ. Неверное текущее решение Btw. Двойной не имеет свойства 'Index' –

1

Как я уже сказал в комментариях, я считаю, что наиболее эффективным способом является избежать ненужной сортировки целых данных, чтобы получить первый элемент. Мы можем просто выбрать его, выполнив поиск элемента с минимальной разницей, рассчитанного отдельно. Для этого требуется две итерации списка, но не сортировка. Дано:

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; 
+0

Очень приятно, спасибо! – Pedro77

Смежные вопросы