web-dev-qa-db-ja.com

LINQ:コレクション内のすべてのオブジェクトのプロパティに対して.Max()を実行して最大値のオブジェクトを返す方法

2つのintプロパティを持つオブジェクトのリストがあります。リストは別のlinqクエリの出力です。オブジェクト:

public class DimensionPair  
{
    public int Height { get; set; }
    public int Width { get; set; }
}

最大のHeightプロパティ値を持つオブジェクトをリストから見つけて返したい。

Height値の最大値を取得することはできますが、オブジェクト自体は取得できません。

Linqでこれをすることができますか?どうやって?

251
theringostarrs

これを MoreLINQ で正確に行うための 拡張メソッド があります。ここで実装を見ることができますが、基本的にはこれまで見てきた最大要素とそれが射影の下で生成した最大値を思い出しながら、データを反復処理するケースです。

あなたの場合は、次のようにします。

var item = items.MaxBy(x => x.Height);

これは、Mehrdadの2番目の解決策(基本的にはMaxByと同じ)を除いて、ここに示されているどの解決策よりも優れています(IMO)。

  • これはO(n)で、前回の 以前に受け入れられた回答 とは異なり、最大の値が反復ごとに検出されます(O(n ^ 2) ))
  • 順序付け解はO(n log n)です。
  • Max値を取ってからその値を持つ最初の要素を見つけることはO(n)ですが、シーケンスを2回繰り返します。可能であれば、LINQはシングルパス方式で使用してください。
  • 集約バージョンよりも読みやすく理解しやすいもので、要素ごとに1回だけ投影を評価します。
251
Jon Skeet

これにはソート(O(nlogn))が必要ですが、非常に単純で柔軟です。もう1つの利点は、LINQ to SQLで使用できることです。

var maxObject = list.OrderByDescending(item => item.Height).First();

これにはlistシーケンスを一度だけ列挙するという利点があります。 listがその間に変更されないList<T>であるかどうかは問題ではないかもしれませんが、任意のIEnumerable<T>オブジェクトには問題になる可能性があります。シーケンスが異なる列挙で変更されないことを保証するものは何もないので、それを複数回実行しているメソッドが危険になる可能性があります(シーケンスの性質によっては非効率的です)。しかし、それでも大規模なシーケンスには理想的とは言えません。ソートやその他のことをまったく行わずに1回のパスでできるようにするために多数の項目がある場合は、手動で独自のMaxObject拡張子を書くことをお勧めします。

static class EnumerableExtensions {
    public static T MaxObject<T,U>(this IEnumerable<T> source, Func<T,U> selector)
      where U : IComparable<U> {
       if (source == null) throw new ArgumentNullException("source");
       bool first = true;
       T maxObj = default(T);
       U maxKey = default(U);
       foreach (var item in source) {
           if (first) {
                maxObj = item;
                maxKey = selector(maxObj);
                first = false;
           } else {
                U currentKey = selector(item);
                if (currentKey.CompareTo(maxKey) > 0) {
                    maxKey = currentKey;
                    maxObj = item;
                }
           }
       }
       if (first) throw new InvalidOperationException("Sequence is empty.");
       return maxObj;
    }
}

と一緒に使用します。

var maxObject = list.MaxObject(item => item.Height);
182
Mehrdad Afshari

最初の商品を注文してから選択するのは、最初の商品の後に商品を注文するのに非常に時間がかかります。あなたはそれらの順序を気にしません。

代わりに、集約関数を使用して、探しているものに基づいて最適なアイテムを選択できます。

var maxHeight = dimensions
    .Aggregate((agg, next) => 
        next.Height > agg.Height ? next : agg);

var maxHeightAndWidth = dimensions
    .Aggregate((agg, next) => 
        next.Height >= agg.Height && next.Width >= agg.Width ? next: agg);
126

そして、なぜあなたはこれを試してみませんか? :

var itemsMax = items.Where(x => x.Height == items.Max(y => y.Height));

またはより最適化する:

var itemMaxHeight = items.Max(y => y.Height);
var itemsMax = items.Where(x => x.Height == itemMaxHeight);

うーん ?

33
Dragouf

答えはこれまでのところ素晴らしいです!しかし、私は次のような制約のある解決策が必要だと思います。

  1. プレーンで簡潔なLINQ。
  2. O(n)の複雑さ
  3. 要素ごとに複数回プロパティを評価しないでください。

ここにあります:

public static T MaxBy<T, R>(this IEnumerable<T> en, Func<T, R> evaluate) where R : IComparable<R> {
    return en.Select(t => new Tuple<T, R>(t, evaluate(t)))
        .Aggregate((max, next) => next.Item2.CompareTo(max.Item2) > 0 ? next : max).Item1;
}

public static T MinBy<T, R>(this IEnumerable<T> en, Func<T, R> evaluate) where R : IComparable<R> {
    return en.Select(t => new Tuple<T, R>(t, evaluate(t)))
        .Aggregate((max, next) => next.Item2.CompareTo(max.Item2) < 0 ? next : max).Item1;
}

使用法:

IEnumerable<Tuple<string, int>> list = new[] {
    new Tuple<string, int>("other", 2),
    new Tuple<string, int>("max", 4),
    new Tuple<string, int>("min", 1),
    new Tuple<string, int>("other", 3),
};
Tuple<string, int> min = list.MinBy(x => x.Item2); // "min", 1
Tuple<string, int> max = list.MaxBy(x => x.Item2); // "max", 4
18
meustrus

MAXを取得したい列でソートしてから最初の列を取得するとうまくいくはずです。ただし、同じMAX値を持つオブジェクトが複数ある場合は、1つだけ取得されます。

private void Test()
{
    test v1 = new test();
    v1.Id = 12;

    test v2 = new test();
    v2.Id = 12;

    test v3 = new test();
    v3.Id = 12;

    List<test> arr = new List<test>();
    arr.Add(v1);
    arr.Add(v2);
    arr.Add(v3);

    test max = arr.OrderByDescending(t => t.Id).First();
}

class test
{
    public int Id { get; set; }
}
3
regex

NHibernate(with NHibernate.Linq)では、次のようにすることができます。

return session.Query<T>()
              .Single(a => a.Filter == filter &&
                           a.Id == session.Query<T>()
                                          .Where(a2 => a2.Filter == filter)
                                          .Max(a2 => a2.Id));

これにより、次のようなSQLが生成されます。

select *
from TableName foo
where foo.Filter = 'Filter On String'
and foo.Id = (select cast(max(bar.RowVersion) as INT)
              from TableName bar
              where bar.Name = 'Filter On String')

これは私にはかなり効率的です。

2
Tod Thomson

Mehrdad Afshariのソリューションを拡張メソッドをより高速な(そして見栄えの良い)ものに書き換えることでアップグレードすることもできます。

static class EnumerableExtensions
{
    public static T MaxElement<T, R>(this IEnumerable<T> container, Func<T, R> valuingFoo) where R : IComparable
    {
        var enumerator = container.GetEnumerator();
        if (!enumerator.MoveNext())
            throw new ArgumentException("Container is empty!");

        var maxElem = enumerator.Current;
        var maxVal = valuingFoo(maxElem);

        while (enumerator.MoveNext())
        {
            var currVal = valuingFoo(enumerator.Current);

            if (currVal.CompareTo(maxVal) > 0)
            {
                maxVal = currVal;
                maxElem = enumerator.Current;
            }
        }

        return maxElem;
    }
}

そしてそれを使うだけです:

var maxObject = list.MaxElement(item => item.Height);

その名前は、C++を使用している人には明らかになります(std :: max_elementがそこにあるため)。

1

Cameronの最初の答えに基づいて、SilverFlowライブラリのFloatingWindowHostの拡張版に追加したものがあります( http://clipflair.codeplex.com sourceのFloatingWindowHost.csからコピーします)。コード)

    public int MaxZIndex
    {
      get {
        return FloatingWindows.Aggregate(-1, (maxZIndex, window) => {
          int w = Canvas.GetZIndex(window);
          return (w > maxZIndex) ? w : maxZIndex;
        });
      }
    }

    private void SetTopmost(UIElement element)
    {
        if (element == null)
            throw new ArgumentNullException("element");

        Canvas.SetZIndex(element, MaxZIndex + 1);
    }

上記のコードに関して、Canvas.ZIndexはさまざまなコンテナ内のUIElementで使用可能な添付プロパティであり、Canvasでホストされている場合には使用されません( SilverlightでCanvasを使用せずにレンダリング順序を制御する(ZOrder)」コントロール )。このコードを修正することで、UIElementのSetTopmostおよびSetBottomMost静的拡張メソッドを簡単に作成できることもあります。

1
George Birbilis