Вернуть наиболее подходящий элемент из коллекции в C # 3.5 всего за одну или две строки - PullRequest
8 голосов
/ 22 марта 2012

Вот пример кода, который я написал в своей жизни тысячи раз:

// find bestest thingy
Thing bestThing;
float bestGoodness = FLOAT_MIN;
foreach( Thing x in arrayOfThings )
{
  float goodness = somefunction( x.property, localvariable );
  if( goodness > bestGoodness )
  {
    bestGoodness = goodness;
    bestThing = x;
  }
}
return bestThing;

И мне кажется, что в C # уже должно быть что-то, что делает это всего лишь строкой.Что-то вроде:

return arrayOfThings.Max( delegate(x)
  { return somefunction( x.property, localvariable ); });

Но это не возвращает вещь (или индекс для вещи, которая была бы хороша), которая возвращает значение качества соответствия.

Так что может быть что-то вроде:

var sortedByGoodness = from x in arrayOfThings 
  orderby somefunction( x.property, localvariable ) ascending 
  select x;
return x.first;

Но это делает весь массив целиком и может быть слишком медленным.

Это существует?

Ответы [ 4 ]

3 голосов
/ 22 марта 2012

Это то, что вы можете сделать с помощью System.Linq:

var value = arrayOfThings
    .OrderByDescending(x => somefunction(x.property, localvariable))
    .First();

Если массив может быть пустым, используйте .FirstOrDefault();, чтобы избежать исключений.

Вы действительно не знаетекак это реализовано внутри, так что вы не можете быть уверены, что это отсортирует весь массив, чтобы получить первый элемент.Например, если бы это было linq to sql, сервер получил бы запрос, включающий сортировку и условие.Он не получит массив, затем отсортирует его, затем получит первый элемент.

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

var sortedValues =arrayOfThings
  .OrderByDescending(x => somefunction(x.property, localvariable));
// values isn't still evaluated
var value = sortedvalues.First();
// the whole expression is evaluated at this point.
2 голосов
/ 22 марта 2012

Я не думаю, что это возможно в стандартном LINQ без сортировки enuermable (что в общем случае медленно), но вы можете использовать метод MaxBy() из библиотеки MoreLinq для достижения этой цели. Я всегда включаю эту библиотеку в свои проекты, так как она очень полезна.

http://code.google.com/p/morelinq/source/browse/trunk/MoreLinq/MaxBy.cs

(Код на самом деле выглядит очень похоже на то, что у вас есть, но обобщенный.)

1 голос
/ 22 марта 2012

Я бы реализовал IComparable<Thing> и просто использовал arrayOfThings.Max().

Пример здесь: http://msdn.microsoft.com/en-us/library/bb347632.aspx

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

ОБНОВЛЕНИЕ

Существует также перегруженный метод Max, который принимает функцию проекции, поэтому вы можете предоставить другую логику для получения роста, возраста и т. Д.

http://msdn.microsoft.com/en-us/library/bb534962.aspx

0 голосов
/ 22 марта 2012

Я перешел по ссылке Porges, указанной в комментарии, Как использовать LINQ для выбора объекта с минимальным или максимальным значением свойства и запустил следующий код в LINQPad и проверил, что оба LINQ выражения вернули правильные ответы.


void Main()
{
    var things = new Thing [] {
        new Thing { Value = 100 },
        new Thing { Value = 22 },
        new Thing { Value = 10 },
        new Thing { Value = 303 },
        new Thing { Value = 223}
    };

    var query1 = (from t in things
                orderby GetGoodness(t) descending 
                select t).First();

    var query2 = things.Aggregate((curMax, x) => 
        (curMax == null || (GetGoodness(x) > GetGoodness(curMax)) ? x : curMax));   
}

int GetGoodness(Thing thing)
{
    return thing.Value * 2;
}

public class Thing 
{
    public int Value {get; set;}
}


Результат от LinqPad

Results from LinqPad

...