Linq में "MinOrDefault" को प्राप्त करने का सबसे साफ तरीका क्या है?


82

मैं एक linq अभिव्यक्ति से दशमलव मानों की एक सूची तैयार कर रहा हूं और मैं न्यूनतम गैर शून्य मान चाहता हूं। हालांकि यह पूरी तरह से संभव है कि लाइनक अभिव्यक्ति एक खाली सूची में परिणाम देगा।

यह एक अपवाद को बढ़ाएगा और इस स्थिति से निपटने के लिए कोई MinOrDefault नहीं है।

decimal result = (from Item itm in itemList
                  where itm.Amount > 0
                  select itm.Amount).Min();

यदि सूची खाली है, तो परिणाम को 0 पर सेट करने का सबसे साफ तरीका क्या है?


9
लाइब्रेरी में MinOrDefault () को जोड़ने के लिए +1 जोड़ा जाएगा।
जे। एंड्रयू लॉफलिन

जवाबों:


54
decimal? result = (from Item itm in itemList
                  where itm.Amount != 0
                  select (decimal?)itm.Amount).Min();

में रूपांतरण पर ध्यान दें decimal?। यदि कोई नहीं है तो आपको एक खाली परिणाम मिलेगा (बस इस तथ्य के बाद कि - मैं मुख्य रूप से अपवाद को रोकने का तरीका बता रहा हूं)। मैंने इसके !=बजाय "गैर-शून्य" उपयोग किया >


दिलचस्प। मैं काम नहीं कर सकता कि यह एक खाली सूची से कैसे बचा जा सकता है, लेकिन मैं इसे एक कोशिश करूँगा
क्रिस सिम्पसन

7
यह प्रयास करें: decimal? result = (new decimal?[0]).Min();देता हैnull
मार्क Gravell

2
और शायद तब उपयोग ?? 0 वांछित परिणाम प्राप्त करने के लिए?
क्रिस्टोफ़र लेटटे

यह निश्चित रूप से काम करता है। मैंने इसे आज़माने के लिए सिर्फ एक यूनिट टेस्ट बनाया है, लेकिन मैं 5 मिनट काम करने जा रहा हूं, इसलिए चयन का परिणाम एक खाली सूची के बजाय एक एकल शून्य मान है (यह संभव है कि मेरी sql पृष्ठभूमि मुझे भ्रमित कर रही है )। इसके लिए धन्यवाद।
क्रिस सिम्पसन

1
@ फिर, अगर मैं इसे बदलता हूं: दशमलव परिणाम 1 = ..... मिन () ?? 0; यह भी काम करता है, इसलिए आपके इनपुट के लिए धन्यवाद।
क्रिस सिम्पसन

125

आप यह क्या चाहते हैं:

IEnumerable<double> results = ... your query ...

double result = results.MinOrDefault();

खैर, MinOrDefault()मौजूद नहीं है। लेकिन अगर हम इसे खुद लागू करते तो यह कुछ इस तरह दिखता:

public static class EnumerableExtensions
{
    public static T MinOrDefault<T>(this IEnumerable<T> sequence)
    {
        if (sequence.Any())
        {
            return sequence.Min();
        }
        else
        {
            return default(T);
        }
    }
}

हालांकि, इसमें कार्यक्षमता है System.Linqकि एक ही परिणाम का उत्पादन होगा (थोड़े अलग तरीके से):

double result = results.DefaultIfEmpty().Min();

यदि resultsअनुक्रम में कोई तत्व नहीं हैं, DefaultIfEmpty()तो एक अनुक्रम होगा जिसमें एक तत्व होगा - default(T)- जिसे आप बाद में कॉल कर सकते हैं Min()

यदि default(T)वह नहीं है जो आप चाहते हैं, तो आप अपना स्वयं का डिफ़ॉल्ट निर्दिष्ट कर सकते हैं:

double myDefault = ...
double result = results.DefaultIfEmpty(myDefault).Min();

अब, यह साफ है!


1
@ChristofferLette मैं टी की सिर्फ एक खाली सूची चाहता हूं, इसलिए मैंने मिन () के साथ किसी भी () का उपयोग करके समाप्त कर दिया। धन्यवाद!
एड्रियन मारिनिका

1
@ AdrianMar: BTW, क्या आपने एक अशक्त वस्तु का उपयोग डिफ़ॉल्ट के रूप में किया है?
क्रिस्टोफर लेटेस्ट

17
यहां उल्लिखित MinOrDefault कार्यान्वयन दो बार गणना करने योग्य है। यह मेमोरी-मेमोरी संग्रह के लिए कोई मायने नहीं रखता है, लेकिन LINQ से लेकर इकाई या आलसी "यील्ड रिटर्न" के लिए इन्यूमरेबल बनाया गया है, इसका अर्थ है डेटाबेस में दो राउंड-ट्रिप या पहले तत्व को दो बार संसाधित करना। मैं परिणाम पसंद करता हूं। DefaultIfEmpty (myDefault) .Min () समाधान।
केविन कूलॉमबे जुले

4
के लिए स्रोत को देखते हुए DefaultIfEmpty, यह वास्तव में स्मार्ट लागू किया गया है, केवल अनुक्रम का अग्रेषण करता है यदि yield returnएस का उपयोग करने वाले तत्व हैं ।
पीटर लिलवॉल्ड

2
@JDandChips आप उस के रूप से उद्धृत कर रहे हैं DefaultIfEmptyएक लेता है IEnumerable<T>। यदि आपने इसे एक पर बुलाया है IQueryable<T>, जैसे कि आपके पास एक डेटाबेस ऑपरेशन होगा, तो यह एक सिंगलटन अनुक्रम नहीं लौटाता है, लेकिन एक उचित उत्पन्न करता है MethodCallExpression, और इसलिए परिणामी क्वेरी को सब कुछ प्राप्त करने की आवश्यकता नहीं होती है। यहाँ सुझाए गए EnumerableExtensionsदृष्टिकोण में वह मुद्दा है।
जॉन हैना

16

एक छोटी राशि कोड में एक बार करने के मामले में सबसे साफ, जैसा कि पहले ही उल्लेख किया गया है:

decimal result = (from Item itm in itemList
  where itm.Amount > 0
    select itm.Amount).DefaultIfEmpty().Min();

कास्टिंग के साथ itm.Amountकरने के लिए decimal?और प्राप्त करने Minकि के neatest जा रहा है अगर हम इस खाली हालत पता लगाने में सक्षम होना चाहता हूँ।

हालांकि अगर आप वास्तव में प्रदान करना चाहते हैं MinOrDefault()तो हम निश्चित रूप से शुरू कर सकते हैं:

public static TSource MinOrDefault<TSource>(this IQueryable<TSource> source, TSource defaultValue)
{
  return source.DefaultIfEmpty(defaultValue).Min();
}

public static TSource MinOrDefault<TSource>(this IQueryable<TSource> source)
{
  return source.DefaultIfEmpty(defaultValue).Min();
}

public static TResult MinOrDefault<TSource, TResult>(this IQueryable<TSource> source, Expression<Func<TSource, TResult>> selector, TSource defaultValue)
{
  return source.DefaultIfEmpty(defaultValue).Min(selector);
}

public static TResult MinOrDefault<TSource, TResult>(this IQueryable<TSource> source, Expression<Func<TSource, TResult>> selector)
{
  return source.DefaultIfEmpty().Min(selector);
}

अब आपके पास एक MinOrDefaultचयनकर्ता शामिल है या नहीं, और क्या आप डिफ़ॉल्ट निर्दिष्ट करते हैं या नहीं, इसका एक पूरा सेट है ।

इस बिंदु पर आपके कोड से बस:

decimal result = (from Item itm in itemList
  where itm.Amount > 0
    select itm.Amount).MinOrDefault();

इसलिए, जबकि शुरू करने के लिए यह उतना साफ नहीं है, यह तब से नीच है।

लेकिन रुकें! अभी और है!

मान लीजिए कि आप EF का उपयोग करते हैं और asyncसमर्थन का उपयोग करना चाहते हैं । आसानी से किया:

public static Task<TSource> MinOrDefaultAsync<TSource>(this IQueryable<TSource> source, TSource defaultValue)
{
  return source.DefaultIfEmpty(defaultValue).MinAsync();
}

public static Task<TSource> MinOrDefaultAsync<TSource>(this IQueryable<TSource> source)
{
  return source.DefaultIfEmpty(defaultValue).MinAsync();
}

public static Task<TSource> MinOrDefaultAsync<TSource, TResult>(this IQueryable<TSource> source, Expression<Func<TSource, TResult>> selector, TSource defaultValue)
{
  return source.DefaultIfEmpty(defaultValue).MinAsync(selector);
}

public static Task<TSource> MinOrDefaultAsync<TSource, TResult>(this IQueryable<TSource> source, Expression<Func<TSource, TResult>> selector)
{
  return source.DefaultIfEmpty().MinAsync(selector);
}

(ध्यान दें कि मैं awaitयहां उपयोग नहीं करता हूं ; हम सीधे एक Task<TSource>ऐसा बना सकते हैं जो हमें इसके बिना की आवश्यकता है, और इसलिए छिपी हुई जटिलताओं से awaitबचाता है)।

लेकिन रुकिए, और भी है! मान लें कि हम IEnumerable<T>कुछ समय के साथ इसका उपयोग कर रहे हैं । हमारा दृष्टिकोण उप-इष्टतम है। निश्चित रूप से हम बेहतर कर सकते हैं!

सबसे पहले, Minपर परिभाषित int?, long?, float? double?और decimal?पहले से ही क्या हम वैसे भी चाहते हैं (मार्क Gravell का जवाब बनाता है के उपयोग के रूप में) है। इसी तरह, हमें वह व्यवहार भी मिलता है जिसे हम Minपहले से परिभाषित चाहते हैं यदि किसी अन्य के लिए कहा जाता हैT? । तो चलो कुछ छोटे, और इसलिए आसानी से इनलाइन, इस तथ्य का लाभ उठाने के तरीके:

public static TSource? MinOrDefault<TSource>(this IEnumerable<TSource?> source, TSource? defaultValue) where TSource : struct
{
  return source.Min() ?? defaultValue;
}
public static TSource? MinOrDefault<TSource>(this IEnumerable<TSource?> source) where TSource : struct
{
  return source.Min();
}
public static TResult? Min<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult?> selector, TResult? defaultValue) where TResult : struct
{
  return source.Min(selector) ?? defaultValue;
}
public static TResult? Min<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult?> selector) where TResult : struct
{
  return source.Min(selector);
}

अब पहले अधिक सामान्य मामले से शुरू करते हैं:

public static TSource MinOrDefault<TSource>(this IEnumerable<TSource> source, TSource defaultValue)
{
  if(default(TSource) == null) //Nullable type. Min already copes with empty sequences
  {
    //Note that the jitter generally removes this code completely when `TSource` is not nullable.
    var result = source.Min();
    return result == null ? defaultValue : result;
  }
  else
  {
    //Note that the jitter generally removes this code completely when `TSource` is nullable.
    var comparer = Comparer<TSource>.Default;
    using(var en = source.GetEnumerator())
      if(en.MoveNext())
      {
        var currentMin = en.Current;
        while(en.MoveNext())
        {
          var current = en.Current;
          if(comparer.Compare(current, currentMin) < 0)
            currentMin = current;
        }
        return currentMin;
      }
  }
  return defaultValue;
}

अब स्पष्ट ओवरराइड जो इसका उपयोग करते हैं:

public static TSource MinOrDefault<TSource>(this IEnumerable<TSource> source)
{
  var defaultValue = default(TSource);
  return defaultValue == null ? source.Min() : source.MinOrDefault(defaultValue);
}
public static TResult MinOrDefault<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector, TResult defaultValue)
{
  return source.Select(selector).MinOrDefault(defaultValue);
}
public static TResult MinOrDefault<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector)
{
  return source.Select(selector).MinOrDefault();
}

यदि हम प्रदर्शन के बारे में वास्तव में तेज हैं, तो हम कुछ मामलों के लिए अनुकूलन कर सकते हैं, जैसे Enumerable.Min()कि:

public static int MinOrDefault(this IEnumerable<int> source, int defaultValue)
{
  using(var en = source.GetEnumerator())
    if(en.MoveNext())
    {
      var currentMin = en.Current;
      while(en.MoveNext())
      {
        var current = en.Current;
        if(current < currentMin)
          currentMin = current;
      }
      return currentMin;
    }
  return defaultValue;
}
public static int MinOrDefault(this IEnumerable<int> source)
{
  return source.MinOrDefault(0);
}
public static int MinOrDefault<TSource>(this IEnumerable<TSource> source, Func<TSource, int> selector, int defaultValue)
{
  return source.Select(selector).MinOrDefault(defaultValue);
}
public static int MinOrDefault<TSource>(this IEnumerable<TSource> source, Func<TSource, int> selector)
{
  return source.Select(selector).MinOrDefault();
}

और के लिए पर इसलिए long, float, doubleऔर decimalके सेट से मेल करने के Min()द्वारा उपलब्ध कराई गई Enumerable। यह उस प्रकार की चीज़ है जहाँ T4 टेम्प्लेट उपयोगी हैं।

इन सभी के अंत में, हमारे पास बस उसी प्रकार के कार्यान्वयन के बारे में है MinOrDefault(), जिस प्रकार की एक विस्तृत श्रृंखला के लिए हम आशा कर सकते हैं। निश्चित रूप से इसके लिए एक उपयोग के चेहरे में "साफ" नहीं (फिर से, बस उपयोग करें DefaultIfEmpty().Min()), लेकिन बहुत अधिक "साफ" अगर हम खुद को इसका उपयोग करते हुए पाते हैं, तो हमारे पास एक अच्छा पुस्तकालय है जिसे हम पुन: उपयोग (या वास्तव में, पेस्ट कर सकते हैं) StackOverflow… पर उत्तर)।


0

यह दृष्टिकोण एकल से सबसे छोटा Amountमान लौटाएगा itemList। सिद्धांत रूप में यह डेटाबेस के लिए कई दौर की यात्राओं से बचना चाहिए

decimal? result = (from Item itm in itemList
                  where itm.Amount > 0)
                 .Min(itm => (decimal?)itm.Amount);

अशक्त संदर्भ अपवाद अब कारण नहीं है क्योंकि हम एक अशक्त प्रकार का उपयोग कर रहे हैं।

Anyकॉल करने से पहले निष्पादित तरीकों के उपयोग से बचने से Min, हमें केवल डेटाबेस में एक यात्रा करना चाहिए


1
आपको क्या लगता है कि Selectस्वीकृत उत्तर का उपयोग क्वेरी को एक से अधिक बार निष्पादित करेगा? स्वीकार किए गए उत्तर का परिणाम सिंगल DB कॉल होगा।
जॉन हैना

आप सही हैं, Selectएक आस्थगित तरीका है और निष्पादन का कारण नहीं होगा। मैंने अपने उत्तर से इन झूठों को हटा दिया है। संदर्भ: एडम फ्रीमैन (पुस्तक) द्वारा "प्रो ASP.NET MVC4"
JDandChips

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

-1

यदि आइटम नॉन-नॉलेबल है (जहां DefaultIfEmpty 0 देता है) और आप एक संभावित आउटपुट मान के रूप में शून्य चाहते हैं, तो आप लैम्ब्डा सिंटैक्स का भी उपयोग कर सकते हैं:

decimal? result = itemList.Where(x => x.Amount != 0).Min(x => (decimal?)x);
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.