使用std::map<T, bool>,计算值为true的数量

16

我有一张地图:

std::map<std::string, bool> all_triggers_didfire;

我填充它,最终想要获取真实值的数量。以下代码有效:

int count_did_fire = std::count_if(
  all_triggers_didfire.begin(), 
  all_triggers_didfire.end(), 
  [](std::pair<std::string, bool> p){return p.second;}
);

有没有比为此定义lambda表达式更简单的方法?


10
如果您使用的是C++14,可以使用 [](auto p) { return p.second; } - Nim
2
或者 [](decltype(all_triggers)::value_type p) { return p.second; } - rodrigo
3
对我来说,lambda看起来很好,除了应该通过引用传递其参数以避免复制字符串所需的内存分配(与上面两个注释相同)。 - Jonathan Wakely
1
@JonathanWakely 除此之外,它需要是 std::pair<const std::string, bool> 才能避免复制 :) - T.C.
4个回答

11

我会使用std::set而不是std::map。它们在语义上是等价的,但使用std::set更容易。例如:

std::set<std::string> triggers_that_did_fire;
int count_did_fire = triggers_that_did_fire.size();

当您最初填充triggers_that_did_fire集时,可以执行以下操作:

triggers_that_did_fire.insert(mystring); //equivalent to setting to "true" in your map
triggers_that_did_fire.remove(mystring); //equivalent to setting to "false"

2
@Steephen,我相信作者使用map/set而不是std::vector有他/她自己的逻辑原因。例如,也许作者的软件还想查询特定触发器是否确实已触发。 - Alex Shtof
6
这并不是对问题的回答。 - user1804599
1
@Steephen:如果这里可以使用std::set,那么std::vector可能更合适。完全错误。 - Lightness Races in Orbit
20
不,它们在语义上并不相等。一个布尔类型的映射可以将X映射为true、将X映射为false,或者根本不对X进行映射。而一个集合只能包含X或不包含X。 - R. Martinho Fernandes
1
@Steephen: 在实践中,它们当然是按排序顺序公开的。然而,从学术上讲,我更喜欢将集合视为块而不是序列。毕竟,它们是关联容器,而不是序列容器。您不应该将它们用于事物列表,而是用于事物集合。 - Lightness Races in Orbit
显示剩余8条评论

9
有时候,一个简单的for循环更加清晰明了:
auto count = 0;
for (auto&& p : all_triggers_didfire)
  if (p.second)
    ++count;

编辑1:我将发布原始代码,以防有人无法查看编辑历史记录。

auto count = 0;
for (auto& p : all_triggers_didfire)
  count += p.second;

5
@rightfold,为什么要改变?之前的方式有什么问题吗?依赖于bool<->int转换真的那么可怕吗? - Nim
2
依赖这样的转换会让代码比if语句更难读懂,因此很令人困惑。 - user1804599
4
@rightfold,有趣,所以您引入了一个“通用参考”来“澄清”代码? :) 现在当您查看上面的代码时,第一件吸引您注意力的是什么?&&它是什么东西?它在那里做什么?在我看来,此时我们会对这个东西进行二次确认并尝试弄清楚那里发生了什么,而不是跳过之前的代码,因为它已经非常简明扼要了...无论如何,这只是我的想法... - Nim
15
我不同意你的修改 -- 它违背了张贴者的意图。我认为这是基于个人观点的。你应该留下评论或发布自己的答案。 - TonyK
3
在这里使用转发引用完全没有好处。我们知道解引用map<T,U>::iterator会生成一个左值,所以为什么要让代码具有处理不可能的右值的通用性呢? - Jonathan Wakely
显示剩余6条评论

4
您可以使用std::mem_fn将对数据成员的访问封装为一个可调用对象:
int count_did_fire = std::count_if(
  all_triggers_didfire.begin(), 
  all_triggers_didfire.end(), 
  std::mem_fn(&decltype(all_triggers_didfire)::value_type::second)
);

1
你为什么不使用std::mem_fn呢? - user1804599

0
有没有比为此定义lambda表达式更简单的方法? 没有
这取决于您所说的“更简单”的含义。在C++中,一个重要的事情是,std::mapvalue_typepair<const key_type,mapped_type>而不仅仅是mapped_type。 std::map::iterator迭代器遍历这个value_type,您需要一个包装器来获取键或映射类型。
所有C++标准库算法都使用迭代器,对于std::map,它是指向value_type的迭代器。因此,为了使算法适用于映射类型,我们需要将value_type重新映射到映射类型,为此可以使用以下任一方法:
  1. 您需要一个帮助命名函数(C++11之前)
  2. 您需要一个函数对象
  3. 或者,您需要一个lambda表达式。
值得注意的是:

“如果C++支持lambda表达式,C++标准库算法将更加愉快”

一个关于将 Lambda 函数添加到 C++ 标准中的提议,N1958=06-002。

所以,如果您认为代码看起来很丑陋,您对清理代码的意图将会打败 Lambda 的原始动机。

因此,如果您想使用 C++ 标准库算法,就需要在需要时使用 Lambda,比如 std::map 就是这种情况。当然,您仍然可以使用 迭代的方式 重写代码,但这是一种选择和可读性问题,“可读性取决于审阅者的眼睛”


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接