Выберите максимальный элемент из коллекции по некоторому критерию - PullRequest
2 голосов
/ 21 декабря 2008

Я новичок в .net 3.5. У меня есть коллекция предметов:

IList<Model> models;

, где

class Model
{
    public string Name
    {
       get;
       private set;
    }
}

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

string maxItem = models.Max<Model>(model => model.Name.Length);

но, конечно, возвращает максимальную длину (и мне нужен Model объект). Я знаю, что есть способ сделать это, используя методы расширения, но я не знаю как.

Ответы [ 6 ]

7 голосов
/ 21 декабря 2008

К сожалению, нет встроенного способа сделать это, но действительно легко написать метод расширения для этого.

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

РЕДАКТИРОВАТЬ: Хорошо, у меня есть несколько сокращенная версия - он просто возвращает максимальный элемент, используя данный селектор. Также не нужно делать прогноз - делайте это один раз после этого, если вам нужно. Обратите внимание, что вы можете удалить ограничение на TValue и использовать вместо него Comparer<TValue>.Default, либо иметь перегрузку, которая позволяет указать сравнение в качестве другого параметра.

public static TSource MaxBy<TSource, TValue>(this IEnumerable<TSource> source,
                                             Func<TSource, TValue> selector)
    where TValue : IComparable<TValue>
{
    TValue maxValue = default(TValue);
    TSource maxElement = default(TSource);
    bool gotAny = false;

    foreach (TSource sourceValue in source)
    {
        TValue value = selector(sourceValue);
        if (!gotAny || value.CompareTo(maxValue) > 0)
        {
            maxValue = value;
            maxElement = sourceValue;
            gotAny = true;
        }
    }
    if (!gotAny)
    {
        throw new InvalidOperationException("source is empty");
    }
    return maxElement;
}

Пример использования: (вывод типа примечания):

string maxName = models.MaxBy(model => model.Name.Length).Name;
1 голос
/ 21 декабря 2008

Вот еще один способ сделать это. Есть версия Max, которая не принимает критерий и использует IComparable. Таким образом, мы могли бы предоставить способ обернуть что-либо в сопоставимый объект с делегатом, обеспечивающим сравнение.

public class Comparable<T> : IComparable<Comparable<T>>
{
    private readonly T _value;
    private readonly Func<T, T, int> _compare;

    public Comparable(T v, Func<T, T, int> compare)
    {
        _value = v;
        _compare = compare;
    }

    public T Value { get { return _value; } }

    public int CompareTo(Comparable<T> other)
    {
        return _compare(_value, other._value);
    }
}

Тогда мы можем сказать:

Model maxModel = models.Select(m => new Comparable<Model>(m, (a, b) => a.Name.Length - b.Name.Length)).Max().Value;

Это требует много дополнительных ресурсов, но это своего рода академически интересно (я думаю).

0 голосов
/ 21 декабря 2008

Вы можете использовать Aggregate. Это можно сделать без написания нового метода расширения.

models.Aggregate(
                new KeyValuePair<Model, int>(),
                (a, b) => (a.Value < b.Name.Length) ? new KeyValuePair<Model, int>(b, b.Name.Length) : a,
                a => a.Key);
0 голосов
/ 21 декабря 2008

Другой способ может быть:

var item = (from m in models select m orderby m.Name.Length descending).FirstOrDefault(); 

Первым будет самый длинный.

0 голосов
/ 21 декабря 2008

Есть ли что-нибудь полезное с помощью методов расширения?

Может быть, достаточно метода или процедуры с простой итерацией списка?

Что-то с эффектом

Dim result as string = models(0).Name
for each m as Model in models
  if m.Name.length > result.length then
    result = m.Name
  end if
next
0 голосов
/ 21 декабря 2008

Вот как я заставил это работать. Может быть, есть лучший способ, я не уверен:

    decimal de = d.Max(p => p.Name.Length);
    Model a = d.First(p => p.Name.Length == de);
...