std :: map get value - find vs handcrafted loop

时间:2016-03-20 16:05:05

标签: c++ performance coding-style time-complexity stdmap

我有一个std :: map对象Option Explicit Function IsOfficeFileAlreadyOpen(strOfficeFileFullName) Dim lPos Dim strLockFileFullName lPos = InstrRev(strOfficeFileFullName, "\", -1, vbBinaryCompare) If (lPos = 0) Then ' Only file name has been given, no path specified. Must be in current folder. Good luck! strLockFileFullName = "~$" & strOfficeFileFullName Else strLockFileFullName = Left(strOfficeFileFullName, lPos) & "~$" & Mid(strOfficeFileFullName, lPos + 1) End If IsOfficeFileAlreadyOpen = CreateObject("Scripting.FileSystemObject").FileExists(strLockFileFullName) End Function If (IsOfficeFileAlreadyOpen("<FileName>") = True) Then MsgBox "The file '" & <FileName> & "' is already open. Please try again once the file is closed.", vbExclamation WScript.Quit Else ' Open the file first MsgBox "The file '" & "<FileName>" & "' is available and will be processed.", vbInformation End If ,其中map<string , Property*> _propertyMap是属性的名称,string包含属性值。

我需要处理属性值并将它们转换为特定的数据格式 - 每个属性都有自己的格式,例如..如果地图初始化如下:

Property*

然后_propertyMap["id"] = new Property(Property::UUID, "12345678"); _propertyMap["name"] = new Property(Property::STRING, "name"); .... 的处理方式应与"id"等不同。

这意味着我需要在地图中查找每个属性并相应地处理其值。

我想到了两种方法。

一,使用"name"方法获取特定属性,如:

std::map::find

二,迭代地图,并为每个条目检查属性的名称是什么,并相应地继续:

map<string , Property*>::iterator it1 = _propertyMap.find("id");
if(it1 != _propertyMap.end())
{
   //element found - process id values
} 
map<string , Property*>::iterator it2 = _propertyMap.find("name");
if(it2 != _propertyMap.end())
{
   //element found - process name values
} 
....

鉴于Time complexity of std::map::find is O(logN),第一个解决方案的复杂性为for (it = _propertyMap.begin(); it != _propertyMap.end(); ++it ) { //if it is events - append the values to the matching nodes if (it->first == "id") { //process id values } else if (it->first == "name") { //process name values } ..... } 。我不确定第二个解决方案的复杂性,因为它迭代地图一次(O(NlogN)),但每次迭代执行大量O(N)。我试图谷歌常见if-else个问题,但无法找到任何有用的信息;他们中的大多数只需要从地图中获取一个值,然后map::find()以更高的复杂度(find())执行此操作。

什么是更好的方法?或者还有另外一个我没有想到的?

另外,代码样式说话,哪一个更好,更清晰的代码?

3 个答案:

答案 0 :(得分:2)

我在这里看到了一些不同的用例,具体取决于你的想法:

固定属性

(为了完整性,我想这不是你想要的)如果应该修复可能属性的名称和类型,最好的版本是使用一个简单的类/结构,可能使用boost::optional({ {1}}使用C ++ 17)可能存在或不存在的值

std::optional

优点:

  • 所有&#34;查找&#34;在编译时解决

缺点:

  • 在运行时无法灵活扩展

根据其键

仅处理特定选项

如果您只想处理特定的选项子集,但保留选项以拥有(未经处理的)自定义键,您的方法似乎是合适的。

在这种情况下请记住使用find这样:

struct Data{
    int id = 0;
    std::string name = "";
    boost::optional<int> whatever = boost::none;
}

具有复杂度O(logN)但是使用了M次,其中M是处理选项的数量。 这不是地图的大小,而是您使用 it1 = _propertyMap.find("id"); 获取特定媒体资源的次数。在您的(缩短)示例中,这意味着复杂度为O(2 * logN),因为您只需要查找2个键。

因此,当只有地图的大小增加时,基本上使用M次find()缩放比循环更好,但如果以相同方式增加查找次数则更糟。但只有剖析可以告诉你哪一个更适合你的大小和用例。

根据类型

处理所有选项

由于您的地图看起来很像关键字可以自定义但类型来自一个小子集,因此请考虑在地图上循环并使用类型而不是名称来确定如何处理它们。像这样:

find()

这样做的好处是,您不需要任何有关地图键实际内容的信息,只需要它能够存储的类型。

答案 1 :(得分:1)

假设我们有一个N个属性的映射,我们正在寻找P属性的子集。这是一个粗略的分析,不知道密钥的统计分布:

  • 在纯映射方法中,搜索P次,复杂度为O(log(n)),即O(p * log(n))

  • 在chained-if方法中,您将遍历地图。那是O(N)。但是你不应该忘记if-then链也是P元素列表的(hiden)遍历。因此,对于N个元素中的每一个,您正在搜索可能最多为P的元素。所以你在这里有一个复杂的O(p * n)。

这意味着地图方法将优于您的遍历,并且性能差距将随着n的增加而显着增加。当然,这并没有考虑到if-chain中没有的map中的函数调用开销。因此,如果P和N很小,你的方法仍然可以进行理论比较。

您最终可以采取的进一步提高性能的方法是使用 unordered_map ,复杂度为O(1),将问题复杂性降低到O(P)。

答案 2 :(得分:1)

还有另一种选择,它结合了两者的优点。给定这样的函数(这是std::set_intersection的改编):

template<class InputIt1, class InputIt2, 
         class Function, class Compare>
void match(InputIt1 first1, InputIt1 last1,
           InputIt2 first2, InputIt2 last2,
           Function f, Compare comp)
{   
    while (first1 != last1 && first2 != last2) {
        if (comp(*first1,*first2)) {
            ++first1;
        } else {
            if (!comp(*first2,*first1)) {
                f(*first1++,*first2);
            }
            ++first2;
        }
    }
}

您可以使用它来处理O(N + M)时间内的所有属性。这是一个例子:

#include <map>
#include <string>
#include <functional>
#include <cassert>

using std::map;
using std::string;
using std::function;

struct Property {
  enum Type { UUID, STRING };
  Type type;
  string value;
};

int main()
{
    map<string,Property> properties;
    map<string,function<void(Property&)>> processors;

    properties["id"] = Property{Property::UUID,"12345678"};
    properties["name"] = Property{Property::STRING,"name"};

    bool id_found = false;
    bool name_found = false;

    processors["id"]   = [&](Property&){ id_found = true; };
    processors["name"] = [&](Property&){ name_found = true; };

    match(
       properties.begin(),properties.end(),
       processors.begin(),processors.end(),
       [](auto &a,auto &b){ b.second(a.second); },
       [](auto &a,auto &b) { return a.first < b.first; }
    );

    assert(id_found && name_found);
}

processors地图可以单独构建并重复使用以减少开销。