【问题标题】:MFC dictionary collection with key unicity and ordering by position具有关键唯一性和按位置排序的 MFC 字典集合
【发布时间】:2012-03-08 21:32:44
【问题描述】:

看桌子 http://msdn.microsoft.com/en-us/library/y1z022s1%28v=vs.80%29.aspx#_core_collection_shape_features

我看不到满足我需要的 MFC 集合。

http://msdn.microsoft.com/en-us/library/s897094z%28v=vs.80%29.aspx 上的 CMap 文档也指出

“你可能认为这个迭代是键值顺序的,但不是。检索到的元素的顺序是不确定的。”

正如我所期望的那样,我认为它使用散列算法。

我需要的是一本具有以下功能的字典:

  • 有序(例如,通过 int)- 目的:交换 order 元素并按照所述顺序获取它们
  • 在我看来,这甚至不需要成为“真正的密钥”——我真的不需要那些疯狂的散列算法来快速访问。还有,按键访问元素的问题,我好像暂时不需要了,但是开始使用的时候我只能放心的回答了。
  • 不需要快速插入、删除、更新等。
  • 不需要快速搜索指定元素
  • 关键唯一性

我想知道我可以在实际应用中应用这种模式多少次。 您可以为此建议我的最佳解决方案是什么?

注意:不久前,我在 C# 中也遇到了同样的问题。也欢迎它的解决方案。如果我没记错 SortedDictionary 是按 key 排序的,那么它是不合适的。

编辑:即使最好 - 只是为了不与已经存在的代码库不和谐 - 使用 MFC 中的东西,这不是义务。因此,如果它是标准 C++,您可以提出任何您想要的建议。

EDIT2:为了提高清晰度:容器的每个元素的描述将是

{
     int Unique NonNullable OrderIndex,
     enum KeyEnum Unique NonNullable key, 
     enum ValueEnum NotUnique NonNullable value
}

如果它被实现为动态数组,我什至不关心存储 OrderIndex。对于这个,我真的只需要一个唯一的值来指示相对位置并且可以交换位置元素。

提前致谢,

塞尔吉奥

【问题讨论】:

标签: mfc dictionary


【解决方案1】:

我决定从 CMap 和 CArray 派生一个模板类,并编写 ArrayMapTempl.h 文件如下:

//Author: Sérgio Loureiro. This is open source.

#include <afxtempl.h>

template<class KEY, class ARG_KEY, class VALUE, class ARG_VALUE>
class CArrayMap: protected CArray<KEY,ARG_KEY>, protected CMap<KEY, ARG_KEY, VALUE, ARG_VALUE>
{
private:
    bool m_isChangingSize;
public:
    CArrayMap():
      m_isChangingSize(false)
    {
    };

    CArrayMap(const CArrayMap& src){};
    CArrayMap operator=(const CArrayMap& src){return *this;};

    // Attributes
    INT_PTR GetSize() const;
    INT_PTR GetCount() const;
    BOOL IsEmpty() const;
    INT_PTR GetUpperBound() const;

    // Lookup
    int Lookup(ARG_KEY key) const;
    int Lookup(ARG_KEY key, VALUE& rValue) const;

// Operations
    // Clean up
    void RemoveAll();

    // Accessing elements
    const CPair& GetAt(INT_PTR nIndex) const;
    CPair& GetAt(INT_PTR nIndex);
    void SetAt(INT_PTR nIndex, ARG_KEY newKey, ARG_VALUE newValue);
    const CPair& ElementAt(INT_PTR nIndex) const;
    CPair& ElementAt(INT_PTR nIndex);

    // Direct Access to the element data
    const CPair& GetData() const;
    CPair& GetData();

    // Potentially growing the array
    INT_PTR Add(ARG_KEY newKey, ARG_VALUE newValue);
    void Copy(const CArrayMap& src);

    // overloaded operator helpers
    const CPair& operator[](INT_PTR nIndex) const;
    CPair& operator[](INT_PTR nIndex);

    // Operations that move elements around

    BOOL Swap(INT_PTR nIndex, INT_PTR nIndex2);
    BOOL MoveUp(INT_PTR nIndex);
    BOOL MoveDown(INT_PTR nIndex);

    BOOL SwapByKey(ARG_KEY key, ARG_KEY key2);
    BOOL MoveUpByKey(ARG_KEY key);
    BOOL MoveDownByKey(ARG_KEY key);

    BOOL InsertAt(INT_PTR nIndex, ARG_KEY newKey, ARG_VALUE newValue);
    BOOL RemoveAt(INT_PTR nIndex);
    BOOL RemoveByKey(ARG_KEY key);


public:
    void Serialize(CArchive&);
#ifdef _DEBUG
    void Dump(CDumpContext&) const;
    void AssertValid() const;
#endif

#if 0   
public:
// Construction
    CArray();

// Attributes
    INT_PTR GetSize() const;
    INT_PTR GetCount() const;
    BOOL IsEmpty() const;
    INT_PTR GetUpperBound() const;
    void SetSize(INT_PTR nNewSize, INT_PTR nGrowBy = -1);

// Operations
    // Clean up
    void FreeExtra();
    void RemoveAll();

    // Accessing elements
    const TYPE& GetAt(INT_PTR nIndex) const;
    TYPE& GetAt(INT_PTR nIndex);
    void SetAt(INT_PTR nIndex, ARG_TYPE newElement);
    const TYPE& ElementAt(INT_PTR nIndex) const;
    TYPE& ElementAt(INT_PTR nIndex);

    // Direct Access to the element data (may return NULL)
    const TYPE* GetData() const;
    TYPE* GetData();

    // Potentially growing the array
    void SetAtGrow(INT_PTR nIndex, ARG_TYPE newElement);
    INT_PTR Add(ARG_TYPE newElement);
    INT_PTR Append(const CArray& src);
    void Copy(const CArray& src);

    // overloaded operator helpers
    const TYPE& operator[](INT_PTR nIndex) const;
    TYPE& operator[](INT_PTR nIndex);

    // Operations that move elements around
    void RemoveAt(INT_PTR nIndex, INT_PTR nCount = 1);
    void InsertAt(INT_PTR nStartIndex, CArray* pNewArray);

// Implementation
protected:
    TYPE* m_pData;   // the actual array of data
    INT_PTR m_nSize;     // # of elements (upperBound - 1)
    INT_PTR m_nMaxSize;  // max allocated
    INT_PTR m_nGrowBy;   // grow amount

public:
    ~CArray();
    void Serialize(CArchive&);
#ifdef _DEBUG
    void Dump(CDumpContext&) const;
    void AssertValid() const;
#endif
#endif

//----------------------------------------------------------------------------------------
#if 0
public:
    // CPair
    struct CPair
    {
        const KEY key;
        VALUE value;
    protected:
        CPair( ARG_KEY keyval ) : key( keyval ) {}
    };

protected:
    // Association
    class CAssoc : public CPair
    {
        friend class CMap<KEY,ARG_KEY,VALUE,ARG_VALUE>;
        CAssoc* pNext;
        UINT nHashValue;  // needed for efficient iteration
    public:
        CAssoc( ARG_KEY key ) : CPair( key ) {}
    };

public:
// Construction
    /* explicit */ CMap(INT_PTR nBlockSize = 10);

// Attributes
    // number of elements
    INT_PTR GetCount() const;
    INT_PTR GetSize() const;
    BOOL IsEmpty() const;

    // Lookup
    BOOL Lookup(ARG_KEY key, VALUE& rValue) const;
    const CPair *PLookup(ARG_KEY key) const;
    CPair *PLookup(ARG_KEY key);

// Operations
    // Lookup and add if not there
    VALUE& operator[](ARG_KEY key);

    // add a new (key, value) pair
    void SetAt(ARG_KEY key, ARG_VALUE newValue);

    // removing existing (key, ?) pair
    BOOL RemoveKey(ARG_KEY key);
    void RemoveAll();

    // iterating all (key, value) pairs
    POSITION GetStartPosition() const;

    const CPair *PGetFirstAssoc() const;
    CPair *PGetFirstAssoc();

    void GetNextAssoc(POSITION& rNextPosition, KEY& rKey, VALUE& rValue) const;

    const CPair *PGetNextAssoc(const CPair *pAssocRet) const;
    CPair *PGetNextAssoc(const CPair *pAssocRet);

    // advanced features for derived classes
    UINT GetHashTableSize() const;
    void InitHashTable(UINT hashSize, BOOL bAllocNow = TRUE);

// Implementation
protected:
    CAssoc** m_pHashTable;
    UINT m_nHashTableSize;
    INT_PTR m_nCount;
    CAssoc* m_pFreeList;
    struct CPlex* m_pBlocks;
    INT_PTR m_nBlockSize;

    CAssoc* NewAssoc(ARG_KEY key);
    void FreeAssoc(CAssoc*);
    CAssoc* GetAssocAt(ARG_KEY, UINT&, UINT&) const;

public:
    ~CMap();
    void Serialize(CArchive&);
#ifdef _DEBUG
    void Dump(CDumpContext&) const;
    void AssertValid() const;
#endif
#endif

};

template <class KEY, class ARG_KEY, class VALUE, class ARG_VALUE>
inline INT_PTR CArrayMap<KEY, ARG_KEY, VALUE, ARG_VALUE>::GetSize() const
{
    ASSERT(CArray::m_nSize == CMap::m_nCount);
    return CArray::GetSize();
};


template <class KEY, class ARG_KEY, class VALUE, class ARG_VALUE>
inline INT_PTR CArrayMap<KEY, ARG_KEY, VALUE, ARG_VALUE>::GetCount() const
{
    ASSERT(CArray::m_nSize == CMap::m_nCount);
    return CArray::GetCount();
}


template <class KEY, class ARG_KEY, class VALUE, class ARG_VALUE>
inline BOOL CArrayMap<KEY, ARG_KEY, VALUE, ARG_VALUE>::IsEmpty() const
{ 
    ASSERT(CArray::m_nSize == CMap::m_nCount);
    return CArray::IsEmpty();
}

template <class KEY, class ARG_KEY, class VALUE, class ARG_VALUE>
inline INT_PTR CArrayMap<KEY, ARG_KEY, VALUE, ARG_VALUE>::GetUpperBound() const
{ 
    ASSERT(CArray::m_nSize == CMap::m_nCount);
    return CArray::GetUpperBound();
}


template <class KEY, class ARG_KEY, class VALUE, class ARG_VALUE>
inline INT_PTR CArrayMap<KEY, ARG_KEY, VALUE, ARG_VALUE>::Add(ARG_KEY newKey, ARG_VALUE newValue)
{ 
    VALUE rValue;
    if( CMap::Lookup(newKey,rValue))    //already exists
        return -1;

    INT_PTR nIndex = CArray::m_nSize;   //old size will be the new position

    m_isChangingSize= true;
    CMap::operator[] (newKey)= newValue;
    CArray::Add(newKey);
    m_isChangingSize= false;

    ASSERT(CArray::m_nSize == CMap::m_nCount);
    return nIndex;
};


template <class KEY, class ARG_KEY, class VALUE, class ARG_VALUE>
inline const typename CMap<KEY, ARG_KEY, VALUE, ARG_VALUE>::CPair&
    CArrayMap<KEY, ARG_KEY, VALUE, ARG_VALUE>::GetAt(INT_PTR nIndex) const
{ 
    ASSERT(nIndex >= 0 && nIndex < m_nSize);
    if(nIndex >= 0 && nIndex < m_nSize)
    {
        return *CMap::PLookup(CArray::GetAt(nIndex));
    }
    AfxThrowInvalidArgException();
};


template <class KEY, class ARG_KEY, class VALUE, class ARG_VALUE>
inline typename CMap<KEY, ARG_KEY, VALUE, ARG_VALUE>::CPair&
    CArrayMap<KEY, ARG_KEY, VALUE, ARG_VALUE>::GetAt(INT_PTR nIndex)
{ 
    ASSERT(nIndex >= 0 && nIndex < m_nSize);
    if(nIndex >= 0 && nIndex < m_nSize)
    {
        return *CMap::PLookup(CArray::GetAt(nIndex));
    }
    AfxThrowInvalidArgException();
};

template<class KEY, class ARG_KEY, class VALUE, class ARG_VALUE>
int CArrayMap<KEY, ARG_KEY, VALUE, ARG_VALUE>::Lookup(ARG_KEY key) const
{
    VALUE rValue;
    return this->Lookup(key, rValue);
};

template<class KEY, class ARG_KEY, class VALUE, class ARG_VALUE>
int CArrayMap<KEY, ARG_KEY, VALUE, ARG_VALUE>::Lookup(ARG_KEY key, VALUE& rValue) const
{
    for (int i=0; i<m_nSize ;i++)
    {
        if(CArray::operator [] ( i ) == key && CMap::Lookup(key, rValue))
        {
            return i;
        }
    }
    return -1;
};


template <class KEY, class ARG_KEY, class VALUE, class ARG_VALUE>
void CArrayMap<KEY, ARG_KEY, VALUE, ARG_VALUE>::RemoveAll()
{
    m_isChangingSize=true;
    CMap::RemoveAll();
    CArray::RemoveAll();
    m_isChangingSize=false;

    ASSERT(CArray::m_nSize == CMap::m_nCount);
};



template <class KEY, class ARG_KEY, class VALUE, class ARG_VALUE>
BOOL CArrayMap<KEY, ARG_KEY, VALUE, ARG_VALUE>::Swap(INT_PTR nIndex, INT_PTR nIndex2)
{
    if(nIndex<0 || nIndex2<0)
        return FALSE;

    if(nIndex>=m_nSize || nIndex2>=m_nSize)
        return FALSE;

    //Swap with itself. Everything is fine and nothing needs to be done
    if(nIndex == nIndex2)
        return TRUE;

    KEY k= CArray::GetAt(nIndex);
    CArray::SetAt(nIndex, CArray::GetAt(nIndex2));
    CArray::SetAt(nIndex, k);
};

template <class KEY, class ARG_KEY, class VALUE, class ARG_VALUE>
BOOL CArrayMap<KEY, ARG_KEY, VALUE, ARG_VALUE>::MoveUp(INT_PTR nIndex)
{
    if (nIndex == 0)
        return FALSE;
    return Swap(nIndex,nIndex-1);
};

template <class KEY, class ARG_KEY, class VALUE, class ARG_VALUE>
BOOL CArrayMap<KEY, ARG_KEY, VALUE, ARG_VALUE>::MoveDown(INT_PTR nIndex)
{
    if (nIndex == m_nSize-1)
        return FALSE;
    return Swap(nIndex,nIndex+1);
};

template <class KEY, class ARG_KEY, class VALUE, class ARG_VALUE>
BOOL CArrayMap<KEY, ARG_KEY, VALUE, ARG_VALUE>::SwapByKey(ARG_KEY key, ARG_KEY key2)
{
    int nIndex= Lookup(key);
    int nIndex2= Lookup(key2);
    if(nIndex == -1 || nIndex2 == -1)
        return FALSE;

    return Swap(nIndex,nIndex2);
}

template <class KEY, class ARG_KEY, class VALUE, class ARG_VALUE>
BOOL CArrayMap<KEY, ARG_KEY, VALUE, ARG_VALUE>::MoveUpByKey(ARG_KEY key)
{
    int nIndex= Lookup(key);
    if(nIndex == -1)
        return FALSE;

    return MoveUp(nIndex);
}

template <class KEY, class ARG_KEY, class VALUE, class ARG_VALUE>
BOOL CArrayMap<KEY, ARG_KEY, VALUE, ARG_VALUE>::MoveDownByKey(ARG_KEY key)
{
    int nIndex= Lookup(key);
    if(nIndex == -1)
        return FALSE;

    return MoveDown(nIndex);
}



template <class KEY, class ARG_KEY, class VALUE, class ARG_VALUE>
int CArrayMap<KEY, ARG_KEY, VALUE, ARG_VALUE>::InsertAt(INT_PTR nIndex,ARG_KEY newKey, ARG_VALUE newValue)
{

    AssertValid();          //ASSERT_VALID(this);

    if(nIndex < 0)
        return FALSE;   //AfxThrowInvalidArgException();

    if(nIndex > m_nSize)    //doesn't make sense to grow more than last+1 , given newKey has to be unique
        return FALSE;

    //I am not using this->Lookup(ARG_KEY key), because I 
    //presume CMap::Lookup(ARG_KEY key, VALUE& rValue) will be faster,
    //as it does not need to traverse the array.    

    VALUE rValue;
    if(CMap::Lookup(newKey,rValue)) //already exists
        return FALSE;

    m_isChangingSize=true;
    CMap::operator[] (newKey)= newValue;
    CArray::InsertAt(nIndex,newKey,1);
    m_isChangingSize=false;

    ASSERT(CArray::m_nSize == CMap::m_nCount);
    return TRUE;
}


template <class KEY, class ARG_KEY, class VALUE, class ARG_VALUE>
BOOL CArrayMap<KEY, ARG_KEY, VALUE, ARG_VALUE>::RemoveAt(INT_PTR nIndex)
{
    if(nIndex<0 || nIndex>= m_nSize)
        return FALSE;

    KEY k= CArray::GetAt(nIndex);

    //I am not using this->Lookup(ARG_KEY key), because I 
    //presume CMap::Lookup(ARG_KEY key, VALUE& rValue) will be faster,
    //as it does not need to traverse the array.    

    VALUE rValue;
    if(CMap::Lookup(k,rValue))  //already exists
    {
        m_isChangingSize= true;
        CMap::RemoveKey(k);
        CArray::RemoveAt(nIndex);
        m_isChangingSize= false;

        ASSERT(CArray::m_nSize == CMap::m_nCount);
        return TRUE;
    }
    else
        return FALSE;
};

template <class KEY, class ARG_KEY, class VALUE, class ARG_VALUE>
BOOL CArrayMap<KEY, ARG_KEY, VALUE, ARG_VALUE>::RemoveByKey(ARG_KEY key)
{
    int nIndex= Lookup(key);
    if(nIndex == -1)
        return FALSE;

    KEY k= CArray::GetAt(nIndex);

    return RemoveAt(nIndex);
};


template<class KEY, class ARG_KEY, class VALUE, class ARG_VALUE>
void CArrayMap<KEY, ARG_KEY, VALUE, ARG_VALUE>::Serialize(CArchive& ar)
{
    ASSERT(CArray::m_nSize == CMap::m_nCount);
    //ASSERT_VALID((const CArray *)this);
    //ASSERT_VALID((const CMap *)this);

    CObject::Serialize(ar);

    if (ar.IsStoring())
    {
        ar.WriteCount(m_nSize);
        if (m_nSize == 0)
            return;  // nothing more to do

        for(INT_PTR i=0;i<m_nSize;i++)
        {
            KEY* pKey;
            VALUE* pValue;
            /* 
             * in some cases the & operator might be overloaded, and we cannot use it to 
             * obtain the address of a given object.  We then use the following trick to 
             * get the address
             */
            pKey = reinterpret_cast< KEY* >( &reinterpret_cast< int& >( const_cast< KEY& > ( static_cast< const KEY& >( CArray::operator[]( i ) ) ) ) );
            pValue = reinterpret_cast< VALUE* >( &reinterpret_cast< int& >( static_cast< VALUE& >( CMap::operator[]( *pKey ) ) ) );
            SerializeElements<KEY>(ar, pKey, 1);
            SerializeElements<VALUE>(ar, pValue, 1);
        }
    }
    else
    {
        DWORD_PTR nNewCount = ar.ReadCount();
        while (nNewCount--)
        {
            KEY newKey[1];
            VALUE newValue[1];
            SerializeElements<KEY>(ar, newKey, 1);
            SerializeElements<VALUE>(ar, newValue, 1);
            this->Add(newKey[0], newValue[0]);  //includes checking if it already exists
        }
    }
}

#ifdef _DEBUG
template<class KEY, class ARG_KEY, class VALUE, class ARG_VALUE>
void CArrayMap<KEY, ARG_KEY, VALUE, ARG_VALUE>::Dump(CDumpContext& dc) const
{
    ASSERT(CArray::m_nSize == CMap::m_nCount);

    CObject::Dump(dc);

    dc << "with " << m_nSize << " elements";
    if (dc.GetDepth() > 0)
    {
        // Dump in format "[key] -> value"
        KEY key[1];
        VALUE val[1];

        POSITION pos = GetStartPosition();
        while (pos != NULL)

        for (int i=0; i<m_nSize; i++)
        {
            key[0]= CArray::operator[]( i );

            //val[0]= CMap::operator[]( key[0] );
            CMap::Lookup(key[0], val[0]);

            dc << "\n\t[";
            DumpElements<KEY>(dc, key, 1);
            dc << "] = ";
            DumpElements<VALUE>(dc, val, 1);
        }
    }

    dc << "\n";
}

template<class KEY, class ARG_KEY, class VALUE, class ARG_VALUE>
void CArrayMap<KEY, ARG_KEY, VALUE, ARG_VALUE>::AssertValid() const
{
    CArray::AssertValid();
    CMap::AssertValid();

    if(!m_isChangingSize)   
        ASSERT(CArray::m_nSize == CMap::m_nCount);
}

#endif //_DEBUG

现在我拥有了我需要的一切。我没有测试一切,但我相信如果需要的话,只需要很少的修正。

无论如何,谢谢大家的回答和cmets。

【讨论】:

    【解决方案2】:

    使用数组并将数组中的索引存储为值的元素。如果需要,请使用另一个 CMap 进行快速哈希访问。我知道和听到的所有地图都不支持订单索引号。

    【讨论】:

    • 我所做的是一个从 CArray 和 CMap 都具有多重继承的类。它实际上已经完成,只缺少最糟糕的部分:测试它。
    【解决方案3】:

    在 C++ Freenode 的 IRC 频道上进行了这次对话后,我很想尝试 Boost Multi index(zxvf 是我):

    zxvf            hello
    zxvf            I was trying to solve the problem of this question: http://stackoverflow.com/questions/9329011/mfc-dictionary-collection-with-key-unicity-and-ordering-by-position/9438561#9438561
    zxvf            what kind of container would you recommend to me?
    TinoDidriksen   std::vector sounds fitting.
    zxvf            you can not guarantee unicity of keyEnum, TinoDidriksen
    MrBar       zxvf: so use std::map then
    zxvf            MrBar: I think you all didn't get the point of the problem
    evilissimo  I think you'd need something like boost multi_index
    zxvf            I want both unicity of KeyEnum and OrderIndex 
    MrBar       zxvf: i get point, but that container u asked about not exist
    TinoDidriksen   zxvf, Boost.Multi_index then.
    evilissimo  zxvf, by unicity you mean that there are at no given time two of any of them in the container?
    evilissimo  or by the combination?
    MrBar       zxvf: u need to use intermix of containers
    zxvf            and NO, I don't want unicity of the pair <OrderIndex, KeyEnum> but unicity of each one
    evilissimo  zxvf, had to ask
                    zxvf seeing what the heck is Boost Multi index
    MrBar       zxvf: noooo, not see in boost there no back way
    zxvf            MrBar, could you rephrase please?
    zxvf            Not english native speaker, me
    evilissimo  zxvf, that wasn't even valid english
    MrBar       zxvf: hmm i too
    evilissimo  zxvf, basically there are tons of useful stuff in boost, and usually once you start loving one library you start using more of them 
    zxvf            thanks, people. It's aready late here and I will not see this tonight. But seems to me that Boost Multi index is a very good help for me
    

    【讨论】:

    【解决方案4】:

    当您说“有序”时,您已经间接暗示“订单索引”是唯一的“键”。为什么不能使用 std::vector?

    如果您想拥有另一个唯一键,则可以使用 std::map ,但您必须即兴发挥“顺序”作为键/值组合的一部分。我看到最终的解决方案将是复合的。您将拥有自己的类来支持您想要的内容,在 tern 中可以 std::map 来实现这个或任何其他包含类似列表的内容。

    【讨论】:

    • 评论提高人们传入的清晰度:我想要 KeyEnum 和 OrderIndex 的唯一性;不,我不想要一对 的唯一性,而是每个人的唯一性。
    【解决方案5】:

    MFC 不提供满足您需要的容器,但您可以改用std::mapstd::set。每个都将保持密钥唯一;您选择哪个取决于您是希望将密钥与有效负载分开还是集成到单个对象中。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-03-15
      • 2021-11-27
      • 2011-06-17
      • 1970-01-01
      相关资源
      最近更新 更多