Wenzel Jakob | 38bd711 | 2015-07-05 20:05:44 +0200 | [diff] [blame] | 1 | /* |
Dean Moldovan | a0c1ccf | 2016-08-12 13:50:00 +0200 | [diff] [blame] | 2 | tests/test_sequences_and_iterators.cpp -- supporting Pythons' sequence protocol, iterators, |
Wenzel Jakob | a576e6a | 2015-07-29 17:51:54 +0200 | [diff] [blame] | 3 | etc. |
Wenzel Jakob | 38bd711 | 2015-07-05 20:05:44 +0200 | [diff] [blame] | 4 | |
Wenzel Jakob | 8cb6cb3 | 2016-04-17 20:21:41 +0200 | [diff] [blame] | 5 | Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch> |
Wenzel Jakob | 38bd711 | 2015-07-05 20:05:44 +0200 | [diff] [blame] | 6 | |
| 7 | All rights reserved. Use of this source code is governed by a |
| 8 | BSD-style license that can be found in the LICENSE file. |
| 9 | */ |
| 10 | |
Dean Moldovan | a0c1ccf | 2016-08-12 13:50:00 +0200 | [diff] [blame] | 11 | #include "pybind11_tests.h" |
| 12 | #include "constructor_stats.h" |
Wenzel Jakob | 8f4eb00 | 2015-10-15 18:13:33 +0200 | [diff] [blame] | 13 | #include <pybind11/operators.h> |
| 14 | #include <pybind11/stl.h> |
Wenzel Jakob | 38bd711 | 2015-07-05 20:05:44 +0200 | [diff] [blame] | 15 | |
| 16 | class Sequence { |
| 17 | public: |
| 18 | Sequence(size_t size) : m_size(size) { |
Jason Rhinelander | 3f58937 | 2016-08-07 13:05:26 -0400 | [diff] [blame] | 19 | print_created(this, "of size", m_size); |
Wenzel Jakob | 38bd711 | 2015-07-05 20:05:44 +0200 | [diff] [blame] | 20 | m_data = new float[size]; |
| 21 | memset(m_data, 0, sizeof(float) * size); |
| 22 | } |
| 23 | |
| 24 | Sequence(const std::vector<float> &value) : m_size(value.size()) { |
Jason Rhinelander | 3f58937 | 2016-08-07 13:05:26 -0400 | [diff] [blame] | 25 | print_created(this, "of size", m_size, "from std::vector"); |
Wenzel Jakob | 38bd711 | 2015-07-05 20:05:44 +0200 | [diff] [blame] | 26 | m_data = new float[m_size]; |
| 27 | memcpy(m_data, &value[0], sizeof(float) * m_size); |
| 28 | } |
| 29 | |
| 30 | Sequence(const Sequence &s) : m_size(s.m_size) { |
Jason Rhinelander | 3f58937 | 2016-08-07 13:05:26 -0400 | [diff] [blame] | 31 | print_copy_created(this); |
Wenzel Jakob | 38bd711 | 2015-07-05 20:05:44 +0200 | [diff] [blame] | 32 | m_data = new float[m_size]; |
| 33 | memcpy(m_data, s.m_data, sizeof(float)*m_size); |
| 34 | } |
| 35 | |
| 36 | Sequence(Sequence &&s) : m_size(s.m_size), m_data(s.m_data) { |
Jason Rhinelander | 3f58937 | 2016-08-07 13:05:26 -0400 | [diff] [blame] | 37 | print_move_created(this); |
Wenzel Jakob | 38bd711 | 2015-07-05 20:05:44 +0200 | [diff] [blame] | 38 | s.m_size = 0; |
| 39 | s.m_data = nullptr; |
| 40 | } |
| 41 | |
| 42 | ~Sequence() { |
Jason Rhinelander | 3f58937 | 2016-08-07 13:05:26 -0400 | [diff] [blame] | 43 | print_destroyed(this); |
Wenzel Jakob | 38bd711 | 2015-07-05 20:05:44 +0200 | [diff] [blame] | 44 | delete[] m_data; |
| 45 | } |
| 46 | |
| 47 | Sequence &operator=(const Sequence &s) { |
Jason Rhinelander | 3f58937 | 2016-08-07 13:05:26 -0400 | [diff] [blame] | 48 | if (&s != this) { |
| 49 | delete[] m_data; |
| 50 | m_size = s.m_size; |
| 51 | m_data = new float[m_size]; |
| 52 | memcpy(m_data, s.m_data, sizeof(float)*m_size); |
| 53 | } |
| 54 | |
| 55 | print_copy_assigned(this); |
| 56 | |
Wenzel Jakob | 38bd711 | 2015-07-05 20:05:44 +0200 | [diff] [blame] | 57 | return *this; |
| 58 | } |
| 59 | |
| 60 | Sequence &operator=(Sequence &&s) { |
Wenzel Jakob | 38bd711 | 2015-07-05 20:05:44 +0200 | [diff] [blame] | 61 | if (&s != this) { |
| 62 | delete[] m_data; |
| 63 | m_size = s.m_size; |
| 64 | m_data = s.m_data; |
| 65 | s.m_size = 0; |
| 66 | s.m_data = nullptr; |
| 67 | } |
Jason Rhinelander | 3f58937 | 2016-08-07 13:05:26 -0400 | [diff] [blame] | 68 | |
| 69 | print_move_assigned(this); |
| 70 | |
Wenzel Jakob | 38bd711 | 2015-07-05 20:05:44 +0200 | [diff] [blame] | 71 | return *this; |
| 72 | } |
| 73 | |
| 74 | bool operator==(const Sequence &s) const { |
| 75 | if (m_size != s.size()) |
| 76 | return false; |
| 77 | for (size_t i=0; i<m_size; ++i) |
| 78 | if (m_data[i] != s[i]) |
| 79 | return false; |
| 80 | return true; |
| 81 | } |
| 82 | |
| 83 | bool operator!=(const Sequence &s) const { |
| 84 | return !operator==(s); |
| 85 | } |
| 86 | |
| 87 | float operator[](size_t index) const { |
| 88 | return m_data[index]; |
| 89 | } |
| 90 | |
| 91 | float &operator[](size_t index) { |
| 92 | return m_data[index]; |
| 93 | } |
| 94 | |
| 95 | bool contains(float v) const { |
| 96 | for (size_t i=0; i<m_size; ++i) |
| 97 | if (v == m_data[i]) |
| 98 | return true; |
| 99 | return false; |
| 100 | } |
| 101 | |
| 102 | Sequence reversed() const { |
| 103 | Sequence result(m_size); |
| 104 | for (size_t i=0; i<m_size; ++i) |
| 105 | result[m_size-i-1] = m_data[i]; |
| 106 | return result; |
| 107 | } |
| 108 | |
| 109 | size_t size() const { return m_size; } |
| 110 | |
Wenzel Jakob | b282595 | 2016-04-13 23:33:00 +0200 | [diff] [blame] | 111 | const float *begin() const { return m_data; } |
| 112 | const float *end() const { return m_data+m_size; } |
| 113 | |
Wenzel Jakob | 38bd711 | 2015-07-05 20:05:44 +0200 | [diff] [blame] | 114 | private: |
| 115 | size_t m_size; |
| 116 | float *m_data; |
| 117 | }; |
| 118 | |
Ivan Smirnov | 4c5e21b | 2016-08-24 23:30:00 +0100 | [diff] [blame^] | 119 | class IntPairs { |
| 120 | public: |
| 121 | IntPairs(std::vector<std::pair<int, int>> data) : data_(std::move(data)) {} |
| 122 | const std::pair<int, int>* begin() const { return data_.data(); } |
| 123 | |
| 124 | private: |
| 125 | std::vector<std::pair<int, int>> data_; |
| 126 | }; |
| 127 | |
Jason Rhinelander | 5aa85be | 2016-08-11 21:22:05 -0400 | [diff] [blame] | 128 | // Interface of a map-like object that isn't (directly) an unordered_map, but provides some basic |
| 129 | // map-like functionality. |
| 130 | class StringMap { |
| 131 | public: |
| 132 | StringMap(std::unordered_map<std::string, std::string> init = {}) |
| 133 | : map(std::move(init)) {} |
| 134 | |
| 135 | void set(std::string key, std::string val) { |
| 136 | map[key] = val; |
| 137 | } |
| 138 | |
| 139 | std::string get(std::string key) const { |
| 140 | return map.at(key); |
| 141 | } |
| 142 | |
| 143 | size_t size() const { |
| 144 | return map.size(); |
| 145 | } |
| 146 | |
| 147 | private: |
| 148 | std::unordered_map<std::string, std::string> map; |
| 149 | |
| 150 | public: |
| 151 | decltype(map.cbegin()) begin() const { return map.cbegin(); } |
| 152 | decltype(map.cend()) end() const { return map.cend(); } |
| 153 | }; |
| 154 | |
Ivan Smirnov | 4c5e21b | 2016-08-24 23:30:00 +0100 | [diff] [blame^] | 155 | template<typename T> |
| 156 | class NonZeroIterator { |
| 157 | const T* ptr_; |
| 158 | public: |
| 159 | NonZeroIterator(const T* ptr) : ptr_(ptr) {} |
| 160 | const T& operator*() const { return *ptr_; } |
| 161 | NonZeroIterator& operator++() { ++ptr_; return *this; } |
| 162 | }; |
| 163 | |
| 164 | class NonZeroSentinel {}; |
| 165 | |
| 166 | template<typename A, typename B> |
| 167 | bool operator==(const NonZeroIterator<std::pair<A, B>>& it, const NonZeroSentinel&) { |
| 168 | return !(*it).first || !(*it).second; |
| 169 | } |
Jason Rhinelander | 5aa85be | 2016-08-11 21:22:05 -0400 | [diff] [blame] | 170 | |
Jason Rhinelander | b3f3d79 | 2016-07-18 16:43:18 -0400 | [diff] [blame] | 171 | void init_ex_sequences_and_iterators(py::module &m) { |
Ivan Smirnov | 4c5e21b | 2016-08-24 23:30:00 +0100 | [diff] [blame^] | 172 | |
Wenzel Jakob | 38bd711 | 2015-07-05 20:05:44 +0200 | [diff] [blame] | 173 | py::class_<Sequence> seq(m, "Sequence"); |
| 174 | |
| 175 | seq.def(py::init<size_t>()) |
| 176 | .def(py::init<const std::vector<float>&>()) |
| 177 | /// Bare bones interface |
| 178 | .def("__getitem__", [](const Sequence &s, size_t i) { |
| 179 | if (i >= s.size()) |
| 180 | throw py::index_error(); |
| 181 | return s[i]; |
| 182 | }) |
| 183 | .def("__setitem__", [](Sequence &s, size_t i, float v) { |
| 184 | if (i >= s.size()) |
| 185 | throw py::index_error(); |
| 186 | s[i] = v; |
| 187 | }) |
| 188 | .def("__len__", &Sequence::size) |
| 189 | /// Optional sequence protocol operations |
Wenzel Jakob | b282595 | 2016-04-13 23:33:00 +0200 | [diff] [blame] | 190 | .def("__iter__", [](const Sequence &s) { return py::make_iterator(s.begin(), s.end()); }, |
| 191 | py::keep_alive<0, 1>() /* Essential: keep object alive while iterator exists */) |
Wenzel Jakob | 38bd711 | 2015-07-05 20:05:44 +0200 | [diff] [blame] | 192 | .def("__contains__", [](const Sequence &s, float v) { return s.contains(v); }) |
| 193 | .def("__reversed__", [](const Sequence &s) -> Sequence { return s.reversed(); }) |
| 194 | /// Slicing protocol (optional) |
| 195 | .def("__getitem__", [](const Sequence &s, py::slice slice) -> Sequence* { |
Wenzel Jakob | 0a07805 | 2016-05-29 13:40:40 +0200 | [diff] [blame] | 196 | size_t start, stop, step, slicelength; |
Wenzel Jakob | 38bd711 | 2015-07-05 20:05:44 +0200 | [diff] [blame] | 197 | if (!slice.compute(s.size(), &start, &stop, &step, &slicelength)) |
| 198 | throw py::error_already_set(); |
| 199 | Sequence *seq = new Sequence(slicelength); |
Wenzel Jakob | 0a07805 | 2016-05-29 13:40:40 +0200 | [diff] [blame] | 200 | for (size_t i=0; i<slicelength; ++i) { |
Wenzel Jakob | 38bd711 | 2015-07-05 20:05:44 +0200 | [diff] [blame] | 201 | (*seq)[i] = s[start]; start += step; |
| 202 | } |
| 203 | return seq; |
| 204 | }) |
| 205 | .def("__setitem__", [](Sequence &s, py::slice slice, const Sequence &value) { |
Wenzel Jakob | 0a07805 | 2016-05-29 13:40:40 +0200 | [diff] [blame] | 206 | size_t start, stop, step, slicelength; |
Wenzel Jakob | 38bd711 | 2015-07-05 20:05:44 +0200 | [diff] [blame] | 207 | if (!slice.compute(s.size(), &start, &stop, &step, &slicelength)) |
| 208 | throw py::error_already_set(); |
Wenzel Jakob | 0a07805 | 2016-05-29 13:40:40 +0200 | [diff] [blame] | 209 | if (slicelength != value.size()) |
Wenzel Jakob | 38bd711 | 2015-07-05 20:05:44 +0200 | [diff] [blame] | 210 | throw std::runtime_error("Left and right hand size of slice assignment have different sizes!"); |
Wenzel Jakob | 0a07805 | 2016-05-29 13:40:40 +0200 | [diff] [blame] | 211 | for (size_t i=0; i<slicelength; ++i) { |
Wenzel Jakob | 38bd711 | 2015-07-05 20:05:44 +0200 | [diff] [blame] | 212 | s[start] = value[i]; start += step; |
| 213 | } |
| 214 | }) |
| 215 | /// Comparisons |
| 216 | .def(py::self == py::self) |
| 217 | .def(py::self != py::self); |
| 218 | // Could also define py::self + py::self for concatenation, etc. |
| 219 | |
Jason Rhinelander | 5aa85be | 2016-08-11 21:22:05 -0400 | [diff] [blame] | 220 | py::class_<StringMap> map(m, "StringMap"); |
| 221 | |
| 222 | map .def(py::init<>()) |
| 223 | .def(py::init<std::unordered_map<std::string, std::string>>()) |
| 224 | .def("__getitem__", [](const StringMap &map, std::string key) { |
| 225 | try { return map.get(key); } |
| 226 | catch (const std::out_of_range&) { |
| 227 | throw py::key_error("key '" + key + "' does not exist"); |
| 228 | } |
| 229 | }) |
| 230 | .def("__setitem__", &StringMap::set) |
| 231 | .def("__len__", &StringMap::size) |
| 232 | .def("__iter__", [](const StringMap &map) { return py::make_key_iterator(map.begin(), map.end()); }, |
| 233 | py::keep_alive<0, 1>()) |
| 234 | .def("items", [](const StringMap &map) { return py::make_iterator(map.begin(), map.end()); }, |
| 235 | py::keep_alive<0, 1>()) |
| 236 | ; |
| 237 | |
Ivan Smirnov | 4c5e21b | 2016-08-24 23:30:00 +0100 | [diff] [blame^] | 238 | py::class_<IntPairs>(m, "IntPairs") |
| 239 | .def(py::init<std::vector<std::pair<int, int>>>()) |
| 240 | .def("nonzero", [](const IntPairs& s) { |
| 241 | return py::make_iterator(NonZeroIterator<std::pair<int, int>>(s.begin()), NonZeroSentinel()); |
| 242 | }, py::keep_alive<0, 1>()) |
| 243 | .def("nonzero_keys", [](const IntPairs& s) { |
| 244 | return py::make_key_iterator(NonZeroIterator<std::pair<int, int>>(s.begin()), NonZeroSentinel()); |
| 245 | }, py::keep_alive<0, 1>()); |
| 246 | |
Jason Rhinelander | 5aa85be | 2016-08-11 21:22:05 -0400 | [diff] [blame] | 247 | |
Wenzel Jakob | b282595 | 2016-04-13 23:33:00 +0200 | [diff] [blame] | 248 | #if 0 |
| 249 | // Obsolete: special data structure for exposing custom iterator types to python |
| 250 | // kept here for illustrative purposes because there might be some use cases which |
| 251 | // are not covered by the much simpler py::make_iterator |
| 252 | |
| 253 | struct PySequenceIterator { |
| 254 | PySequenceIterator(const Sequence &seq, py::object ref) : seq(seq), ref(ref) { } |
| 255 | |
| 256 | float next() { |
| 257 | if (index == seq.size()) |
| 258 | throw py::stop_iteration(); |
| 259 | return seq[index++]; |
| 260 | } |
| 261 | |
| 262 | const Sequence &seq; |
| 263 | py::object ref; // keep a reference |
| 264 | size_t index = 0; |
| 265 | }; |
| 266 | |
Wenzel Jakob | 38bd711 | 2015-07-05 20:05:44 +0200 | [diff] [blame] | 267 | py::class_<PySequenceIterator>(seq, "Iterator") |
| 268 | .def("__iter__", [](PySequenceIterator &it) -> PySequenceIterator& { return it; }) |
| 269 | .def("__next__", &PySequenceIterator::next); |
Wenzel Jakob | b282595 | 2016-04-13 23:33:00 +0200 | [diff] [blame] | 270 | |
| 271 | On the actual Sequence object, the iterator would be constructed as follows: |
| 272 | .def("__iter__", [](py::object s) { return PySequenceIterator(s.cast<const Sequence &>(), s); }) |
| 273 | #endif |
Wenzel Jakob | 38bd711 | 2015-07-05 20:05:44 +0200 | [diff] [blame] | 274 | } |