【问题标题】:In a hashmap/unordered_map, is it possible to avoid data duplication when the value already contains the key在 hashmap/unordered_map 中,当 value 已经包含 key 时,是否可以避免数据重复
【发布时间】:2012-11-17 07:59:23
【问题描述】:

给定以下代码:

struct Item
{
    std::string name;
    int someInt;
    string someString;
    Item(const std::string& aName):name(aName){}
};
std::unordered_map<std::string, Item*> items;
Item* item = new Item("testitem");
items.insert(make_pair(item.name, item);

项目名称将在内存中存储两次 - 一次作为 Item 结构的一部分,一次作为映射条目的键。是否可以避免重复?对于大约 1 亿条记录,这种开销会变得很大。

注意: 我需要在 Item 结构中包含名称,因为我使用 hashmap 作为另一个 Item-s 容器的索引,并且我无法访问映射的键值。

【问题讨论】:

  • 我认为Boost.MultiIndex提供了这种功能。
  • 您的物品最初存放在哪里?
  • @Luc Touraille:是的,我考虑过多索引,但我不使用 Boost。有没有一种方法可以独立使用多索引,而无需构建/链接整个 boost 库?
  • Boost.MultiIndex 是一个只有头文件的库。您只链接您实际使用的容器实例和一堆帮助模板(如散列函数)。 Boost 发行版包含一个名为 bcp 的工具,如果您只想将相关部分包含到版本控制中,它可以提取给定模块的依赖项。

标签: c++ stl hashmap unordered-map


【解决方案1】:

假设您用于存储项目的结构首先是一个简单的列表,您可以将其替换为 multi-indexed container

类似的东西(未经测试)应该满足您的要求:

typedef multi_index_container<
    Item,
    indexed_by<
        sequenced<>,
        hashed_unique<member<Item, std::string, &Item::name
    >
> itemContainer;

itemContainer items;

现在您可以按插入顺序访问项目,也可以按名称查找它们:

itemContainer::nth_index<0>::type & sequentialItems = items.get<O>();
// use sequentialItems as a regular std::list

itemContainer::nth_index<1>::type & associativeItems = items.get<1>();
// uses associativeItems as a regular std::unordered_set

根据您的需要,您也可以使用其他索引。

【讨论】:

    【解决方案2】:

    不,没有。你可以:

    • 不要将name 存储在Item 中并单独传递。
    • 创建ItemItemData,其字段与Item 相同,但名称和其中一个除外
      • std::pair&lt;std::string, ItemData&gt; 派生Item(= 类型的value_type)或
      • 使其可与该类型相互转换。
    • 使用对字符串的引用作为键。您应该能够使用std::reference_wrapper&lt;const std::string&gt; 作为键并在std::cref(value.name) 中传递键作为键,std::cref(std::string(whatever)) 用于搜索。您可能需要专门化 std::hash&lt;std::reference_wrapper&lt;const std::string&gt;&gt;,但这应该很容易。
    • 使用std::unordered_set,但它的缺点是查找会为查找创建虚拟Item
      • 当您实际上将 Item * 作为值类型时,您可以将名称移至基类并使用多态性来避免该缺点。
    • 创建自定义哈希映射,例如Boost.Intrusive

    【讨论】:

    • 我需要在 Item 结构中包含名称,我已经在 Denis Ermolin 的回答的评论中解释了它。从地图的 value_type 派生 Item 是个好主意,没想到这一点,我想它会解决我的问题。
    • @AlexanderVassilev:然而,值/指针的区别改变了一切!请修改问题!
    • 我已经添加了reference_wrapper 选项,它应该对你有用(它将有效地使用指向名称成员的指针作为键)。 unordered_set 选项也可以更好地使用指针。
    • @AlexanderVassilev: reference_wrapper 只包含一个指向目标类型的指针和一堆内联方法和运算符。原始指针的问题是它不允许使用指向临时的指针,但允许将临时传递给使用 const 左值引用并且可以使用指针的函数,因此 cref 帮助器将与临时对象一起使用。
    • @AlexanderVassilev:行得通。因为k 不是临时的。
    【解决方案3】:

    好的,既然你说你使用指针作为值,我特此让我的答案恢复活力。

    有点hacky,但应该可以。基本上你使用指针和自定义散列函数

    struct Item
    {
        std::string name;
        int someInt;
        string someString;
        Item(const std::string& aName):name(aName){}
    
        struct name_hash  
        { 
           size_t operator() (std::string* name)
           {
               std::hash<std::string> h;
               return h(*name);
           }
        };
    };
    std::unordered_map<std::string*, Item*, Item::name_hash> items;
    Item* item = new Item ("testitem");
    items.insert(make_pair(&(item->name), item);
    

    【讨论】:

      【解决方案4】:

      TL;DR如果您使用的是 libstdc++(与 gcc 一起提供),那么您已经可以了。

      有3种方式,2种是“简单”的:

      • 将您的对象拆分为两个键/值,并停止重复值中的键
      • 将您的对象存储在 unordered_set

      第三个比较复杂,除非你的编译器提供:

      • 使用引用计数的std::string 实现(例如libstdc++)

      在这种情况下,当您将std::string 复制到另一个时,内部缓冲区的引用计数器会增加...仅此而已。复制推迟到所有者之一请求修改的时间:Copy On Write

      【讨论】:

      • 与第三个相关:在Item 和key 中都使用std::shared_ptr&lt;std::string&gt;。当然,如果名称小于 shared_ptr 的开销,这无济于事,但同样,如果名称短于引用字符串的开销,则引用字符串也无济于事。我希望后者的开销更低。
      • @SteveJessop:这将是可移植的,尽管在那时,围绕shared_ptr 重新创建string 类而不是产生三重/四重分配的成本可能更低。
      • 您在为Item 数一个吗?我将其设置为双重/三重分配(一个用于字符串数据,一个用于字符串,如果您不使用make_shared,则另一个用于控制块)。但是同意,如果您在类中移动引用计数,那么您将保存分配,因为它是您计数的字符串数据而不是字符串。
      • 使用原始 std::string 指针不是更有效吗,前提是我会处理字符串的生命周期(即首先删除 hashmap 条目,然后才删除项目对象,包含字符串)。我这里真的需要效率,我会处理几亿件物品。
      • @AlexanderVassilev:是的,它会起作用,但更难做对,stringinsert 期间移动...
      【解决方案5】:

      不要在你的结构中存储std::string name 字段。无论如何,当您执行查找时,您已经知道名称字段。

      【讨论】:

      • 我使用 hashmap 作为索引。实际上它存储了指向项目的指针,而不是实际的项目,但想要一个简单的例子。这些项目也从另一个结构中引用,我无权访问地图的键。
      • @Steve Jessop:是的,很抱歉造成混乱,我已经更新了问题。 string* 将解决它。非常感谢
      猜你喜欢
      • 1970-01-01
      • 2010-09-08
      • 1970-01-01
      • 2013-03-23
      • 2018-11-01
      • 2016-11-09
      • 2012-01-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多