array2d.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. #include "pocketpy/array2d.h"
  2. namespace pkpy{
  3. struct Array2d{
  4. PK_ALWAYS_PASS_BY_POINTER(Array2d)
  5. PY_CLASS(Array2d, array2d, array2d)
  6. PyObject** data;
  7. int n_cols;
  8. int n_rows;
  9. int numel;
  10. Array2d(){
  11. data = nullptr;
  12. n_cols = 0;
  13. n_rows = 0;
  14. numel = 0;
  15. }
  16. Array2d* _() { return this; }
  17. void init(int n_cols, int n_rows){
  18. this->n_cols = n_cols;
  19. this->n_rows = n_rows;
  20. this->numel = n_cols * n_rows;
  21. this->data = new PyObject*[numel];
  22. }
  23. bool is_valid(int col, int row) const{
  24. return 0 <= col && col < n_cols && 0 <= row && row < n_rows;
  25. }
  26. PyObject* _get(int col, int row){
  27. return data[row * n_cols + col];
  28. }
  29. void _set(int col, int row, PyObject* value){
  30. data[row * n_cols + col] = value;
  31. }
  32. static void _register(VM* vm, PyObject* mod, PyObject* type){
  33. vm->bind(type, "__new__(cls, *args, **kwargs)", [](VM* vm, ArgsView args){
  34. Type cls = PK_OBJ_GET(Type, args[0]);
  35. return vm->heap.gcnew<Array2d>(cls);
  36. });
  37. vm->bind(type, "__init__(self, n_cols: int, n_rows: int, default=None)", [](VM* vm, ArgsView args){
  38. Array2d& self = PK_OBJ_GET(Array2d, args[0]);
  39. int n_cols = CAST(int, args[1]);
  40. int n_rows = CAST(int, args[2]);
  41. if(n_cols <= 0 || n_rows <= 0){
  42. vm->ValueError("n_cols and n_rows must be positive integers");
  43. }
  44. self.init(n_cols, n_rows);
  45. if(vm->py_callable(args[3])){
  46. for(int i = 0; i < self.numel; i++) self.data[i] = vm->call(args[3]);
  47. }else{
  48. for(int i = 0; i < self.numel; i++) self.data[i] = args[3];
  49. }
  50. return vm->None;
  51. });
  52. PY_READONLY_FIELD(Array2d, "n_cols", _, n_cols);
  53. PY_READONLY_FIELD(Array2d, "n_rows", _, n_rows);
  54. PY_READONLY_FIELD(Array2d, "width", _, n_cols);
  55. PY_READONLY_FIELD(Array2d, "height", _, n_rows);
  56. PY_READONLY_FIELD(Array2d, "numel", _, numel);
  57. vm->bind(type, "is_valid(self, col: int, row: int)", [](VM* vm, ArgsView args){
  58. Array2d& self = PK_OBJ_GET(Array2d, args[0]);
  59. int col = CAST(int, args[1]);
  60. int row = CAST(int, args[2]);
  61. return VAR(self.is_valid(col, row));
  62. });
  63. vm->bind(type, "get(self, col: int, row: int, default=None)", [](VM* vm, ArgsView args){
  64. Array2d& self = PK_OBJ_GET(Array2d, args[0]);
  65. int col = CAST(int, args[1]);
  66. int row = CAST(int, args[2]);
  67. if(!self.is_valid(col, row)) return args[3];
  68. return self._get(col, row);
  69. });
  70. #define HANDLE_SLICE() \
  71. int start_col, stop_col, step_col; \
  72. int start_row, stop_row, step_row; \
  73. vm->parse_int_slice(PK_OBJ_GET(Slice, xy[0]), self.n_cols, start_col, stop_col, step_col); \
  74. vm->parse_int_slice(PK_OBJ_GET(Slice, xy[1]), self.n_rows, start_row, stop_row, step_row); \
  75. if(step_col != 1 || step_row != 1) vm->ValueError("slice step must be 1"); \
  76. int slice_width = stop_col - start_col; \
  77. int slice_height = stop_row - start_row; \
  78. if(slice_width <= 0 || slice_height <= 0) vm->ValueError("slice width and height must be positive");
  79. vm->bind__getitem__(PK_OBJ_GET(Type, type), [](VM* vm, PyObject* _0, PyObject* _1){
  80. Array2d& self = PK_OBJ_GET(Array2d, _0);
  81. const Tuple& xy = CAST(Tuple&, _1);
  82. i64 col, row;
  83. if(try_cast_int(xy[0], &col) && try_cast_int(xy[1], &row)){
  84. if(!self.is_valid(col, row)){
  85. vm->IndexError(_S('(', col, ", ", row, ')', " is not a valid index for array2d(", self.n_cols, ", ", self.n_rows, ')'));
  86. }
  87. return self._get(col, row);
  88. }
  89. if(is_non_tagged_type(xy[0], VM::tp_slice) && is_non_tagged_type(xy[1], VM::tp_slice)){
  90. HANDLE_SLICE();
  91. PyObject* new_array_obj = vm->heap.gcnew<Array2d>(Array2d::_type(vm));
  92. Array2d& new_array = PK_OBJ_GET(Array2d, new_array_obj);
  93. new_array.init(stop_col - start_col, stop_row - start_row);
  94. for(int j = start_row; j < stop_row; j++){
  95. for(int i = start_col; i < stop_col; i++){
  96. new_array._set(i - start_col, j - start_row, self._get(i, j));
  97. }
  98. }
  99. return new_array_obj;
  100. }
  101. vm->TypeError("expected `tuple[int, int]` or `tuple[slice, slice]` as index");
  102. PK_UNREACHABLE();
  103. });
  104. vm->bind__setitem__(PK_OBJ_GET(Type, type), [](VM* vm, PyObject* _0, PyObject* _1, PyObject* _2){
  105. Array2d& self = PK_OBJ_GET(Array2d, _0);
  106. const Tuple& xy = CAST(Tuple&, _1);
  107. i64 col, row;
  108. if(try_cast_int(xy[0], &col) && try_cast_int(xy[1], &row)){
  109. if(!self.is_valid(col, row)){
  110. vm->IndexError(_S('(', col, ", ", row, ')', " is not a valid index for array2d(", self.n_cols, ", ", self.n_rows, ')'));
  111. }
  112. self._set(col, row, _2);
  113. return;
  114. }
  115. if(is_non_tagged_type(xy[0], VM::tp_slice) && is_non_tagged_type(xy[1], VM::tp_slice)){
  116. HANDLE_SLICE();
  117. Array2d& other = CAST(Array2d&, _2); // _2 must be an array2d
  118. if(slice_width != other.n_cols || slice_height != other.n_rows){
  119. vm->ValueError("array2d size does not match the slice size");
  120. }
  121. for(int j = 0; j < slice_height; j++){
  122. for(int i = 0; i < slice_width; i++){
  123. self._set(i + start_col, j + start_row, other._get(i, j));
  124. }
  125. }
  126. return;
  127. }
  128. vm->TypeError("expected `tuple[int, int]` or `tuple[slice, slice]` as index");
  129. });
  130. #undef HANDLE_SLICE
  131. vm->bind(type, "tolist(self)", [](VM* vm, ArgsView args){
  132. Array2d& self = PK_OBJ_GET(Array2d, args[0]);
  133. List t(self.n_rows);
  134. for(int j = 0; j < self.n_rows; j++){
  135. List row(self.n_cols);
  136. for(int i = 0; i < self.n_cols; i++) row[i] = self._get(i, j);
  137. t[j] = VAR(std::move(row));
  138. }
  139. return VAR(std::move(t));
  140. });
  141. vm->bind__len__(PK_OBJ_GET(Type, type), [](VM* vm, PyObject* _0){
  142. Array2d& self = PK_OBJ_GET(Array2d, _0);
  143. return (i64)self.n_rows;
  144. });
  145. vm->bind__repr__(PK_OBJ_GET(Type, type), [](VM* vm, PyObject* _0){
  146. Array2d& self = PK_OBJ_GET(Array2d, _0);
  147. return VAR(_S("array2d(", self.n_cols, ", ", self.n_rows, ')'));
  148. });
  149. vm->bind(type, "map(self, f)", [](VM* vm, ArgsView args){
  150. Array2d& self = PK_OBJ_GET(Array2d, args[0]);
  151. PyObject* f = args[1];
  152. PyObject* new_array_obj = vm->heap.gcnew<Array2d>(Array2d::_type(vm));
  153. Array2d& new_array = PK_OBJ_GET(Array2d, new_array_obj);
  154. new_array.init(self.n_cols, self.n_rows);
  155. for(int i = 0; i < new_array.numel; i++){
  156. new_array.data[i] = vm->call(f, self.data[i]);
  157. }
  158. return new_array_obj;
  159. });
  160. vm->bind(type, "copy(self)", [](VM* vm, ArgsView args){
  161. Array2d& self = PK_OBJ_GET(Array2d, args[0]);
  162. PyObject* new_array_obj = vm->heap.gcnew<Array2d>(Array2d::_type(vm));
  163. Array2d& new_array = PK_OBJ_GET(Array2d, new_array_obj);
  164. new_array.init(self.n_cols, self.n_rows);
  165. for(int i = 0; i < new_array.numel; i++){
  166. new_array.data[i] = self.data[i];
  167. }
  168. return new_array_obj;
  169. });
  170. vm->bind(type, "fill_(self, value)", [](VM* vm, ArgsView args){
  171. Array2d& self = PK_OBJ_GET(Array2d, args[0]);
  172. for(int i = 0; i < self.numel; i++){
  173. self.data[i] = args[1];
  174. }
  175. return vm->None;
  176. });
  177. vm->bind(type, "apply_(self, f)", [](VM* vm, ArgsView args){
  178. Array2d& self = PK_OBJ_GET(Array2d, args[0]);
  179. PyObject* f = args[1];
  180. for(int i = 0; i < self.numel; i++){
  181. self.data[i] = vm->call(f, self.data[i]);
  182. }
  183. return vm->None;
  184. });
  185. vm->bind(type, "copy_(self, other)", [](VM* vm, ArgsView args){
  186. Array2d& self = PK_OBJ_GET(Array2d, args[0]);
  187. if(is_non_tagged_type(args[1], VM::tp_list)){
  188. const List& list = PK_OBJ_GET(List, args[1]);
  189. if(list.size() != self.numel){
  190. vm->ValueError("list size must be equal to the number of elements in the array2d");
  191. }
  192. for(int i = 0; i < self.numel; i++){
  193. self.data[i] = list[i];
  194. }
  195. return vm->None;
  196. }
  197. Array2d& other = CAST(Array2d&, args[1]);
  198. // if self and other have different sizes, re-initialize self
  199. if(self.n_cols != other.n_cols || self.n_rows != other.n_rows){
  200. delete self.data;
  201. self.init(other.n_cols, other.n_rows);
  202. }
  203. for(int i = 0; i < self.numel; i++){
  204. self.data[i] = other.data[i];
  205. }
  206. return vm->None;
  207. });
  208. vm->bind__eq__(PK_OBJ_GET(Type, type), [](VM* vm, PyObject* _0, PyObject* _1){
  209. Array2d& self = PK_OBJ_GET(Array2d, _0);
  210. if(!is_non_tagged_type(_1, Array2d::_type(vm))) return vm->NotImplemented;
  211. Array2d& other = PK_OBJ_GET(Array2d, _1);
  212. if(self.n_cols != other.n_cols || self.n_rows != other.n_rows) return vm->False;
  213. for(int i = 0; i < self.numel; i++){
  214. if(vm->py_ne(self.data[i], other.data[i])) return vm->False;
  215. }
  216. return vm->True;
  217. });
  218. vm->bind(type, "count_neighbors(self, value, neighborhood='moore') -> array2d[int]", [](VM* vm, ArgsView args){
  219. Array2d& self = PK_OBJ_GET(Array2d, args[0]);
  220. PyObject* new_array_obj = vm->heap.gcnew<Array2d>(Array2d::_type(vm));
  221. Array2d& new_array = PK_OBJ_GET(Array2d, new_array_obj);
  222. new_array.init(self.n_cols, self.n_rows);
  223. PyObject* value = args[1];
  224. const Str& neighborhood = CAST(Str&, args[2]);
  225. if(neighborhood == "moore"){
  226. for(int j = 0; j < new_array.n_rows; j++){
  227. for(int i = 0; i < new_array.n_cols; i++){
  228. int count = 0;
  229. count += self.is_valid(i-1, j-1) && vm->py_eq(self._get(i-1, j-1), value);
  230. count += self.is_valid(i, j-1) && vm->py_eq(self._get(i, j-1), value);
  231. count += self.is_valid(i+1, j-1) && vm->py_eq(self._get(i+1, j-1), value);
  232. count += self.is_valid(i-1, j) && vm->py_eq(self._get(i-1, j), value);
  233. count += self.is_valid(i+1, j) && vm->py_eq(self._get(i+1, j), value);
  234. count += self.is_valid(i-1, j+1) && vm->py_eq(self._get(i-1, j+1), value);
  235. count += self.is_valid(i, j+1) && vm->py_eq(self._get(i, j+1), value);
  236. count += self.is_valid(i+1, j+1) && vm->py_eq(self._get(i+1, j+1), value);
  237. new_array._set(i, j, VAR(count));
  238. }
  239. }
  240. }else if(neighborhood == "von_neumann"){
  241. for(int j = 0; j < new_array.n_rows; j++){
  242. for(int i = 0; i < new_array.n_cols; i++){
  243. int count = 0;
  244. count += self.is_valid(i, j-1) && vm->py_eq(self._get(i, j-1), value);
  245. count += self.is_valid(i-1, j) && vm->py_eq(self._get(i-1, j), value);
  246. count += self.is_valid(i+1, j) && vm->py_eq(self._get(i+1, j), value);
  247. count += self.is_valid(i, j+1) && vm->py_eq(self._get(i, j+1), value);
  248. new_array._set(i, j, VAR(count));
  249. }
  250. }
  251. }else{
  252. vm->ValueError("neighborhood must be 'moore' or 'von_neumann'");
  253. }
  254. return new_array_obj;
  255. });
  256. vm->bind(type, "count(self, value) -> int", [](VM* vm, ArgsView args){
  257. Array2d& self = PK_OBJ_GET(Array2d, args[0]);
  258. PyObject* value = args[1];
  259. int count = 0;
  260. for(int i = 0; i < self.numel; i++) count += vm->py_eq(self.data[i], value);
  261. return VAR(count);
  262. });
  263. vm->bind(type, "find_bounding_rect(self, value)", [](VM* vm, ArgsView args){
  264. Array2d& self = PK_OBJ_GET(Array2d, args[0]);
  265. PyObject* value = args[1];
  266. int left = self.n_cols;
  267. int top = self.n_rows;
  268. int right = 0;
  269. int bottom = 0;
  270. for(int j = 0; j < self.n_rows; j++){
  271. for(int i = 0; i < self.n_cols; i++){
  272. if(vm->py_eq(self._get(i, j), value)){
  273. left = std::min(left, i);
  274. top = std::min(top, j);
  275. right = std::max(right, i);
  276. bottom = std::max(bottom, j);
  277. }
  278. }
  279. }
  280. int width = right - left + 1;
  281. int height = bottom - top + 1;
  282. if(width <= 0 || height <= 0) return vm->None;
  283. return VAR(Tuple(VAR(left), VAR(top), VAR(width), VAR(height)));
  284. });
  285. }
  286. void _gc_mark() const{
  287. for(int i = 0; i < numel; i++) PK_OBJ_MARK(data[i]);
  288. }
  289. ~Array2d(){
  290. delete[] data;
  291. }
  292. };
  293. void add_module_array2d(VM* vm){
  294. PyObject* mod = vm->new_module("array2d");
  295. Array2d::register_class(vm, mod);
  296. }
  297. } // namespace pkpy