Programing

컬렉션에있는 모든 객체의 속성에 대해 .Max ()를 수행하고 최대 값으로 객체를 반환하는 방법

lottogame 2020. 3. 31. 08:10
반응형

컬렉션에있는 모든 객체의 속성에 대해 .Max ()를 수행하고 최대 값으로 객체를 반환하는 방법


두 개의 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의 두 번째 솔루션 (기본적으로와 동일 MaxBy) 이외의 여기에 제시된 솔루션보다 낫습니다 (IMO ).

  • 모든 반복에서 최대 값을 찾는 이전에 허용 된 답변 과 달리 O (n)입니다 (O (n ^ 2)).
  • 주문 솔루션은 O (n log n)입니다.
  • Max값을 취한 다음 해당 값을 가진 첫 번째 요소를 찾는 것은 O (n)이지만 시퀀스를 두 번 반복합니다. 가능하면 LINQ를 단일 패스 방식으로 사용해야합니다.
  • 집계 버전보다 읽고 이해하기가 훨씬 간단하며 요소 당 한 번만 투영을 평가합니다.

이것은 정렬 (O (n log n))이 필요하지만 매우 간단하고 유연합니다. 또 다른 장점은 LINQ to SQL과 함께 사용할 수 있다는 것입니다.

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

이것은 list시퀀스를 한 번만 열거 할 수 있다는 이점이 있습니다 . 동안은 문제가되지 경우 수도 listA는 List<T>그 동안 변경되지 않습니다, 그것은 임의의에 대한 문제가 있었다 IEnumerable<T>객체. 시퀀스가 다른 열거에서 변경되지 않는다는 보장은 없으므로 시퀀스를 여러 번 수행하는 메소드는 위험 할 수 있습니다 (시퀀스의 특성에 따라 비효율적). 그러나 여전히 큰 시퀀스에 이상적인 솔루션은 아닙니다. MaxObject정렬 및 기타 항목 (O (n))없이 한 번에 여러 항목을 수행 할 수있는 큰 항목 집합이있는 경우 직접 확장 프로그램을 작성하는 것이 좋습니다 .

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

음?


지금까지의 답변은 훌륭합니다! 그러나 다음과 같은 제약 조건이있는 솔루션이 필요합니다.

  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

MAX를 얻으려는 열을 기준으로 정렬 한 다음 첫 번째를 잡아야한다고 생각합니다. 그러나 동일한 MAX 값을 가진 여러 객체가있는 경우 하나만 가져옵니다.

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 (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')

나에게는 꽤 효율적인 것 같습니다.


Cameron의 초기 답변을 바탕으로, 여기에 향상된 버전의 SilverFlow 라이브러리 인 FloatingWindowHost에 추가 한 내용이 있습니다 ( http://clipflair.codeplex.com 소스 코드의 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는 Canvas에서 호스팅 될 때뿐만 아니라 다양한 컨테이너의 UIElement에 사용할 수있는 첨부 된 속성입니다 ( Canvas 컨트롤을 사용하지 않고 Silverlight에서 렌더링 순서 제어 (ZOrder) 참조 ). 이 코드를 적용하여 UIElement에 대한 SetTopmost 및 SetBottomMost 정적 확장 메소드를 쉽게 만들 수 있다고 생각합니다.


확장 방법을 더 빠르고보기 좋은 방법으로 다시 작성하여 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가 있기 때문에).

참고 URL : https://stackoverflow.com/questions/1101841/how-to-perform-max-on-a-property-of-all-objects-in-a-collection-and-return-th


반응형