2010-05-27 3 views
6

क्या ऐसी कोई चीज मौजूद है? या कोई भी कृपया सिफारिश कर सकता है कि मैं इस तरह के कंटेनर को कैसे कार्यान्वित कर सकता हूं?सी ++ कंटेनर आपको उन वस्तुओं को सॉर्ट करने की इजाजत देता है जब वे अंतिम बार पहुंचे थे?

मूल रूप से मैं एक std :: नक्शा जो उसके प्रमुख और युक्त आइटम के रूप में एक कस्टम डेटाप्रकार के रूप में एक 64 बिट पूर्णांक का उपयोग करता है।

मुझे समय-समय पर उन वस्तुओं को हटाने में सक्षम होना चाहिए जो सबसे इष्टतम तरीके से थोड़ी देर तक पहुंचे हैं। क्या किसी के पास इसके लिए कोई सुझाव है?

चियर्स

उत्तर

4

एक विचार: std::deque बनाए रखें जो आपके मानचित्र तत्व में एक पुनरावर्तक को मानचित्र तक पहुंचने पर आगे बढ़ता है। इसके बाद आप यह बताने के लिए डेक को आसानी से देख सकते हैं कि हाल ही में कौन से तत्वों का उपयोग किया गया है।

कुछ सी ++ स्केच (त्रुटि जांच के शून्य, बिंदु यह दर्शाता है कि नक्शा तक पहुंचने पर डेक अपडेट किया गया है, और आप बाद में मानचित्र को ट्रिम कर सकते हैं)।

class MyMap { 
    typedef std::map<int64_t, void *> Map; 
    Map m_map; 
    std::deque<Map::iterator> m_recentlyUsedItems; 

public: 
    void *getItem(int64_t key) { 
    Map::iterator it = m_map.find(key); 
    if (it == m_map.end()) { 
     return 0; 
    } 
    m_recentlyUsedItems.push_front(it); 
    return it->second; 
    } 

    void removeAllButMostRecentlyUsedItems(int n) { 
    std::deque<Map::iterator> it = m_recentlyUsedItems.begin(); 
    advance(it, n); 
    std::deque<Map::iterator> it2 = it; 
    for (; it2 != m_recentlyUsedItems.end(); ++it2) { 
     m_map.erase(*it2); 
    } 
    m_recentlyUsedItems.erase(it, m_recentlyUsedItems.end()); 
    } 
}; 
+0

धन्यवाद यह वही दिखता है जो मुझे चाहिए! – Stowelly

+0

यहां एक समस्या है। आप एक बार से अधिक एक आइटम का उपयोग करते हैं, तो Deque अपनी इटरेटर अपनी एकाधिक प्रतिलिपियाँ शामिल, एक ही आइटम दो बार मिटा करने की कोशिश कर में जिसके परिणामस्वरूप (अपरिभाषित व्यवहार के कारण) होगा। आपको आइटम को एक्सेस पर हटाने और फ्रंट पर फिर से डालने की आवश्यकता है। इसके लिए एक डेक की तुलना में एक सूची अधिक कुशल होगी। –

+0

एक डेक छोटे संग्रह (~ 100/~ 1000 आइटम) के लिए पर्याप्त तेज़ है। हम इसके बाद इटेटर (पॉइंटर्स) बात कर रहे हैं। –

5

एक प्राथमिकता कतार कि कतार के सिर पर हाल ही सबसे कम इस्तेमाल किया (LRU) आइटम स्थानों का प्रयोग करें। जब कोई आइटम एक्सेस किया जाता है, तो इसे हटा दें और वर्तमान टाइमस्टैम्प के विरुद्ध इसे दोबारा डालें। जब आप वस्तुओं की समयसीमा समाप्त करना चाहते हैं, तो उन्हें कतार के सिर से हटा दें।

मैं कहना चाहिए कि आप मानक priority_queue उपयोग नहीं कर सकते, क्योंकि ऐसा करना यादृच्छिक हटाने का समर्थन नहीं करता। आपको एक वेक्टर के साथ संयोजन के रूप में ढेर कार्यों का उपयोग करना होगा।

मुझे यह भी इंगित करना चाहिए कि एक्सेस पर किसी आइटम को अपडेट करना महंगा होगा (ओ (एन) निकालने के लिए तत्व ढूंढने के लिए)।

संपादित करें: कृपया इस सवाल का जवाब न दें। पुनर्विचार पर, यह करने का सबसे अच्छा तरीका नहीं है। (इसके अलावा, टिप्पणियां देखें।)

+1

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

1

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

यदि आप प्रदर्शन को बढ़ाने के लिए उस विचार का उपयोग करना चाहते हैं, तो आप अपने कंटेनर को कार्यान्वित कर सकते हैं। सबसे आसान दृष्टिकोण का मतलब डेटा संरचना बनाना है जिसे auto-sorting list के नाम से जाना जाता है। दरअसल, इसका मतलब है कि प्रत्येक एक्सेस ऑपरेशन आपकी सूची का एक्सेस तत्व बनाता है, यह नया सिर है। इस मामले में, अक्सर उपयोग किए जाने वाले तत्व शुरुआत के करीब रहते हैं, जिसके परिणामस्वरूप बेहतर खोज समय होता है।

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

1

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

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

यह दृष्टिकोण, के रूप में नक्शे में iterators हमेशा मान्य होना चाहिए डाटा स्टोर करने के लिए एक सूची का उपयोग करता है, तो यह एक Deque iterators एक डालने के बाद अवैध जा सकता है इस्तेमाल किया जाना चाहिए। सूची डेटा, कुंजी, जिस समय बनाई गई थी (अंतिम बार नहीं पहुंची गई) को स्टोर करने के लिए एक स्ट्रक्चर का उपयोग करती है और अंततः डेटा मौजूद है।

struct Record 
{ 
    KeyT key; 
    DataT data; 
    time_t createTime; 
    bool exists; 
}; 

यदि आपका डेटा स्थिर है और आप को बचाने के लिये हाल ही में पहुंचे तो आप struct के लिए एक उपयोग समय सदस्य जोड़ सकते हैं, और प्रत्येक बार इस तक पहुंच सूची के शीर्ष पर आइटम ले जाएं।

यहाँ मेरी कोड है, यह थोड़ा जटिल दिखता है, लेकिन यह मुख्य रूप से टेम्पलेट मापदंडों और एक पाठक लेखक ताला कारण होता है।

#include "BWThread/BWReadersWriterLock.h" 

#include <list> 
#include <map> 
#include <ctime> 
#include <memory> 
#include <boost/scoped_ptr.hpp> 

/** 
* This is a Generic Cache implementation. 
* 
* To implement a cache using this class create a new class 
* derived from CacheFactory and implement the lookup method. 
* If the datasource supports updating implement update and 
* remove method. 
* 
* EG 
* typedef NameCache DynamicCache<int, BWString>; 
* NameCacheFactory : NameCache::Factory 
* { 
* public: 
* virtual bool lookup(int, BWString *); 
* }; 
* 
* NameCache cache(new NameCacheFactory, "<cache name>"); 
* 
* -------------------------------------------------------- 
* Implementation note: 
* This class uses a list as an efficient way to remove stale items from 
* the cache. The map stores a key and an iterators to the data in the list. 
* The list and the map are updated together. 
*/ 

template <class KeyT, class DataT> 
class CacheFactory 
{ 
public: 
    virtual ~CacheFactory() {} 

    // Lookup the data for from the data source. 
    // Return true if the data is found. 
    virtual bool lookup(const KeyT & key, DataT * data) = 0; 

    // Update or insert the data in the data source. 
    // Return true if the data can be updated. 
    // Returning false means the cache is not updated either. 
    virtual bool update(const KeyT & key, const DataT & data) { return false; } 

    // Remove the data in the data source. 
    // Return true if the data can be deleted weather it exists or not. 
    // Returning false means the cache is not updated either. 
    virtual bool remove(const KeyT & key) { return false; } 
}; 


template <class KeyT, class DataT> 
class DynamicCache 
{ 
public: 
    typedef CacheFactory<KeyT, DataT> Factory; 

    DynamicCache(Factory * f, const std::string & name, time_t t = (5 * 60)) : 
     factory(f), timeout(t), lastClean(std::time(0)), lock(name + " DynamicCache") {} 

    /* 
    * Lookup a key in the cache, the cached version is returned if it is 
    * present and the value is not old. If the value is old or is not 
    * present then use the factory to create it and insert the value in the 
    * cache for future lookups. If the factory cannot create it cache this 
    * fact too so we will ignore future lookups. Afterwards any entries in 
    * the cache longer than timeout are removed. 
    * 
    * This is the main method and entry point for the cache. All locking is 
    * performed inside the child methods. 
    */ 
    bool lookup(const KeyT & key, DataT * data, time_t now = std::time(0)) 
    { 
     bool found = false; 
     FindStatus status = find(key, data, now); 

     switch(status & EntryStatus) { 
     case Found: 
      found = true; 
      break; 
     case Create: 
      found = build(key, data, now); 
      break; 
     } 
     if (status & CleanRequired) { 
      cleanOldEntries(now); 
     } 
     return found; 
    } 

    bool update(const KeyT & key, const DataT & data, time_t now = std::time(0)) 
    { 
     if (factory->update(key, data)) 
     { 
      Record record; 
      record.key = key; 
      record.createTime = now; 
      record.data = data; 
      record.exists = true; 

      BWReadersWriterLock::WriteLockGuard guard(lock, __FILE__, __LINE__); 

      updateEntry(key, record); 
      return true; 
     } 
     return false; 
    } 

    bool remove(const KeyT & key, time_t now = std::time(0)) 
    { 
     if (factory->remove(key)) 
     { 
      Record record; 
      record.key = key; 
      record.createTime = now; 
      record.exists = false; 

      BWReadersWriterLock::WriteLockGuard guard(lock, __FILE__, __LINE__); 

      updateEntry(key, record); 
      return true; 
     } 
     return false; 
    } 

    /** 
    * Return the size of the cache (only really useful for unit testing). 
    */ 
    size_t size() const 
    { 
     BWReadersWriterLock::ReadLockGuard guard(lock, __FILE__, __LINE__); 

     return map.size(); 
    } 

    Factory * getFactory() 
    { 
     return factory.get(); 
    } 
private: 
    // Cache record 
    struct Record 
    { 
     KeyT key; 
     DataT data; 
     time_t createTime; 
     bool exists; 
    }; 

    // Find and Clean status 
    // CleanRequired is part of this so that searching the cache and finding 
    // stale items in the cache can be automic (use a single readlock). 
    enum FindStatus { 
     None, 
     Found, 
     Create, //Add 
     NotExist, 
     EntryStatus=Found|Create|NotExist, 
     CleanRequired = 8 
    }; 

    typedef std::list<Record> List; 
    typedef typename List::iterator Iterator; 
    typedef std::map<KeyT, typename Iterator> Map; 


    // 
    // The following methods all use and require explicit locking. 
    // 

    FindStatus find(const KeyT & key, DataT * data, time_t now) 
    { 
     BWReadersWriterLock::ReadLockGuard guard(lock, __FILE__, __LINE__); 

     Iterator itr = getEntry(key); 
     if (isValid(itr) && !isOld(itr, now)) { 
      if (itr->exists) { 
       *data = itr->data; 
       return FindStatus(Found | cleanRequired(now)); 
      } 
      else { 
       return FindStatus(NotExist | cleanRequired(now)); 
      } 
     } 
     return FindStatus(Create | cleanRequired(now)); 
    } 

    bool build(const KeyT & key, DataT * data, time_t now) 
    { 
     Record record; 
     record.key = key; 
     record.createTime = now; 
     record.exists = factory->lookup(key, &record.data); 

     BWReadersWriterLock::WriteLockGuard guard(lock, __FILE__, __LINE__); 

     if (record.exists) { 
      *data = record.data; 
     } 

     updateEntry(key, record); 
     return record.exists; 
    } 

    void cleanOldEntries(time_t now) 
    { 
     BWReadersWriterLock::WriteLockGuard guard(lock, __FILE__, __LINE__); 

     lastClean = now; 
     time_t old = now - timeout; 

     typename List::reverse_iterator itr = list.rbegin(); 
     while(!list.empty() && list.back().createTime < old) { 
      removeEntry(getEntry(list.back().key)); 
     } 
    } 

    // 
    // The following methods don't use locking but require the calling 
    // method to already have aquired a lock. 
    // 

    Iterator getEntry(const KeyT & key) 
    { 
     typename Map::const_iterator itr = map.find(key); 
     if (itr != map.end()) { 
      return map.find(key)->second; 
     } 
     return list.end(); 
    } 

    bool updateEntry(const KeyT key, const Record & record) 
    { 
     Iterator itr = getEntry(key); 
     if (isValid(itr)) { 
      removeEntry(itr); 
     } 

     insertEntry(record); 
     return record.exists; 
    } 

    bool isValid(Iterator itr) const 
    { 
     typename List::const_iterator constItr(itr); 
     return constItr != list.end(); 
    } 

    bool isOld(Iterator itr, time_t now) const 
    { 
     // isOld or time_t has wrapped 
     return ((itr->createTime + timeout) < now) || (now < itr->createTime); 
    } 

    Iterator insertEntry(const Record & record) 
    { 
     list.push_front(record); 
     Iterator itr = list.begin(); 
     map.insert(typename Map::value_type(record.key, itr)); 
     return itr; 
    } 

    void removeEntry(Iterator itr) 
    { 
     map.erase(itr->key); 
     list.erase(itr); 
    } 

    FindStatus cleanRequired(time_t now) const 
    { 
     return (lastClean + timeout) < now ? CleanRequired : None; 
    } 

    List list; 
    Map map; 
    time_t timeout; 
    time_t lastClean; 
    boost::scoped_ptr<CacheFactory<KeyT, DataT> > factory; 
    mutable BWReadersWriterLock lock; 
}; 
2

मैं एकवचन अलग विचार का प्रस्ताव देने जा रहा हूं।

इष्टतम की समस्या यह है कि इसकी समझ प्राप्त करना मुश्किल है। खासकर: क्या आप तेजी से सफाई पाने के लिए पुनर्प्राप्ति ऑपरेशन को अपंग करना चाहते हैं? विशिष्ट सफाई आमतौर पर "डाउन टाइम" के दौरान की जाती है जब गति महत्वपूर्ण नहीं होती है, दूसरी ओर आप स्नैपी पुनर्प्राप्त कर सकते हैं (लूप आदि में पहुंच के लिए ...)

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

+0

जैसे मामलों में कम प्रकार आदि हो सकते हैं। यदि आप मानचित्र को साफ करने के लिए किसी अन्य थ्रेड का उपयोग करते हैं तो यह दृष्टिकोण ठीक है। यह काम नहीं करेगा यदि आप धागे को मानचित्र पर पहुंचने के लिए पुरानी संरचनाओं को साफ करने के लिए भी चाहते हैं। मुझे अतीत में बहुत धीमी गति से नक्शे मिलते हैं। मैंने 4000 बड़े structs के साथ एक नक्शा बदल दिया, जिसे आमतौर पर एक क्रमबद्ध वेक्टर के लिए अनुक्रमिक रूप से एक्सेस किया गया था। 45 सेकंड से 5 सेकेंड से कम समय तक यह बेहतर पहुंच समय, जबकि लुकअप समय पर ध्यान से प्रभाव नहीं डाल रहा है। – iain

+0

अनुक्रमिक एक्सेस के लिए सॉर्ट किए गए वैक्टर बेहतर होते हैं। लोकी :: AssocVector में Alexandrescu द्वारा लिया गया यह दृष्टिकोण है। यदि संरचना को अद्यतन नहीं किया जाता है तो यह अच्छी तरह से काम करता है, हालांकि प्रविष्टि पीड़ित होती है जब वस्तुओं की संख्या बड़े पैमाने पर हो जाती है (~ 100/~ 1000)।मल्टीथ्रेडिंग अभी तक एक और मुद्दा है, लेकिन मेरी बात जरूरतों के बारे में अधिक सावधानीपूर्वक विश्लेषण करने के लिए और अधिक थी। कोई भी "स्वयं-अद्यतन" अनुक्रम पुनर्प्राप्त करने के लिए आवश्यक रूप से धीमा है (केवल पढ़ने के लिए नहीं) जो धीमे साफ-सफाई की तुलना में प्रदर्शन पर बड़ा प्रभाव डाल सकता है। –

5

यहाँ यह कैसे किया जा सकता है, एक सूची का उपयोग कर के क्रम में हाल ही में पहुंचे आइटम स्टोर करने के लिए का चित्र है। सूची लगातार समय में अद्यतन किया जाता है, इसलिए वहाँ नक्शा एक्सेस (कुछ अन्य उत्तर जो प्रत्येक एक्सेस पर एक रेखीय खोज की आवश्यकता के विपरीत) के ऊपर कोई महत्वपूर्ण ओवरहेड है। मैंने इंटरफ़ेस को बहुत बुनियादी रखा है, और इसे बहुत अच्छी तरह से परीक्षण नहीं किया है।

template <typename KEY, typename VALUE> 
class Container 
{ 
public: 
    void Set(const KEY& key, const VALUE& value) 
    { 
     typename Map::iterator it = map.find(key); 
     if (it == map.end()) 
     { 
      list.push_front(it); 
      it = map.insert(std::make_pair(key, std::make_pair(value, list.begin()))).first; 
      list.front() = it; 
     } 
     else 
     { 
      it->second.first = value; 
      Accessed(it); 
     } 
    } 

    const VALUE* Get(const KEY& key) 
    { 
     typename Map::iterator it = map.find(key); 
     if (it == map.end()) 
      return 0; 

     Accessed(it); 
     return &it->second.first; 
    } 

    void Expire(std::size_t new_size) 
    { 
     while (list.size() > new_size) 
     { 
      map.erase(list.back()); 
      list.pop_back(); 
     } 
    } 

private: 
    // Needed to resolve the semicircular dependency on nested iterator types. 
    struct MapIterator; 

    typedef std::list<MapIterator> List; 
    typedef std::map<KEY, std::pair<VALUE, typename List::iterator> > Map; 

    struct MapIterator : Map::iterator 
    { 
     MapIterator(const typename Map::iterator& it) : Map::iterator(it) {} 
    }; 

    void Accessed(typename Map::iterator it) 
    { 
     list.erase(it->second.second); 
     list.push_front(it); 
     it->second.second = list.begin(); 
    } 

    Map map; 
    List list; 
}; 
+0

इस बहुत पूर्ण उत्तर के लिए धन्यवाद, यह एक शर्म की बात है कि मैं वोट नहीं दे सकता 2 जवाब एक ही समय में, यह बिल्कुल वही दिखता है जो मुझे चाहिए, कल के माध्यम से एक उचित रूप से देखेंगे, धन्यवाद! वास्तव में इस विचार को कार्यान्वित करने के लिए – Stowelly

+0

+1 –

1

तुम भी MCT library से linked_hash_map उपयोग कर सकते हैं। वास्तव में, इसके दस्तावेज़ में इस उपयोग के लिए एक नुस्खा है।

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