Select the maximal item from collection, by some c

2019-05-27 04:12发布

问题:

i am new to .net 3.5. I have a collection of items:

IList<Model> models;

where

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

I would like to get the element, which has the longest name's length. I tried

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

but it of course returns the maximum length (and I need a Model object). I know there is a way of doing this using the extension methods but I don't know how.

回答1:

This is how I got it to work. Maybe there's a better way, I'm not sure:

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


回答2:

There isn't a built-in way of doing this, unfortunately - but it's really easy to write an extension method to do it.

It was in one of my very first blog posts, in fact... note that there's a better implementation in one of the comments. I'll move it into the body if I get time.

EDIT: Okay, I have a slightly abbreviated version - it just returns the maximal element, using the given selector. No need to do a projection as well - do that once afterwards if you need to. Note that you could remove the constraint on TValue and use Comparer<TValue>.Default instead, or have an overload which allows the comparison to be specified as another parameter.

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

Sample use: (note type inference):

string maxName = models.MaxBy(model => model.Name.Length).Name;


回答3:

Here's another way of doing it. There's a version of Max that takes no criterion, and uses IComparable. So we could provide a way to wrap anything in a comparable object, with a delegate providing the comparison.

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

Then we can say:

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

This involves a lot of extra allocation, but it's sort of academically interesting (I think).



回答4:

You can use Aggregate. It can be done without writing new extension method.

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


回答5:

Is there anything gained by using the extension methods?

Perhaps a method or procedure with a simple iteration of the list would suffice?

Something to the effect of

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


回答6:

Another way might be:

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

First one will be the one with the longest length.