【问题标题】:Should I avoid using pointers here?我应该避免在这里使用指针吗?
【发布时间】:2013-06-27 02:32:17
【问题描述】:

我有这个简单的代码:

std::vector<std::map<double,double>> v;
//populate v

//we know each map already has correct key order (enforced by c++)
//but i also want to make sure the different maps have correct key order
//this is how I do it using a pointer:

const double *last_key = nullptr;
for (const auto &map : v)
{
  if (map.size() > 0) //ignore empty maps
  {
    if (last_key)
    {
      const auto &new_key = map.cbegin()->first;
      if (!(*last_key < new_key))
        throw std::runtime_error("invalid key order");
    }
    last_key = &(--map.cend())->first;
  }
}

这对指针有用吗?你会怎么做呢?

我知道的唯一真正的选择(如果我想避免使用指针)是这样做:

double last_key;
bool last_key_has_been_set = false;

这可行,但它要求密钥是默认可构造的,并且涉及不必要的密钥复制(与double 不同的密钥类型存在问题)。

【问题讨论】:

  • 参考文献怎么样?还是我误解了这个问题?
  • @H2CO3 我考虑过引用,但你能给引用分配新值吗?
  • 你应该使用的是迭代器本身。它们充当指针。
  • 使用std::adjacent_find。有算法是有原因的。
  • 那么迭代器呢?

标签: c++ dictionary pointers std stdmap


【解决方案1】:

好的,既然我现在(想我)明白你的代码是关于什么的,下面是我的看法:

auto iter = v.begin();
auto end = v.end();
while (iter != end && iter->empty())
  ++iter;
if (iter != end)
{
  while (true) // loop and a half
  {
    auto next = iter+1; // at this point, we know iter != end
    while (next != end && next->empty())
      ++next;
    if (next == end)
      break;
    auto lhslast = lhs.end();
    --lhslast;
    if (lhslast->first > next->begin()->first)
      throw std::runtime_error("invalid key order");
    iter = next;
  }
}

编辑:

上面的代码可以使用另一种算法进一步改进:

替换

while (iter != end && iter->empty())
  ++iter;

iter = std::find_if(iter, end,
                    [](std::map<double, double> const& m) { return m.empty(); });

next 循环类似。

另一种选择是注意,如果它不是空地图,您可以使用adjacent_find。因此另一种选择是利用 Boost 的filter_iterator 来摆脱空地图。就这样

#include <boost/iterator/filter_iterator.hpp>

struct is_not_empty
{
  template<typename Container> bool operator()(Container const& c) const
  {
    return !c.empty();
  }
};

然后在你的代码的地方

auto fbegin = boost::make_filter_iterator(is_not_empty(), v.begin(), v.end());
auto fend =  boost::make_filter_iterator(is_not_empty(), v.end(), v.end());

if (std::adjacent_find(fbegin, fend,
                       [](std::map<double, double> const& lhs,
                          std::map<double, double> const& rhs) -> bool
                       {
                         auto lhslast = lhs.end();
                         --lhslast;
                         return lhslast->first > rhs.begin()->first;
                       }) != fend)
  throw std::runtime_error("invalid key order");

过滤器迭代器确保只考虑非空映射。

【讨论】:

  • 如果向量的第一个条目是空地图怎么办?
  • @jogojapan:请注意,我添加了第二个解决方案
  • filter_iterator 解决方案很有趣。我只希望它支持 lambda 函数而不是那个 is_not_empty 结构。
  • 存在语法错误。你能编译和更正代码吗? (顺便说一句,有趣的想法。我已经为你的答案投票了,所以我不能再投票了......)
  • 我很抱歉出现错误;现在应该修复它们。除了琐碎的错误(拼写错误和缺少 const)之外,主要问题是 map 没有 frontback;它们必须用迭代器来模拟(我还对第一个版本应用了相应的修复)。另外,我忘记将adjacent_find 的结果与结束迭代器进行比较。
【解决方案2】:

我认为标准库中没有合适的预定义算法可以做到这一点。特别是,如果您要为它定义一个相对复杂和有状态的谓词,std::adjacent_find可以用于此,但这实际上等于滥用std::adjacent_find作为@987654323的某种替代品@,即与std::adjacent_find的初衷并无太大关系。

但是,您应该使用迭代器,而不是裸指针。我还建议将检查代码放入一个单独的函数中,可能命名为check。这是我的建议:

#include <vector>
#include <map>
#include <iostream>

bool check(const std::vector<std::map<double,double>> &v)
{
  /* Fast-forward to the first non-empty entry. */
  auto it = begin(v);
  for( ; it != end(v) ; ++it)
    if (!it->empty())
      break;

  /* We might be done by now. */
  if (it == end(v))
    return true;

  /* Else, go through the remaining entries,
     skipping empty maps. */
  auto prev = it->end();
  advance(prev,-1);
  ++it;

  for ( ; it != end(v) ; ++it)
    {
      if (!it->empty())
        {
          if (it->begin()->first < prev->first)
            return false;
          prev = it->end();
          advance(prev,-1);
        }
    }

  return true;
}

int main()
{
  std::vector<std::map<double,double>> v;

  /* Two entries for the vector, invalid order. */    
  v.push_back({ {1.0,1.0} , {2.0,4.0} });
  v.push_back({ {3.0,9.0} , {1.0,16.0} });

  if (!check(v))
    throw std::runtime_error("Invalid order of the maps in the vector.");

  return 0;
}

注意: 如果您将 check 函数定义为采用迭代器的范围,而不是对容器的引用,作为参数。重写函数以匹配这个概念是直截了当的。

注 2: 使用迭代器而不是裸指针的优势在于,您可以对所需内容进行更好、更清晰的抽象:引用地图中的项目的内容,而 double*指针可能指向各种事物。然而,使用迭代器也有一个缺点:如果你要修改你的算法,使它在迭代向量时改变映射,迭代器可能会失效,而指针不会(除非你删除它指向的元素) . (不过,如果你改变向量,指针可能会失效。)

但只要检查过程仅用于检查而没有其他用途(我的代码通过将代码放入专门用于此目的的单独函数中,并将向量作为常量引用来表明这一点),迭代器无效不是问题。

【讨论】:

  • 你可以证明如果你有一个坏订单,你必须有一个本地相邻的坏订单。这就是为什么使用adjacent_find 可以完美匹配问题的原因。
  • @lip std::adjacent_find 的初衷是为了找到相等的相邻元素,而不是找乱序的相邻元素。我同意,在某种程度上,通过定义一个实际上检查小于否定的相等谓词来滥用其原始目的可能是可以的。但是为了处理空映射,您需要引入状态,这意味着您需要定义的谓词变得非常复杂。这相当于编写一个非常复杂的伪等式谓词,以便让std::adjacent_find 做一些不该做的事情。
  • 这看起来与原始指针解决方案同样复杂。你同意应该避免指针吗?为什么?
  • @roger.james 算法复杂度和之前一样。代码有点长,但这不是因为迭代器。我只是觉得这样更容易阅读代码。
【解决方案3】:

这使用 C++1y 功能 (std::tr2::optional),但应该适用于任何容器以及容器元素的任何排序:

struct compare_key_order {
  template<typename LHS, typename RHS>
  bool operator()( LHS const& lhs, RHS const& rhs ) {
    return lhs.first < rhs.first;
  }
};

template<typename ContainerOfContainers, typename Ordering>
bool are_container_endpoints_ordered( ContainerOfMaps&& meta, Ordering&& order=compare_key_order()  )
{
  using std::begin; using std::end;

  // or boost::optional:
  std::tr2::optional< decltype( begin(begin(meta)) ) > last_valid;

  for( auto&& Map : std::forward<Meta>(meta) ) {
    auto b = begin(Map);
    auto e = end(Map);
    if (b==e)
      continue;
    if (last_valid)
      if (!order( **last_valid, *b ))
        return false;
    last_valid = e;
  }
  return true;
}

optional 是一种处理“此元素可能存在也可能不存在”的更漂亮、更不易出错的方式,而不是指针可以成为nullptr。如果您正在使用boost 或可以访问std::tr2::optional(或者您将在将来阅读此内容,当std::optional 存在时),这比指针更好。

您还可以将“是否有last_valid”移出状态并移至程序代码位置:

struct compare_key_order {
  template<typename LHS, typename RHS>
  bool operator()( LHS const& lhs, RHS const& rhs ) {
    return lhs.first < rhs.first;
  }
};

template<typename ContainerOfContainers, typename Ordering>
bool are_container_endpoints_ordered( ContainerOfMaps&& meta, Ordering&& order=compare_key_order()  )
{
  using std::begin; using std::end;

  auto it = begin(meta);
  while( it != end(meta) && (begin(*it) == end(*it)) {
    ++it;
  }
  if ( it == end(meta) )
    return true;
  auto last_valid_end = end(*it);
  for( ++it; it != end(meta); ++it ) {
    auto b = begin(*it);
    auto e = end(*it);
    if (b==e)
      continue;
    if (!order( *last_valid_end, *b ))
      return false;
    last_valid = e;
  }
  return true;
}

这将使相同的算法在vectors-of-vectors-of-pairs上运行,甚至检查vector-of-vectors是否具有排序端点(具有不同的order)。

【讨论】:

    【解决方案4】:

    最佳注释给出了答案:使用adjacent_find

    首先是一点逻辑。如果有 n key[m],则存在索引 i,n key[i+i]。

    你可以用荒谬的推理来证明这一点:如果没有这样的 i,那么对于 n 和 m 之间的所有 i 我们都有顺序,并且因为顺序关系是传递的,key[n] 相邻 键顺序错误。

    所以你的算法应该是:

    typedef map<double, double> map_t;
    vector<map_t> v;
    remove_if(v.begin(), v.end(), [](map_t const& m){return m.empty();});
    if(adjacent_find(v.begin(), v.end(), [](map_t const& l, map_t const& r)
        {
            return (--l.cend())->first > r.cbegin()->first;
        }) != v.end())
        throw std::runtime_error("invalid key order");
    

    当然,如果你可以先从你的向量中删除空地图。 (我们可以假设,因为空地图可能没有那么有意义,但这当然取决于整体情况。

    【讨论】:

    • 它是地图向量,而不是整数向量。这确实使情况复杂化。
    • @jogojapan:实际上更复杂的情况是地图可能是空的。
    • @celtschk 而 ints 确实不能为空。
    • 对不起,这有点远,我编辑了代码以更接近实际问题。
    • 但这是否考虑到两个地图之间有空地图的无效顺序?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-16
    • 2012-10-14
    • 2014-04-05
    • 1970-01-01
    • 2011-03-26
    相关资源
    最近更新 更多