2010-02-08 12 views
6

से परिणाम प्राप्त करने के लिए LINQ का उपयोग करना मेरे पास एक LINQ कथन है जो संग्रह से शीर्ष एन रिकॉर्ड आईडी खींचता है और फिर एक और क्वेरी जो उन सभी रिकॉर्ड्स को खींचती है जिनके पास आईडी हैं। यह बहुत ही भद्दा और अक्षम महसूस करता है और अगर वहाँ एक और अधिक संक्षिप्त, LINQy रास्ताअन्य LINQ संग्रह

var records = cache.Select(rec => rec.Id).Distinct().Take(n); 

var results = cache.Where(rec => records.Contains(rec.Id)); 

FYI एक ही परिणाम प्राप्त करने के लिए हो सकता है मैं सोच रहा था - वहाँ एक ही आईडी है, जो के साथ कई रिकॉर्ड किया जाएगा क्यों है अलग() और क्यों मैं पहली जगह में एक साधारण टेक() का उपयोग नहीं कर सकता।

धन्यवाद!

उत्तर

4

इस तरह कुछ कैसे?

var results = cache.GroupBy(rec => rec.Id, rec => rec) 
        .Take(n) 
        .SelectMany(rec => rec); 
+0

यह बहुत अच्छा है। काम करता है। LINQy। शायद आपके मूल की तुलना में कोई तेज़ नहीं होने वाला, ग्रुपबी के साथ क्या होता है इसके आधार पर धीमा हो सकता है। – David

+0

यह हमेशा मूल की तुलना में थोड़ा धीमा होगा क्योंकि इसे पहली बार पूर्ण पास करना होगा; मूल * * * तत्वों को हिट करते समय रोक सकता है। यदि सूची छोटी है तो शायद कोई बड़ी समस्या नहीं है। – Aaronaught

+0

@Aaronaught: लेकिन मूल को दूसरी क्वेरी में पूर्ण पास करना है, * और * प्रत्येक चरण में 'कंटेन' लुकअप करना है। यह संभावित रूप से एक असली प्रदर्शन हत्यारा हो सकता है। बेशक, निश्चित रूप से जानने का एकमात्र तरीका असली दुनिया के डेटा के साथ बेंचमार्क करना है। – LukeH

0

हां, असुविधाजनक LINQ उपयोगकर्ता को अलग-अलग रिकॉर्ड प्राप्त करने के लिए सदस्य चुनने का समर्थन नहीं करता है। तो मैं इसके लिए अपने खुद के विस्तार विधि बनाने की सिफारिश:

/// <summary> 
    /// Returns a list with the ability to specify key(s) to compare uniqueness on 
    /// </summary> 
    /// <typeparam name="T">Source type</typeparam> 
    /// <param name="source">Source</param> 
    /// <param name="keyPredicate">Predicate with key(s) to perform comparison on</param> 
    /// <returns></returns> 
    public static IEnumerable<T> Distinct<T>(this IEnumerable<T> source, 
              Func<T, object> keyPredicate) 
    { 
     return source.Distinct(new GenericComparer<T>(keyPredicate)); 
    } 

और फिर एक सामान्य comparer, जो आप देखेंगे काफी सामान्य है बनाएँ।

public class GenericComparer<T> : IEqualityComparer<T> 
    { 
     private Func<T, object> _uniqueCheckerMethod; 

     public GenericComparer(Func<T, object> keyPredicate) 
     { 
      _uniqueCheckerMethod = keyPredicate; 
     } 

     #region IEqualityComparer<T> Members 

     bool IEqualityComparer<T>.Equals(T x, T y) 
     { 
      return _uniqueCheckerMethod(x).Equals(_uniqueCheckerMethod(y)); 
     } 

     int IEqualityComparer<T>.GetHashCode(T obj) 
     { 
      return _uniqueCheckerMethod(obj).GetHashCode(); 
     } 

     #endregion 
    } 

अब बस श्रृंखला अपने LINQ बयान:। वर रिकॉर्ड = cache.Select (आरईसी => rec.Id) .Distinct() (एन) ले लो;

var results = cache.Distinct(rec => rec.Id).Take(n)); 

hth

+0

मुझे नहीं लगता कि यह आपको एक ही परिणाम देगा। ऐसा लगता है कि यह आपको केवल एन परिणाम देगा - पहले एन आईडी (यानी संभवतः एन से अधिक) से मेल खाने वाली सभी वस्तुओं की बजाय प्रत्येक विशिष्ट आईडी वाला पहला आइटम – David

1

एक ही बात तुमने किया था, लेकिन ((एक पंक्ति में और साथ जुड़ें) के बजाय शामिल है):

var results = cache 
    .Select(rec => rec.Id) 
    .Distinct() 
    .Take(n) 
    .ToList() 
    .Join(cache, rec => rec, record => record.Id, (rec, record) => record); 
0

एक ही रास्ता है कि मैं यह कर के बारे में सोच सकते हैं एसक्यूएल में एक सबक्वायरी के साथ होगा, तो शायद दो LINQ प्रश्न भी होंगे ...
यह "महसूस करता है" अक्षम है ... है ना? शायद आप किसी ऐसे चीज के बारे में चिंता कर रहे हैं जो चिंता करने योग्य नहीं है। आप इसे शामिल करके एक पंक्ति में बना सकते हैं, लेकिन क्या यह स्पष्ट/बेहतर/अधिक कुशल है एक अलग सवाल है।

संपादित करें: Aaronaught द्वारा विस्तार विधि जवाब इस तरह काम करने के लिए बनाया जा सकता है:

public static IEnumerable<T> TakeByDistinctKey<T, TKey>(this IEnumerable<T> source, Func<T, TKey> keyFunc, int numKeys) { 
    if(keyFunc == null) { 
     throw new ArgumentNullException("keyFunc"); 
    } 

    List<TKey> keys = new List<TKey>(); 
    foreach(T item in source) { 
     TKey key = keyFunc(item); 
     if(keys.Contains(key)) { 
      // one if the first n keys, yield 
      yield return item; 
     } else if(keys.Count < numKeys) { 
      // new key, but still one of the first n seen, yield 
      keys.Add(key); 
      yield return item; 
     } 
     // have enough distinct keys, just keep going to return all of the items with those keys 
    } 
} 

हालांकि, GroupBy/SelectMany neatest लग रहा है। मैं उस के साथ जाऊंगा।

+0

आपकी एक्सटेंशन विधि अधिक कुशल होगी यदि आप कुंजी संग्रह के लिए '' सूची के बजाय 'हैशसेट ' का उपयोग करें। 'कंटेनर' लुकअप 'सूची ' के लिए ओ (एन) की तुलना में 'हैशसेट ' के लिए ओ (1) के करीब होना चाहिए। – LukeH

+0

मैंने दक्षता का परीक्षण नहीं किया है लेकिन दूसरे कथन में "शामिल" लुकअप ऐसा लगता है जैसे यह एक बाधा हो सकती है। यही वह है जो मेरे लिए चिपक रहा था। अधिकांशतः मुझे पता था कि वही काम करने के बेहतर तरीके होंगे और लोगों को क्या कहना होगा उत्सुक था। मुझे नहीं पता था कि मुझे बहुत अच्छे विचार मिलेंगे! :-) – Josh

+0

पूरी तरह से, धन्यवाद। मैं पहले सरल के साथ आगे बढ़ता हूं, फिर बाद में अनुकूलित करता हूं, लेकिन इस प्रकार की चीज़ (केवल पूरी तरह से सेट ऑपरेशंस) के लिए शायद किसी को सेट प्रकारों का उपयोग करना चाहिए। धन्यवाद – David

0

कोई निर्मित "Linqy" जिस तरह से (आप समूह कर सकता है, लेकिन यह बहुत अक्षम हो जाएगा), लेकिन इसका मतलब यह नहीं है कि आप अपने खुद के रास्ते नहीं बना सकते:

public static IEnumerable<T> TakeDistinctByKey<T, TKey>(
    this IEnumerable<T> source, 
    Func<T, TKey> keyFunc, 
    int count) 
{ 
    if (keyFunc == null) 
     throw new ArgumentNullException("keyFunc"); 
    if (count <= 0) 
     yield break; 

    int currentCount = 0; 
    TKey lastKey = default(TKey); 
    bool isFirst = true; 
    foreach (T item in source) 
    { 
     yield return item; 
     TKey key = keyFunc(item); 
     if (!isFirst && (key != lastKey)) 
      currentCount++; 
     if (currentCount > count) 
      yield break; 
     isFirst = false; 
     lastKey = key; 
    } 
} 

फिर आप इस के साथ यह आह्वान कर सकते हैं:

var items = cache.TakeDistinctByKey(rec => rec.Id, 20); 

आप समग्र चाबी या की तरह है कि आप आसानी से उपरोक्त विधि का विस्तार कर सकता है एक तर्क के रूप में एक IEqualityComparer<TKey> लेने के लिए कुछ भी है, तो।

यह भी ध्यान रखें कि यह कुंजी द्वारा क्रमबद्ध क्रम में तत्वों पर निर्भर करता है।वे नहीं हैं, तो आप या तो एल्गोरिथ्म बदल सकता है ऊपर एक सीधी गणना और अंतिम आइटम तुलना के बजाय एक HashSet<TKey> उपयोग करने के लिए, या इस के बजाय साथ यह आह्वान:

var items = cache.OrderBy(rec => rec.Id).TakeDistinctByKey(rec => rec.Id, 20); 

संपादित करें - मैं भी करना चाहते हैं इंगित करें कि एसक्यूएल में मैं प्रदर्शन की आवश्यकता के आधार पर ROW_NUMBER क्वेरी या रिकर्सिव सीटीई का उपयोग करता हूं - एक विशिष्ट + जॉइन सबसे प्रभावी विधि नहीं है। यदि आपका कैश क्रमबद्ध क्रम में है (या यदि आप इसे क्रमबद्ध क्रम में बदल सकते हैं) तो ऊपर दी गई विधि स्मृति और निष्पादन समय दोनों के मामले में सबसे सस्ती होगी।

+0

मुझे लगता है कि यह करीब है, लेकिन यह पहली कुंजी के साथ पहली (ऊपर) एन आइटम नहीं देगा ? मुझे लगता है कि यह करीब है, बस इसे बदलने के लिए कुंजी की एक सूची रखने के लिए इसे बदलने की आवश्यकता है, और सूची में एन कुंजी होने तक केवल उस सूची में नई कुंजी जोड़ें। कुंजी सूची से मेल खाने वाली पूरी सूची के माध्यम से चलते रहें (या जैसा कि बताया गया है, एन तक की एक नई कुंजी है)। अनुलेख मुझे लगता है कि यह करने का आपका तरीका अन्यथा अच्छा है :) – David

+0

@ डेविड - यह सुनिश्चित नहीं है कि आपका क्या मतलब है - जब तक कोई बग न हो, तो इस एक्सटेंशन को स्रोत में सभी आइटम को पहले एन विशिष्ट कुंजी के साथ वापस कर देना चाहिए (जब तक यह क्रमबद्ध होता है आदेश, अन्यथा यह एक ओ (एन) ऑपरेशन है और आपको एक हैश सेट की आवश्यकता है, इस मामले में शायद मैं 'ग्रुपबी'/'सिलेमेन' उत्तर के साथ जाऊंगा)। मुझे लगता है कि ओपी चाहता था ... क्या मैंने सवाल गलत तरीके से पढ़ा? – Aaronaught

+0

हां। मुझे नहीं लगता कि वे यही चाहते थे। आपका कोड केवल पूर्व-क्रमबद्ध सूची पर काम करेगा, यह पहली आईडी प्राप्त करेगा, दूसरी आईडी के साथ पहले आइटम को छोड़ देगा, और उसके बाद पहले एन आईडी के साथ सभी आइटमों की बजाय अधिकतम एन आइटम लौटाएं .. जब तक कि मैं ओपी गलत तरीके से नहीं कर रहा हूं। – David

संबंधित मुद्दे