【问题标题】:Does C++ have ordered hash?C++ 是否有有序哈希?
【发布时间】:2015-12-08 23:30:24
【问题描述】:

Perl 有一个名为 “有序哈希” Tie::IxHash 的结构。可以将其用作哈希表/地图。条目按插入顺序排列。

想知道C++中是否有这样的东西。

这是一个示例 Perl sn-p:

use Tie::IxHash;

tie %food_color, "Tie::IxHash";
$food_color{Banana} = "Yellow";
$food_color{Apple}  = "Green";
$food_color{Lemon}  = "Yellow";

print "In insertion order, the foods are:\n";
foreach $food (keys %food_color) {
    print "  $food\n"; #will print the entries in order
}

更新 1

正如@kerrek-sb 指出的那样,可以使用 Boost 多索引容器库。只是想知道是否可以使用 STL 来做到这一点。

【问题讨论】:

  • 听起来像是 Boost.Multiindex 的工作。
  • 谢谢。很高兴知道。想知道它是否可以在纯 C++11 中使用。
  • 这是什么意思?它是一个 C++ 库,因此您可以将它与 C++ 一起使用。
  • @KerrekSB 这应该意味着没有 Boost.Multiindex 依赖。在某些环境中,引入外部依赖项并不容易,然后必须对其进行更新和跟踪。
  • 评论更新:不是直接用一个容器。但是通过矢量和地图(或 unordered_map)的组合,您可以相对轻松地获得相同的结果。

标签: c++ perl


【解决方案1】:

是的,不是的。不,没有一个专门用于提供完全相同的功能。但是,是的,您可以通过几种不同的方式来做同样的事情。如果您希望主要按照插入的顺序访问数据,那么显而易见的方法是使用简单的对向量:

std::vector<std::string, std::string> food_colors;

food_colors.push_back({"banana", "yellow"});
food_colors.push_back({"apple", "green"});
food_colors.push_back({"lemon", "yellow"});

for (auto const &f : food_colors)
    std::cout << f.first << ": " << f.second << "\n";

这通过简单地按顺序存储项目来保持顺序。如果您需要按键访问它们,您可以使用std::find 对特定项目进行线性搜索。这样可以最大限度地减少使用的额外内存,但如果您获得大量项目,则会以键访问速度缓慢为代价。

如果您希望通过键更快地访问大量项目,您可以使用 Boost MultiIndex。如果你真的想避免这种情况,你可以很容易地创建自己的索引。为此,您首先将项目插入std::unordered_map(或者可能是std::map)。这可以通过键快速访问,但不能按插入顺序访问。然而,当它被插入到地图中时,它确实会为每个项目返回一个迭代器。您可以简单地将这些迭代器存储到一个向量中,以便按插入顺序进行访问。虽然这个原理很简单,但是代码有点笨拙,说得好听点:

std::map<std::string, std::string> fruit;
std::vector<std::map<std::string, std::string>::iterator> in_order;

in_order.push_back(fruit.insert(std::make_pair("banana", "yellow")).first);
in_order.push_back(fruit.insert(std::make_pair("apple", "green")).first);
in_order.push_back(fruit.insert(std::make_pair("lemon", "yellow")).first);

这允许通过密钥访问:

// ripen the apple:
fruit["apple"] = "red";

...或按插入顺序:

for (auto i : in_order)
    std::cout << i->first << ": " << i->second << "\n";

目前,我已经展示了执行此操作的基本机制——如果您想大量使用它,您可能希望将其包装到一个不错的类中以隐藏一些丑陋和保留的东西正常使用时漂亮干净。

【讨论】:

  • 使用向量存储顺序将需要 O(n) 复杂度来擦除任意键。这在实践中可能不是问题,但这种限制确实存在,例如在 Python 的 OrderedDict 中。 (我没有检查Tie::IxHash是如何实现删除的。)
  • 感谢@jerry-coffin 的详细回答和大家的讨论。我现在更好地理解它了。对了,我在Tie::IxHash上看到这个帖子说删除的情况下效率不高:stackoverflow.com/questions/5344512/…
  • 在第一种情况下(对向量),我认为可以使用 std::lower_bound 通过迭代键来获取您要查找的元素的索引,这是一种二进制搜索.
  • @ChatterOne:不幸的是,没有。 lower_bound 要求集合按键排序,这里的重点是它保留了插入的顺序,而不是按键排序。
  • @JerryCoffin 你当然是对的,我早该想到的。
【解决方案2】:

记住插入顺序的关联容器不随 C++ 标准库提供,但使用现有的 STL 容器很容易实现。

例如,std::map(用于快速查找)和std::list(用于维护键顺序)的组合可用于模拟插入顺序映射。这是一个演示该想法的示例:

#include <unordered_map>
#include <list>
#include <stdexcept>

template<typename K, typename V>
class InsOrderMap {
  struct value_pos {
    V value;
    typename std::list<K>::iterator pos_iter;
    value_pos(V value, typename std::list<K>::iterator pos_iter):
      value(value), pos_iter(pos_iter) {}
  };

  std::list<K> order;
  std::unordered_map<K, value_pos> map;

  const value_pos& locate(K key) const {
    auto iter = map.find(key);
    if (iter == map.end())
      throw std::out_of_range("key not found");
    return iter->second;
  }

public:
  void set(K key, V value) {
    auto iter = map.find(key);
    if (iter != map.end()) {
      // no order change, just update value
      iter->second.value = value;
      return;
    }
    order.push_back(key);
    map.insert(std::make_pair(key, value_pos(value, --order.end())));
  }

  void erase(K key) {
    order.erase(locate(key).pos_iter);
    map.erase(key);
  }

  V operator[](K key) const {
    return locate(key).value;
  }

  // iterate over the mapping with a function object
  // (writing a real iterator is too much code for this example)
  template<typename F>
  void walk(F fn) const {
    for (auto key: order)
      fn(key, (*this)[key]);
  }
};

// TEST

#include <string>
#include <iostream>
#include <cassert>

int main()
{
  typedef InsOrderMap<std::string, std::string> IxHash;

  IxHash food_color;
  food_color.set("Banana", "Yellow");
  food_color.set("Apple", "Green");
  food_color.set("Lemon", "Yellow");

  assert(food_color["Banana"] == std::string("Yellow"));
  assert(food_color["Apple"] == std::string("Green"));
  assert(food_color["Lemon"] == std::string("Yellow"));

  auto print = [](std::string k, std::string v) {
    std::cout << k << ' ' << v << std::endl;
  };
  food_color.walk(print);
  food_color.erase("Apple");
  std::cout << "-- without apple" << std::endl;
  food_color.walk(print);
  return 0;
}

将此代码开发为完整容器(如std::map)的直接替代品需要相当大的努力。

【讨论】:

  • 也感谢@user4815162342 的详细回答。
【解决方案3】:

C++ 对此有标准容器。 无序的地图看起来就像您正在寻找的那样:

std::unordered_map <std::string, std::string> mymap = {{"Banana", "Yellow" }, {"Orange","orange" } } 

【讨论】:

  • 无序的地图,最明显的是无序的。常规的 std::map 是基于树而不是哈希表,因此操作不能再在 O(1) 中执行,而只能在 O(log N) 中执行。因此没有等效的标准数据结构。
猜你喜欢
  • 2019-03-19
  • 2011-03-20
  • 2014-10-12
  • 2011-02-04
  • 2015-09-18
  • 2015-11-27
  • 2019-12-02
  • 2020-06-30
  • 2016-10-29
相关资源
最近更新 更多