C++ - 迭代双精度向量作为元组

4
我有一个C++的双精度向量,保证它包含偶数个元素。该向量存储一组点的x、y坐标:
A[2 * i] 是第i个点的x坐标。
A[2 * i + 1]是第i个点的y坐标。
如何实现一个迭代器,使我能够使用STL风格的算法(接受迭代器范围,其中解引用迭代器返回相应点的x、y坐标对)?
我在使用C++17版本。

我猜你正在寻找一种“懒惰”的方式,可以避免复制/重新格式化,对吗? - BitTickler
正确 - 我假设任何能够回答它的人都能理解意图 :) - TCSGrad
5个回答

2
我们可以使用range-v3库,使用两个适配器:
  • chunk(n)将一个范围适应为大小为n的不重叠范围的范围
  • transform(f)将一系列x适应为一系列f(x)
将它们组合在一起,我们可以创建一个适配器,该适配器接受一个范围并生成一系列不重叠的对。
auto into_pairs = rv::chunk(2)
                | rv::transform([](auto&& r){ return std::pair(r[0], r[1]); });

使用r [0]语法假定输入范围是随机访问的,这种情况下对于我们要在vector上使用它来说是可以的,但是可以通过多写一点代码将其泛化以适用于仅向前的范围:

| rv::transform([](auto&& r){
      auto it = ranges::begin(r);
      auto next = ranges::next(it);
      return std::pair(*it, *next);
  })

演示,使用fmt方便打印:

int main() {
    std::vector<int> v = {1, 1, 2, 2, 3, 3, 4, 4, 5, 5};

    auto into_pairs = rv::chunk(2)
                    | rv::transform([](auto&& r){ return std::pair(r[0], r[1]); });

    // prints {(1, 1), (2, 2), (3, 3), (4, 4), (5, 5)}
    fmt::print("{}\n", v | into_pairs);
}

问题中并不清楚您想要的是 `pair` 还是 `pair`。如果您需要后者,可以通过为 `std::pair` 提供显式类型而不是依赖于类模板参数推导来实现。

哇,好棒的解决方案!是的,尽可能使用引用以避免复制是更可取的 - 所以我应该使用 pair<int&, int&> 而不是 auto &&,对吗? - TCSGrad

1
C++是一个不断发展的目标,包括迭代器(c++20有相关概念)。但是,如果有一种懒惰的解决方案岂不是更好?即在不进行转换(参见其他答案)和编写循环以将vector<double>转换为vector<tuple<double,double>>的情况下,动态生成元组。
现在我觉得需要免责声明,因为我不确定这是否完全正确(语言专家会指出是否有遗漏)。但它可以编译并产生预期的输出。这很不错,对吧?是的。
这个想法是构建一个伪容器(实际上只是底层容器的外观),具有自己的迭代器,在运行时动态生成所需的输出类型。
#include <vector>
#include <tuple>
#include <iostream>
#include <iterator>

template <class SourceIter>
struct PairWise {
  PairWise() = delete;
  PairWise(SourceIter first, SourceIter last) 
    : first{first}
    , last{last}
  {
  }
  using value_type = 
    typename std::tuple<
      typename SourceIter::value_type, 
      typename SourceIter::value_type
        >; 
  using source_iter = SourceIter;
  struct IterState {
    PairWise::source_iter first;
    PairWise::source_iter last;
    PairWise::source_iter current;
    IterState(PairWise::source_iter first, PairWise::source_iter last)
      : first{first}
      , last{last}
      , current{first}
    {
    }
    friend bool operator==(const IterState& a, const IterState& b) {
      // std::cout << "operator==(a,b)" << std::endl;
      return (a.first == b.first) 
        && (a.last == b.last)
        && (a.current == b.current);
    }
    IterState& operator++() {
      // std::cout << "operator++()" << std::endl;
      if (std::distance(current,last) >= 2) {
        current++;
        current++;
      }
      return *this;
    }
    const PairWise::value_type operator*() const {
      // std::cout << "operator*()" << std::endl;
      return std::make_tuple(*current, *(current+1));
    }
  };
  using iterator = IterState;
  using const_iterator = const IterState;
  const_iterator cbegin() const {
    return IterState{first,last};
  }
  const_iterator cend() const {
    auto i = IterState{first,last};
    i.current = last;    
    return i;
  }
  
  const_iterator begin() const {
    // std::cout << "begin()" << std::endl;
    return IterState{first,last};
  }
  const_iterator end() const {
    // std::cout << "end()" << std::endl;
    auto i = IterState{first,last};
    i.current = last;    
    return i;
  }
  source_iter first;
  source_iter last;
};

std::ostream& operator<<(std::ostream& os, const std::tuple<double,double>& value) {
  auto [a,b] = value;
  os << "<" << a << "," << b << ">";
  return os;
}

template <class Container>
auto pairwise( const Container& container) 
  -> PairWise<typename Container::const_iterator> 
{
  return PairWise(container.cbegin(), container.cend());
}

int main( int argc, const char* argv[]) {
  using VecF64_t = std::vector<double>;

  VecF64_t data{ 1.0, 2.0, 3.0, 4.0, 5.0, 6.0 };
  for (const auto x : pairwise(data)) {
    std::cout << x << std::endl;
  }
  
  return 0;
}


0

向量中的元素存储在连续的内存区域中,因此您可以使用简单的指针算术来访问一对双精度浮点数。

迭代器的operator++应该每次跳过2个双精度浮点数。

operator*可以返回对双精度值的引用元组,因此您可以读取(pair = *it)或编辑值(*it = pair)。

struct Cont {
    std::vector<double>& v;
    Cont(std::vector<double>& v) : v(v) {}

    struct Iterator : public std::iterator<std::input_iterator_tag , std::pair<double,double>> {
        double* ptrData = nullptr;

        Iterator(double* data) : ptrData(data) {}

        Iterator& operator++() { ptrData += 2; return *this; }
        Iterator operator++(int) { Iterator copy(*this); ptrData += 2; return copy; }
        auto operator*() { return std::tie(*ptrData,*(ptrData+1)); }
        bool operator!=(const Iterator& other) const { return ptrData != other.ptrData; }
    };

    auto begin() { return Iterator(v.data()); }
    auto end() { return Iterator(v.data()+v.size());}
};


int main() {
    std::vector<double> v;
    v.resize(4);

    Cont c(v);
    for (auto it = c.begin(); it != c.end(); it++) {
        *it = std::tuple<double,double>(20,30);
    }
    std::cout << v[0] << std::endl; // 20
    std::cout << v[1] << std::endl; // 30
}

演示


-1

没有一种简单的“C ++”方法可以干净地避免复制原始数组。总是有这个(制作副本):

vector<double> A; // your original list of points
vector<pair<double,double>> points;

for (size_t i = 0; i < A.size()/2; i+= 2)
{
    points[i*2] = pair<double,double>(A[i], A[i+1]);
}

以下代码可能有效,但违反了一些标准,语言专家可能会因为我建议这样做而起诉我。但是,如果我们可以假设sizeof(XY)是两个双精度浮点数的大小,没有填充,并且期望对齐,则使用强制转换可能有效。这假定您不需要std::pair

非标准内容如下

vector<double> A; // your original list of points

struct XY {
   double x;
   double y;
};

static_assert(sizeof(double)*2 == sizeof(XY));

static_assert(alignof(double) == alignof(XY));

XY* points = reinterpret_cast<XY*>(A.data());
size_t numPoints = A.size()/2;

// iterate
for (size_t i = 0; i < numPoints; i++) {

     XY& point = points[i];

     cout << point.x << "," << point.y << endl;
}

2
我认为像这样宣传UB是非常值得质疑的。你认为它可能有效的理由并没有起到帮助作用。填充等并不是主要问题,最大的问题是优化器被允许假设不存在UB。不幸的是,你完全没有提及这一点。底线:请勿在家中尝试。 - besc
如果您添加 static_asert(alignof(double) == alignof(XY));,我认为就没有 UB 了。这将是防御性编程的一个很好的例子,唯一需要考虑的是,在假设不成立的系统中,代码会简单地崩溃(但我非常怀疑这不是可移植的)。 - Lorah Attkins

-2
如果您可以保证std::vector始终具有偶数个条目,那么您可以利用以下事实:一个双精度向量将具有与一对双精度向量相同的内存布局。不过这有点不太正经,所以如果可以避免的话我不会建议这样做。好消息是,标准保证向量元素将是连续的。
inline const std::vector<std::pair<double, double>>& make_dbl_pair(std::vector<double>& v)
{
  return reinterpret_cast<std::vector<std::pair<double, double>>&>(v);
}

这仅适用于使用迭代器进行迭代。大小可能是向量中成对数的两倍,因为它仍然是双精度向量。

例子:

int main(int argc, char* argv[])
{
  std::vector<double> dbl_vec = { 0.0, 1.1, 2.2, 3.3, 4.4, 5.5 };
  const std::vector<std::pair<double, double>>& pair_vec = make_dbl_pair(dbl_vec);
  for (auto it = pair_vec.begin(); it != pair_vec.end(); ++it) {
    std::cout << it->first << ", " << it->second << "\n";
  }
  std::cout << "Size: " << dbl_vec.size() << "\n";
  return 0;
}

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