2014-03-31 2 views
0

Я пишу запись для конкурса AI на C#, и я ищу более элегантный способ поиска предметов. (Я больше знаком со встроенным программированием на С, но я предпочитаю C# для конкурса ИИ.)Найти пункт в Enumerable с максимальным значением свойства

Сервер конкурса использует dmcs для компиляции записей .Net framework 4.0; Я использую Visual Studio Express 2013 для тестирования.

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

Вот мой исходный код, который делает то, что я хочу с помощью цикла Еогеаспа:

List<Region> myList = new List<Region>(); 

// ... 
// myList gets populated with elements 
// ... 

Region biggest = null; 
int biggestSize = -1; 

foreach (Region r in myList) 
{ 
    // We only want elements that are eligible for expansion 
    if (r.EligibleForExpansion()) 
    { 
     if (r.Size > biggestSize) 
     { 
      biggest = r; 
      biggestSize = r.Size; 
     } 
    } 
} 

return biggest; // I want the biggest Region, not the Size of the biggest region. 

Я пытаюсь найти более изящный способ сделать это, так что я не Еогеасп петлей по всему моему коду , Я попытался это:

return myList.Max(delegate(Region r) { if (r.EligibleForExpansion()) return r.Size; else return -1; }); 

Однако, которая возвращает значение размера самого большого региона, а не самый крупный сам регион (который является то, что мне нужно).

Я знаю, что мой код foreach вернет null, если ни один регион не удовлетворяет требованию, в то время как Max code даст -1 (или любой регион, который не соответствует требованию); Я могу иметь дело с любым способом.

Я не думаю, что могу просто сделать регион несопоставимым; У меня много поисков объектов Region, и мне нужно сортировать по разным параметрам в разное время, поэтому функция сравнения будет отличаться в разных поисках.

Я мог бы просто обернуть свой код foreach в статической функции и назвать это там, где мне нужно искать, но похоже, что в C# должен быть более элегантный способ сделать это.

ответ

4

Использование MaxBy from moreLINQ library:

public static TSource MaxBy<TSource, TKey>(this IEnumerable<TSource> source, 
    Func<TSource, TKey> selector) 
{ 
    return source.MaxBy(selector, Comparer<TKey>.Default); 
} 

public static TSource MaxBy<TSource, TKey>(this IEnumerable<TSource> source, 
    Func<TSource, TKey> selector, IComparer<TKey> comparer) 
{ 
    if (source == null) throw new ArgumentNullException("source"); 
    if (selector == null) throw new ArgumentNullException("selector"); 
    if (comparer == null) throw new ArgumentNullException("comparer"); 
    using (var sourceIterator = source.GetEnumerator()) 
    { 
     if (!sourceIterator.MoveNext()) 
     { 
      throw new InvalidOperationException("Sequence contains no elements"); 
     } 
     var max = sourceIterator.Current; 
     var maxKey = selector(max); 
     while (sourceIterator.MoveNext()) 
     { 
      var candidate = sourceIterator.Current; 
      var candidateProjected = selector(candidate); 
      if (comparer.Compare(candidateProjected, maxKey) > 0) 
      { 
       max = candidate; 
       maxKey = candidateProjected; 
      } 
     } 
     return max; 
    } 
} 

так:

var item = myList.Where(x => x.EligibleForExpansion()) 
       .MaxBy(x => x.Size); 
2

Как насчет этого?

myList.Where(r => r.EligibleForExpansion).OrderBy(r => r.Size).LastOrDefault() 
+1

Сортировка _O (n \ * logn) _, а сама проблема является линейной. – MarcinJuraszek

+0

Это правда, это неэффективное решение. Его единственным преимуществом является лаконичность, которая является маргинальной, поскольку morelinq можно просто импортировать и забыть. – Blorgbeard

1

Вы можете использовать Aggregate из коробки для этой цели:

 var item = myList 
      .Where(r => r.EligibleForExpansion()) 
      .Aggregate((Region)null, (max, cur) => (max == null ? cur : cur.Size > max.Size ? cur : max)); 

Если Region были типа значения (что это не так) вы можете обернуть начальное значение в обнуляемом, и получить нулевое значение для пустого списка:

 var item = myList 
      .Where(r => r.EligibleForExpansion()) 
      .Aggregate((Region?)null, (max, cur) => (max == null ? cur : cur.Size > max.Value.Size ? cur : max));