微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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

如何解决C++ - 将双精度向量作为元组迭代

我有一个双精度的 C++ 向量,它保证有偶数个元素。这个向量将一组点的坐标存储为 x,y 坐标:

A[2 * i ] 是第 i 个点的 x 坐标。

A[2 * i + 1] 是第 i 个点的 y 坐标。

如何实现允许我使用 STL 风格算法的迭代器(一个采用迭代器范围的算法,其中取消引用迭代器会返回对应于对应点的 x、y 坐标的一对双精度值)?

如果有帮助,我将使用 C++17。

解决方法

我们可以使用带有两个适配器的 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);
  })

Demo,使用 fmt 方便打印:

int main() {
    std::vector<int> v = {1,1,2,3,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<T,T> 还是 pair<T&,T&>。后者可以通过向 std::pair 提供显式类型而不是依赖类模板参数推导来实现。

,

C++ 有点像一个移动目标——在迭代器方面也是如此(c++20 有这方面的概念......)。但是对问题有一个懒惰的解决方案不是很好。 IE。元组是即时生成的,无需强制转换(请参阅其他答案),也无需编写循环将 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;
}

,

vector 中的元素存储在连续的内存区域中,因此您可以使用简单的指针算法来访问双精度对。

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
}

Demo

,

没有一种简单的“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;
}
,

如果你能保证 std::vector 总是有偶数个条目,你可以利用这样一个事实,即双精度向量与双精度向量具有相同的内存布局。不过,这是一个肮脏的把戏,所以如果你能避免的话,我不会推荐它。好消息是该标准保证了向量元素是连续的。

__call__

这仅适用于迭代器的迭代。大小很可能是向量中对数的两倍,因为它下面仍然是一个双数向量。

示例:

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);
}

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。