LINQ를 사용하여 최소 또는 최대 속성 값을 가진 개체를 선택하는 방법 다음과 같습니다. var firstBornDate = People.Min(p =>

Nullable DateOfBirth 속성을 가진 Person 개체가 있습니다. LINQ를 사용하여 가장 짧거나 가장 작은 DateOfBirth 값을 가진 사람에 대한 Person 객체 목록을 쿼리하는 방법이 있습니까?

내가 시작한 것은 다음과 같습니다.

var firstBornDate = People.Min(p => p.DateOfBirth.GetValueOrDefault(DateTime.MaxValue));

Null DateOfBirth 값은 최소 고려 사항에서 제외하기 위해 DateTime.MaxValue로 설정됩니다 (적어도 하나의 지정된 DOB가 있다고 가정).

그러나 나를 위해하는 일은 firstBornDate를 DateTime 값으로 설정하는 것입니다. 내가 얻고 싶은 것은 그와 일치하는 Person 객체입니다. 다음과 같이 두 번째 쿼리를 작성해야합니까?

var firstBorn = People.Single(p=> (p.DateOfBirth ?? DateTime.MaxValue) == firstBornDate);

아니면 더 적은 방법이 있습니까?



답변

People.Aggregate((curMin, x) => (curMin == null || (x.DateOfBirth ?? DateTime.MaxValue) <
    curMin.DateOfBirth ? x : curMin))

답변

불행히도이 작업을 수행하는 기본 제공 방법은 없지만 직접 구현하기는 쉽습니다. 그것의 내장은 다음과 같습니다.

public static TSource MinBy<TSource, TKey>(this IEnumerable<TSource> source,
    Func<TSource, TKey> selector)
{
    return source.MinBy(selector, null);
}

public static TSource MinBy<TSource, TKey>(this IEnumerable<TSource> source,
    Func<TSource, TKey> selector, IComparer<TKey> comparer)
{
    if (source == null) throw new ArgumentNullException("source");
    if (selector == null) throw new ArgumentNullException("selector");
    comparer = comparer ?? Comparer<TKey>.Default;

    using (var sourceIterator = source.GetEnumerator())
    {
        if (!sourceIterator.MoveNext())
        {
            throw new InvalidOperationException("Sequence contains no elements");
        }
        var min = sourceIterator.Current;
        var minKey = selector(min);
        while (sourceIterator.MoveNext())
        {
            var candidate = sourceIterator.Current;
            var candidateProjected = selector(candidate);
            if (comparer.Compare(candidateProjected, minKey) < 0)
            {
                min = candidate;
                minKey = candidateProjected;
            }
        }
        return min;
    }
}

사용법 예 :

var firstBorn = People.MinBy(p => p.DateOfBirth ?? DateTime.MaxValue);

시퀀스가 비어 있으면 예외가 발생하고 첫 번째 요소가 두 개 이상인 경우 최소값으로 반환됩니다 .

또는, 우리가 가지고 구현을 사용할 수 있습니다 MoreLINQ 에, MinBy.cs을 . ( MaxBy물론 해당하는 것이 있습니다.)

패키지 관리자 콘솔을 통해 설치하십시오.

PM> 설치 패키지 morelinq


답변

참고 : OP는 데이터 소스가 무엇인지 언급하지 않았으므로 가정을 취하지 않아야 하므로이 답변을 완벽하게 포함합니다.

이 쿼리는 정답을 제공하지만 데이터 구조에 따라의 모든 항목 을 정렬해야 하므로 속도가 느릴 수 있습니다 .PeoplePeople

var oldest = People.OrderBy(p => p.DateOfBirth ?? DateTime.MaxValue).First();

업데이트 : 실제로이 솔루션을 “순진한”것으로 호출해서는 안되지만 사용자는 자신이 쿼리하는 내용을 알아야합니다. 이 솔루션의 “느림”은 기본 데이터에 따라 다릅니다. 이것이 배열 또는 List<T>인 경우 LINQ to Objects는 첫 번째 항목을 선택하기 전에 전체 컬렉션을 먼저 정렬하는 것 외에는 선택의 여지가 없습니다. 이 경우 제안 된 다른 솔루션보다 속도가 느립니다. 그러나 이것이 LINQ to SQL 테이블이고 DateOfBirth인덱스 열인 경우 SQL Server는 모든 행을 정렬하는 대신 인덱스를 사용합니다. 다른 사용자 정의 IEnumerable<T>구현도 색인을 사용하고 ( i4o : 색인화 된 LINQ 또는 오브젝트 데이터베이스 db4o 참조 )이 솔루션을 / Aggregate()또는MaxBy()MinBy()전체 컬렉션을 한 번 반복해야합니다. 실제로 LINQ to Objects는 이론적 OrderBy()으로와 같은 정렬 된 컬렉션 에 특별한 경우를 만들 수 SortedList<T>있었지만 내가 아는 한 그렇지 않습니다.


답변

People.OrderBy(p => p.DateOfBirth.GetValueOrDefault(DateTime.MaxValue)).First()

트릭을 할 것


답변

그래서 당신을 요구하고 ArgMinArgMax. C #에는 이러한 API가 내장되어 있지 않습니다.

나는 이것을하기 위해 깨끗하고 효율적인 (O (n)) 방법을 찾고있었습니다. 그리고 나는 하나를 찾았다 고 생각합니다.

이 패턴의 일반적인 형태는 다음과 같습니다.

var min = data.Select(x => (key(x), x)).Min().Item2;
                            ^           ^       ^
              the sorting key           |       take the associated original item
                                Min by key(.)

특히 원래 질문의 예를 사용하면 다음과 같습니다.

값 튜플 을 지원하는 C # 7.0 이상의 경우 :

var youngest = people.Select(p => (p.DateOfBirth, p)).Min().Item2;

7.0 이전의 C # 버전의 경우 익명 유형 을 대신 사용할 수 있습니다.

var youngest = people.Select(p => new { ppl = p; age = p.DateOfBirth }).Min().ppl;

값 튜플과 익명 유형 모두 합리적인 기본 비교기를 갖기 때문에 작동합니다. (x1, y1) 및 (x2, y2)의 경우 먼저 x1vs를 비교 x2한 다음 y1vs 를 비교 y2합니다. 이런 이유로 내장형을 .Min사용할 수 있습니다.

익명 유형과 값 튜플은 모두 값 유형이므로 매우 효율적이어야합니다.

노트

위의 ArgMin구현에서 나는 단순성과 명확성을 위해 DateOfBirth유형을 취하는 것으로 가정 했습니다 DateTime. 원래 질문은 null DateOfBirth필드가 있는 항목을 제외하도록 요청 합니다.

Null DateOfBirth 값은 최소 고려 사항에서 제외하기 위해 DateTime.MaxValue로 설정됩니다 (적어도 하나의 지정된 DOB가 있다고 가정).

사전 필터링으로 달성 할 수 있습니다

people.Where(p => p.DateOfBirth.HasValue)

따라서 ArgMin또는 구현 문제에 대해서는 중요하지 않습니다 ArgMax.

노트 2

위의 접근 방식은 최소값이 동일한 두 인스턴스가있을 때 Min()구현시 인스턴스를 타이 브레이커로 비교하려고 시도 한다는 경고가 있습니다 . 그러나 인스턴스 클래스가 구현하지 않으면 IComparable런타임 오류가 발생합니다.

최소한 하나의 객체가 IComparable을 구현해야합니다.

운 좋게도 여전히 깨끗하게 고칠 수 있습니다. 아이디어는 확실한 “ID”를 명확한 타이 브레이커 역할을하는 각 항목과 연결하는 것입니다. 각 항목마다 증분 ID를 사용할 수 있습니다. 여전히 사람들 나이를 예로 사용합니다.

var youngest = Enumerable.Range(0, int.MaxValue)
               .Zip(people, (idx, ppl) => (ppl.DateOfBirth, idx, ppl)).Min().Item3;

답변

추가 패키지가없는 솔루션 :

var min = lst.OrderBy(i => i.StartDate).FirstOrDefault();
var max = lst.OrderBy(i => i.StartDate).LastOrDefault();

또한 확장으로 랩핑 할 수 있습니다.

public static class LinqExtensions
{
    public static T MinBy<T, TProp>(this IEnumerable<T> source, Func<T, TProp> propSelector)
    {
        return source.OrderBy(propSelector).FirstOrDefault();
    }

    public static T MaxBy<T, TProp>(this IEnumerable<T> source, Func<T, TProp> propSelector)
    {
        return source.OrderBy(propSelector).LastOrDefault();
    }
}

이 경우 :

var min = lst.MinBy(i => i.StartDate);
var max = lst.MaxBy(i => i.StartDate);

그건 그렇고 … O (n ^ 2)는 최고의 솔루션이 아닙니다. Paul Betts 는 저보다 더 뚱뚱한 해결책을주었습니다. 그러나 나는 여전히 LINQ 솔루션이며 다른 솔루션보다 더 간단하고 짧습니다.


답변

public class Foo {
    public int bar;
    public int stuff;
};

void Main()
{
    List<Foo> fooList = new List<Foo>(){
    new Foo(){bar=1,stuff=2},
    new Foo(){bar=3,stuff=4},
    new Foo(){bar=2,stuff=3}};

    Foo result = fooList.Aggregate((u,v) => u.bar < v.bar ? u: v);
    result.Dump();
}