न्यूनतम या अधिकतम संपत्ति मूल्य के साथ ऑब्जेक्ट का चयन करने के लिए LINQ का उपयोग कैसे करें


465

मैं एक अशक्त DateOfBirth संपत्ति के साथ एक व्यक्ति वस्तु है। लिनेक्यू का उपयोग करने का एक तरीका है कि जल्द से जल्द / सबसे छोटी DateOfBirth मान के साथ व्यक्ति की वस्तुओं की सूची को क्वेरी करने के लिए।

यहाँ मैंने क्या शुरू किया है:

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

Null DateOfBirth मान DateTime.MaxValue में सेट किए गए हैं ताकि उन्हें मिन विचार से बाहर निकालने के लिए नियम बनाया जा सके (कम से कम एक निर्दिष्ट DOB है)।

लेकिन मेरे लिए जो कुछ भी करना है वह FirstBornDate को DateTime मान पर सेट करना है। मैं जो प्राप्त करना चाहता हूं वह व्यक्तिगत वस्तु है जो मेल खाती है। क्या मुझे दूसरी क्वेरी लिखने की आवश्यकता है:

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

या इसे करने का एक दुबला तरीका है?


24
अपने उदाहरण पर एक टिप्पणी: आप शायद यहाँ सिंगल का उपयोग न करें। दो लोग एक ही DateOfBirth
Niki

1
लगभग-डुप्लिकेट किए गए stackoverflow.com/questions/2736236/… को भी देखें , जिसमें कुछ संक्षिप्त उदाहरण हैं।
goodeye

4
क्या एक सरल और उपयोगी सुविधा है। MinBy को मानक पुस्तकालय में होना चाहिए। हमें Microsoft github.com/dotnet/corefx
कर्नल पैनिक

2
यह आज मौजूद प्रतीत होता है, बस संपत्ति लेने के लिए एक फ़ंक्शन प्रदान करें:a.Min(x => x.foo);
जैकमॉट

4
समस्या को प्रदर्शित करने के लिए: पायथन में, max("find a word of maximal length in this sentence".split(), key=len)स्ट्रिंग 'वाक्य' लौटाता है। C # में "find a word of maximal length in this sentence".Split().Max(word => word.Length)गणना करता है कि 8 किसी भी शब्द की सबसे लंबी लंबाई है, लेकिन आपको यह नहीं बताता कि सबसे लंबा शब्द क्या है
कर्नल पैनिक

जवाबों:


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

16
संभवतः केवल IComparable को लागू करने और Min (या लूप के लिए) का उपयोग करने की तुलना में थोड़ा धीमा। लेकिन O के लिए +1 (n) लिनकी समाधान।
मैथ्यू फ़्लेशेन

3
इसके अलावा, इसे <curmin.DateOfBirth होना चाहिए। अन्यथा, आप किसी व्यक्ति को डेटटाइम की तुलना कर रहे हैं।
मैथ्यू फ्लैशेन

2
दो तारीख के समय की तुलना करने के लिए इसका उपयोग करते समय भी सावधान रहें। मैं इसका उपयोग अनियंत्रित संग्रह में अंतिम परिवर्तन रिकॉर्ड खोजने के लिए कर रहा था। यह विफल हो गया क्योंकि मैं जो रिकॉर्ड चाहता था वह उसी तिथि और समय के साथ समाप्त हो गया।
साइमन गिल

8
आप सुपरफ्लस चेक क्यों करते हैं curMin == null? curMinकेवल तभी हो सकता है nullजब आप Aggregate()एक बीज के साथ उपयोग कर रहे हों null
गुड नाइट नर्ड प्राइड


226

दुर्भाग्य से ऐसा करने के लिए कोई अंतर्निहित पद्धति नहीं है, लेकिन यह अपने आप को लागू करने के लिए काफी आसान है। यहाँ इसकी हिम्मत हैं:

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


1
मैं Ienumerator + की जगह लेगा, जबकि foreach
ggf31416

5
लूप से पहले MoveNext () को पहली कॉल के कारण आसानी से नहीं कर सकते। विकल्प हैं, लेकिन वे IMO के संदेशवाहक हैं।
जॉन स्कीट

2
जबकि मैं डिफ़ॉल्ट (T) वापस कर सकता हूं जो मेरे लिए अनुपयुक्त लगता है। यह फर्स्ट () और डिक्शनरी इंडेक्सर के दृष्टिकोण जैसे तरीकों के साथ अधिक सुसंगत है। यदि आप चाहते हैं तो आप आसानी से इसे अनुकूलित कर सकते हैं।
जॉन स्कीट

8
मैंने गैर-लाइब्रेरी समाधान के कारण पॉल को जवाब दिया, लेकिन इस कोड के लिए धन्यवाद और MoreLINQ लाइब्रेरी के लिए लिंक, जो मुझे लगता है कि मैं उपयोग करना शुरू कर दूंगा!
स्लॉइफ


135

नोट: मैं इस उत्तर को पूर्णता के लिए शामिल करता हूं क्योंकि ओपी ने यह उल्लेख नहीं किया है कि डेटा स्रोत क्या है और हमें कोई धारणा नहीं बनानी चाहिए।

यह क्वेरी सही उत्तर देती है, लेकिन धीमी हो सकती है क्योंकि इसमें सभी आइटम्स को क्रमबद्ध करना पड़ सकता है People, जो कि डेटा संरचना पर निर्भर करता Peopleहै:

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

अद्यतन: वास्तव में मुझे इस समाधान को "भोला" नहीं कहना चाहिए, लेकिन उपयोगकर्ता को यह जानने की आवश्यकता है कि वह किसके खिलाफ सवाल कर रहा है। इस समाधान का "सुस्ती" अंतर्निहित डेटा पर निर्भर करता है। यदि यह एक सरणी है या List<T>, तो LINQ to ऑब्जेक्ट्स के पास पहला आइटम चुनने से पहले पूरे संग्रह को सॉर्ट करने के अलावा कोई विकल्प नहीं है। इस मामले में यह सुझाए गए अन्य समाधान की तुलना में धीमा होगा। हालाँकि, यदि यह LINQ to SQL टेबल है और DateOfBirthएक अनुक्रमित कॉलम है, तो SQL सर्वर सभी पंक्तियों को छाँटने के बजाय सूचकांक का उपयोग करेगा। अन्य कस्टम IEnumerable<T>कार्यान्वयन इंडेक्स का उपयोग भी कर सकते हैं ( i4o देखें : अनुक्रमित LINQ , या ऑब्जेक्ट डेटाबेस db4o ) और इस समाधान को / Aggregate()या से अधिक तेज़ बना सकते हैंMaxBy()MinBy()जिसे एक बार पूरे संग्रह को पुनरावृत्त करने की आवश्यकता है। वास्तव में, वस्तुओं के लिए LINQ (सिद्धांत में) OrderBy()सॉर्ट किए गए संग्रह के लिए विशेष मामले बना सकता है SortedList<T>, लेकिन यह नहीं है, जहां तक ​​मुझे पता है।


1
किसी ने पहले से ही पोस्ट किया है, लेकिन जाहिर है कि मैंने टिप्पणी करने के बाद इसे हटा दिया (मिनट और अंतरिक्ष की खपत) कितनी धीमी थी (मिनट के लिए ओ (एन) की तुलना में ओ (एन लॉग एन) सबसे अच्छी गति थी)। :)
मैथ्यू फ्लशेन

हां, इसलिए भोले समाधान होने के बारे में मेरी चेतावनी :) हालांकि यह मृत सरल है और कुछ मामलों में उपयोग करने योग्य हो सकता है (छोटे संग्रह या यदि DateOfBirth एक अनुक्रमित DB स्तंभ है)
लुकास

एक और विशेष मामला (जो या तो वहां नहीं है) यह है कि क्रमबद्धता के ज्ञान का उपयोग करना संभव होगा और पहले बिना छांटे सबसे कम मूल्य की खोज करना।
रुन एफएस

एक संग्रह को क्रमबद्ध करना Nlog (N) ऑपरेशन है जो रैखिक या O (n) समय की जटिलता से बेहतर नहीं है। अगर हमें अनुक्रम से 1 तत्व / वस्तु की आवश्यकता है जो न्यूनतम या अधिकतम है, तो मुझे लगता है कि हमें रैखिक समय के साथ मिलना चाहिए।
यवर मुर्तजा

@yawar संग्रह पहले से ही क्रमबद्ध किया जा सकता है (अधिक संभावना अनुक्रमित) जिस स्थिति में आप O (लॉग एन) हो सकते हैं
Rune FS

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

चाल चलेगी


1
यह एक महान है! मैंने लाइनक प्रोजेंशन के अपने मामले में ऑर्डरबायडिंग (...)। टेक (1) का उपयोग किया।
वेदरन मंडी

1
यह एक छँटाई का उपयोग करता है, जो O (N) समय से अधिक है और O (N) मेमोरी का भी उपयोग करता है।
जॉर्ज पोलेवॉय

@GeorgePolevoy मानता है कि हम डेटा स्रोत के बारे में काफी कुछ जानते हैं। यदि डेटा स्रोत में पहले से ही दिए गए फ़ील्ड पर एक सॉर्ट किया गया इंडेक्स है, तो यह एक (निम्न) स्थिर होगा और यह स्वीकृत उत्तर की तुलना में बहुत तेज़ होगा, जिसे पूरी सूची को ट्रैवर्स करना होगा। यदि दूसरी ओर डेटा स्रोत उदाहरण के लिए है, तो आप निश्चित रूप से सही हैं
Rune FS

@RuneFS - फिर भी आपको अपने उत्तर में इसका उल्लेख करना चाहिए क्योंकि यह महत्वपूर्ण है।
rory.ap

प्रदर्शन आपको नीचे खींच देगा। मैंने इसे कठिन तरीके से सीखा। यदि आप न्यूनतम या अधिकतम मान के साथ ऑब्जेक्ट चाहते हैं, तो आपको संपूर्ण सरणी को सॉर्ट करने की आवश्यकता नहीं है। बस 1 स्कैन पर्याप्त होना चाहिए। स्वीकृत उत्तर को देखें या MoreLinq पैकेज को देखें।
Sau001

35

तो आप के लिए पूछ रहे हैं ArgMinयाArgMax । 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 और उससे अधिक के लिए जो मान tuple का समर्थन करता है :

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) के लिए, यह पहले x1बनाम x2, फिर y1बनाम की तुलना करता है y2। इसीलिए बिल्ट-इन .Minका इस्तेमाल उन प्रकारों पर किया जा सकता है।

और चूंकि दोनों अनाम प्रकार और मूल्य टुपल मूल्य प्रकार हैं, इसलिए वे दोनों बहुत कुशल होने चाहिए।

ध्यान दें

मेरे उपरोक्त ArgMinकार्यान्वयन में मैंने सादगी और स्पष्टता के लिए DateOfBirthप्रकार ग्रहण किया DateTime। मूल प्रश्न उन प्रविष्टियों को शून्य DateOfBirthक्षेत्र से बाहर करने के लिए कहता है :

Null DateOfBirth मान DateTime.MaxValue में सेट किए गए हैं ताकि उन्हें मिन विचार से बाहर निकालने के लिए नियम बनाया जा सके (कम से कम एक निर्दिष्ट DOB है)।

यह एक पूर्व फ़िल्टरिंग के साथ प्राप्त किया जा सकता है

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

इसलिए इसे लागू करने के सवाल पर अमल करना ArgMinयाArgMax

नोट 2

उपर्युक्त दृष्टिकोण में एक चेतावनी है कि जब दो उदाहरण हैं जिनका एक ही न्यूनतम मूल्य है, तो Min()कार्यान्वयन उदाहरणों को टाई-ब्रेकर के रूप में तुलना करने की कोशिश करेगा। हालाँकि, यदि इंस्टेंस का वर्ग लागू नहीं होता है IComparable, तो रनटाइम एरर को फेंक दिया जाएगा:

कम से कम एक वस्तु को IComparable को लागू करना चाहिए

सौभाग्य से, यह अभी भी साफ-सुथरे तरीके से तय किया जा सकता है। विचार प्रत्येक प्रविष्टि के साथ एक दूरस्थ "आईडी" को जोड़ने का है जो अस्पष्ट टाई-ब्रेकर के रूप में कार्य करता है। हम प्रत्येक प्रविष्टि के लिए एक वृद्धिशील आईडी का उपयोग कर सकते हैं। अभी भी उदाहरण के रूप में लोगों की उम्र का उपयोग:

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

1
यह तब काम नहीं करता है जब मूल्य प्रकार छँटाई कुंजी है। "कम से कम एक वस्तु को IComparable को लागू करना चाहिए"
liang

1
भी महान! यह सबसे अच्छा जवाब होना चाहिए।
गुइडो मोचा

@liang हाँ अच्छी पकड़। सौभाग्य से वहाँ अभी भी एक साफ समाधान है। "नोट 2" अनुभाग में अद्यतन समाधान देखें।
KFL

चयन आपको आईडी दे सकता है! var सबसे कम उम्र के लोग = सेलेक्ट करें ((p, i) => (p.DateOfBirth, i, p)) Min ()। Item2
जेरेमी

19

बिना किसी अतिरिक्त पैकेज के समाधान:

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

वैसे ... ओ (एन ^ 2) सबसे अच्छा समाधान नहीं है। पॉल बेट्ट्स ने मेरी तुलना में मोटा समाधान दिया। लेकिन मेरा अभी भी LINQ समाधान है और यह अन्य समाधानों की तुलना में अधिक सरल और अधिक छोटा है।


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

3

समुच्चय का पूरी तरह से सरल उपयोग (अन्य भाषाओं में गुना के बराबर):

var firstBorn = People.Aggregate((min, x) => x.DateOfBirth < min.DateOfBirth ? x : min);

केवल नकारात्मक पक्ष यह है कि संपत्ति दो बार प्रति अनुक्रम तत्व तक पहुंच जाती है, जो महंगी हो सकती है। जिसे ठीक करना मुश्किल है।


1

निम्नलिखित अधिक सामान्य समाधान है। यह अनिवार्य रूप से एक ही चीज़ (O (N) ऑर्डर में) पर करता है, लेकिन किसी भी प्रकार के IEnumberable पर और उन प्रकारों के साथ मिलाया जा सकता है जिनके गुण चयनकर्ता अशक्त हो सकते हैं।

public static class LinqExtensions
{
    public static T MinBy<T>(this IEnumerable<T> source, Func<T, IComparable> selector)
    {
        if (source == null)
        {
            throw new ArgumentNullException(nameof(source));
        }
        if (selector == null)
        {
            throw new ArgumentNullException(nameof(selector));
        }
        return source.Aggregate((min, cur) =>
        {
            if (min == null)
            {
                return cur;
            }
            var minComparer = selector(min);
            if (minComparer == null)
            {
                return cur;
            }
            var curComparer = selector(cur);
            if (curComparer == null)
            {
                return min;
            }
            return minComparer.CompareTo(curComparer) > 0 ? cur : min;
        });
    }
}

टेस्ट:

var nullableInts = new int?[] {5, null, 1, 4, 0, 3, null, 1};
Assert.AreEqual(0, nullableInts.MinBy(i => i));//should pass

0

फिर से संपादित करें:

माफ़ करना। अनुपलब्ध के अलावा मैं गलत कार्य देख रहा था,

न्यूनतम <(<(TSource, TResult>)>) (IEnumerable <(of (TSource>)>), Func <(TSource, TResult>) का)) जैसा आपने कहा था वैसा ही परिणाम लौटाता है।

मैं कहूंगा कि एक संभव समाधान IComparable को लागू करना और Min <(<(TSource>)>) का उपयोग करना है (IEnumerable <(of <(TSource>)>))) , जो वास्तव में से एक तत्व वापस करता है। यदि आप तत्व को संशोधित नहीं कर सकते हैं, तो निश्चित रूप से, यह आपकी मदद नहीं करता है। मुझे यहाँ एमएस का डिज़ाइन थोड़ा अजीब लगता है।

बेशक, आप हमेशा लूप के लिए कर सकते हैं यदि आपको जरूरत है, या मोरक्लिनक्यू कार्यान्वयन का उपयोग करें जो जॉन स्कीट ने दिया था।


0

एक अन्य कार्यान्वयन, जो अशक्त चयनकर्ता कुंजियों के साथ काम कर सकता है, और संदर्भ प्रकार के संग्रह के लिए यदि कोई उपयुक्त तत्व नहीं मिला है, तो अशक्त है। यह तब सहायक हो सकता है, उदाहरण के लिए डेटाबेस परिणाम संसाधित करना।

  public static class IEnumerableExtensions
  {
    /// <summary>
    /// Returns the element with the maximum value of a selector function.
    /// </summary>
    /// <typeparam name="TSource">The type of the elements of source.</typeparam>
    /// <typeparam name="TKey">The type of the key returned by keySelector.</typeparam>
    /// <param name="source">An IEnumerable collection values to determine the element with the maximum value of.</param>
    /// <param name="keySelector">A function to extract the key for each element.</param>
    /// <exception cref="System.ArgumentNullException">source or keySelector is null.</exception>
    /// <exception cref="System.InvalidOperationException">source contains no elements.</exception>
    /// <returns>The element in source with the maximum value of a selector function.</returns>
    public static TSource MaxBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector) => MaxOrMinBy(source, keySelector, 1);

    /// <summary>
    /// Returns the element with the minimum value of a selector function.
    /// </summary>
    /// <typeparam name="TSource">The type of the elements of source.</typeparam>
    /// <typeparam name="TKey">The type of the key returned by keySelector.</typeparam>
    /// <param name="source">An IEnumerable collection values to determine the element with the minimum value of.</param>
    /// <param name="keySelector">A function to extract the key for each element.</param>
    /// <exception cref="System.ArgumentNullException">source or keySelector is null.</exception>
    /// <exception cref="System.InvalidOperationException">source contains no elements.</exception>
    /// <returns>The element in source with the minimum value of a selector function.</returns>
    public static TSource MinBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector) => MaxOrMinBy(source, keySelector, -1);


    private static TSource MaxOrMinBy<TSource, TKey>
      (IEnumerable<TSource> source, Func<TSource, TKey> keySelector, int sign)
    {
      if (source == null) throw new ArgumentNullException(nameof(source));
      if (keySelector == null) throw new ArgumentNullException(nameof(keySelector));
      Comparer<TKey> comparer = Comparer<TKey>.Default;
      TKey value = default(TKey);
      TSource result = default(TSource);

      bool hasValue = false;

      foreach (TSource element in source)
      {
        TKey x = keySelector(element);
        if (x != null)
        {
          if (!hasValue)
          {
            value = x;
            result = element;
            hasValue = true;
          }
          else if (sign * comparer.Compare(x, value) > 0)
          {
            value = x;
            result = element;
          }
        }
      }

      if ((result != null) && !hasValue)
        throw new InvalidOperationException("The source sequence is empty");

      return result;
    }
  }

उदाहरण:

public class A
{
  public int? a;
  public A(int? a) { this.a = a; }
}

var b = a.MinBy(x => x.a);
var c = a.MaxBy(x => x.a);

-2

मैं अपने आप से कुछ इसी तरह की तलाश कर रहा था, अधिमानतः एक पुस्तकालय का उपयोग किए बिना या पूरी सूची को सॉर्ट किए बिना। मेरा समाधान प्रश्न के समान ही समाप्त हो गया, बस थोड़ा सरल हुआ।

var firstBorn = People.FirstOrDefault(p => p.DateOfBirth == People.Min(p2 => p2.DateOfBirth));

क्या आपके लिनक बयान से पहले मंत्री प्राप्त करना कहीं अधिक कुशल नहीं होगा? var min = People.Min(...); var firstBorn = People.FirstOrDefault(p => p.DateOfBirth == min...अन्यथा इसे बार-बार मिल रहा है जब तक कि वह आपको ढूंढ नहीं रहा है।
नीमीन
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.