【问题标题】:std::transform with lambda: skip some itemsstd::transform with lambda:跳过一些项目
【发布时间】:2016-12-24 18:30:18
【问题描述】:

我有一些 C++11 代码,例如

std::vector<std::string> names;
std::map<std::string, std::string> first_to_last_name_map;
std::transform(names.begin(), names.end(), std::inserter(first_to_last_name_map, first_to_last_name_map.begin()), [](const std::string& i){
    if (i == "bad")
        return std::pair<std::string, std::string>("bad", "bad"); // Don't Want This
    else
        return std::pair<std::string, std::string>(i.substr(0,5), i.substr(5,5));
});

我使用带有 lambda 函数的 std::transform 将向量转换为地图。我的问题是,有时,如图所示,我不想从我的 lambda 函数返回任何内容,即我基本上想跳过 i 并转到下一个(不向地图添加任何内容)。

有什么方法可以实现我的想法吗?如果有帮助,我可以使用 boost。我想避免一个解决方案,我必须对我的向量进行预处理或后处理以过滤掉“坏”项目;我应该只需要查看每个项目一次。另外,我的实际逻辑比写的 if/else 复杂一些,所以我认为如果可能的话,将东西封装在这个 std::transform/lambda 模型中会很好(尽管可能是我想要实现的这个模型是不可能的)。

编辑:只是强调一下,我希望以最有效的方式执行此操作(选择性地处理矢量元素并将它们插入到地图中),即使这意味着不太优雅的解决方案或大的重写。我什至可以根据最有效的方式使用不同的地图数据类型。

【问题讨论】:

  • 就我个人而言,我会编写一个transform_if 模板函数——它会反映copy_if 等其他算法的命名方案,并保持生成的代码简单易懂。
  • 迟了半年的评论,但是对于每个循环和在地图中插入元素的简单操作对您不起作用?
  • @Zoso 我现在没有代码在我面前,但可以想象:我在某个地方存储了一个 lambda,我的代码的各个部分用于执行这种类型的处理/过滤(也许我什至有一组用于不同过滤器的 lambdas 等)。所以在那种情况下,我被困在使用 lambdas 中;我希望我的 lambdas 进行处理,而不仅仅是返回 true/false 是否应该完成处理。如果检查某个项目的坏处的逻辑非常复杂,我希望将该逻辑隐藏在 lambda 中(不再重复)。所以 transform_if + lambda 可能比 foreach + badness 检查更干净。

标签: c++ c++11 boost lambda


【解决方案1】:
template<class Src, class Sink, class F>
void transform_if(Src&& src, Sink&& sink, F&& f){
  for(auto&& x:std::forward<Src>(src))
    if(auto&& e=f(decltype(x)(x)))
      *sink++ = *decltype(e)(e);
}

现在只需获得 boost 或 std 或 std 经验可选。让您的 f 返回 optional&lt;blah&gt;

auto sink = std::inserter(first_to_last_name_map, first_to_last_name_map.begin());
using pair_type = decltype(first_to_last_name_map)::value_type;

transform_if(names, sink,
  [](const std::string& i)->std::optional<pair_type>{
    if (i == "bad")
      return {}; // Don't Want This
    else
      return std::make_pair(i.substr(0,5), i.substr(5,5));
  }
);

我个人的首选选项实际上已经定义了开始结束。我们得到了这个算法:

template<class Src, class Sink, class F>
void polymap(Src&& src, Sink&& sink, F&& f){
  for(auto&& x:std::forward<Src>(src))
    for(auto&& e:f(decltype(x)(x)))
      *sink++ = decltype(e)(e);
}

现在让 f 返回一个范围,其中 optional 是零或一个元素范围的模型。

【讨论】:

  • 我也喜欢这个答案,但它是否还需要查看向量中的每个项目两次,一次在 lambda 内,然后再次在 transform_if 的 if 内(或隐式地在 f 的范围迭代器内在polymap中)?我希望只考虑向量中的每个元素一次。
  • @davewy “考虑”或“寻找”是什么意思? if 子句基于可选项的参与度(是否为空),而不是可选项的内容。它将内容从可选内容移到水槽中,这也不是“寻找”。并且“看”一次完成一个元素——这里只有一次。所以,我不确定你在说什么。
  • 我的意思是对每个元素进行了两次比较操作:第一次比较(i == "bad")判断可选值应该有什么值,然后第二个if看可选值是否有值或不。因此,在将元素插入地图之前执行了两次比较。在将有效元素插入地图之前,我只想对向量中的每个元素执行一次比较或操作。
  • @davewy 所以,你的问题是你有一个返回 false 等价物的 lambda,然后调用 lambda 的代码执行一个 if,你很担心,因为......表现?还是其他什么原因?首先,它是在即将分配内存(映射是基于节点的)的代码中,这将比失败的分支高出 10 倍。其次,第二个分支可以被内联 lambda 的编译器完全移除,这就是 lambda 的用途(易于内联)。最后,即使没有内联,它也是高度可预测的,而分支预测使其免费。
【解决方案2】:

您可以使用std::remove_if 简单地进行第一次/最后一次传递。例如。

std::vector<std::string> names;
std::map<std::string, std::string> first_to_last_name_map;
std::transform(names.begin(), 
               std::remove_if(names.begin(),
                              names.end(),
                              [](const std::string &str){
                                    return str=="bad";
                              }),
               std::inserter(first_to_last_name_map, 
                             first_to_last_name_map.begin()),
               [](const std::string& i){
                     return std::pair<std::string, std::string>(i.substr(0,5), i.substr(5,5));
               });

请注意,remove_if 只是将删除的项目移到它返回的迭代器之外。

【讨论】:

  • 这可行,但这是否需要 2 次通过向量,1 次构造 remove_if 返回的迭代器,1 次执行变换?我希望做这样的事情,但只访问一次向量中的每个项目。
【解决方案3】:

您可以先使用boost::adaptors::filtered 过滤掉您不想要的元素的vector,然后再将其传递给transform

using boost::adaptors::filtered;
boost::transform(names | filtered([](std::string const& s) { return s != "bad"; }),
                 std::inserter(first_to_last_name_map, first_to_last_name_map.begin()),
                 [](std::string const& i) { return std::make_pair(i.substr(0,5), i.substr(5,5)); });

Live demo

【讨论】:

  • 这可行,但这是否需要查看向量中的每个元素两次,一次执行过滤器,然后再次在 lambda 内查看?我希望做这样的事情,但只访问一次向量中的每个项目。
  • @davewy Boost 适配器是懒惰的。这是一个演示 - coliru.stacked-crooked.com/a/60c28e85ec08bbe3。出于某种原因,在调用transform 之前,对vector 中的第一个元素调用了一次lambda(也许这就是范围组合的工作方式),但除此之外,vector 正在被遍历致电transform
  • 谢谢!在您提供的代码示例中,看起来元素被打印了两三次。即使 boost 适配器是惰性的,它们仍然需要在迭代器访问元素时进行评估。所以每个元素都会被访问两次或三次,而不仅仅是一次。有什么想法吗?
  • @davewy 这是同样的例子,但噪音更小 - coliru.stacked-crooked.com/a/fadee6ebf30cd7c6。我定义了string 转换运算符以避免为MyString 定义其他运算符,但由于我无论如何都定义了它们,因此不需要进行转换。所以每个元素都被打印了两次,一次用于过滤,一次用于转换,不知道你想如何避免。
猜你喜欢
  • 1970-01-01
  • 2015-03-18
  • 2012-02-20
  • 1970-01-01
  • 2011-04-22
  • 2011-12-04
  • 2022-07-07
  • 2016-04-22
  • 1970-01-01
相关资源
最近更新 更多