2つのintプロパティを持つオブジェクトのリストがあります。リストは別のlinqクエリの出力です。オブジェクト:
public class DimensionPair
{
public int Height { get; set; }
public int Width { get; set; }
}
最大のHeight
プロパティ値を持つオブジェクトをリストから見つけて返したい。
Height
値の最大値を取得することはできますが、オブジェクト自体は取得できません。
Linqでこれをすることができますか?どうやって?
これを MoreLINQ で正確に行うための 拡張メソッド があります。ここで実装を見ることができますが、基本的にはこれまで見てきた最大要素とそれが射影の下で生成した最大値を思い出しながら、データを反復処理するケースです。
あなたの場合は、次のようにします。
var item = items.MaxBy(x => x.Height);
これは、Mehrdadの2番目の解決策(基本的にはMaxBy
と同じ)を除いて、ここに示されているどの解決策よりも優れています(IMO)。
Max
値を取ってからその値を持つ最初の要素を見つけることはO(n)ですが、シーケンスを2回繰り返します。可能であれば、LINQはシングルパス方式で使用してください。これにはソート(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);
最初の商品を注文してから選択するのは、最初の商品の後に商品を注文するのに非常に時間がかかります。あなたはそれらの順序を気にしません。
代わりに、集約関数を使用して、探しているものに基づいて最適なアイテムを選択できます。
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);
そして、なぜあなたはこれを試してみませんか? :
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);
うーん ?
答えはこれまでのところ素晴らしいです!しかし、私は次のような制約のある解決策が必要だと思います。
ここにあります:
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
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; }
}
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')
これは私にはかなり効率的です。
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がそこにあるため)。
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静的拡張メソッドを簡単に作成できることもあります。