twisty_puzzles 0.0.1 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +21 -1
  3. data/README.md +6 -1
  4. data/ext/twisty_puzzles/native/cube_algorithm.c +267 -0
  5. data/ext/twisty_puzzles/native/cube_algorithm.h +5 -0
  6. data/ext/twisty_puzzles/native/cube_average.c +184 -0
  7. data/ext/twisty_puzzles/native/cube_average.h +5 -0
  8. data/ext/twisty_puzzles/native/cube_coordinate.c +207 -0
  9. data/ext/twisty_puzzles/native/cube_coordinate.h +34 -0
  10. data/ext/twisty_puzzles/native/cube_state.c +264 -0
  11. data/ext/twisty_puzzles/native/cube_state.h +31 -0
  12. data/ext/twisty_puzzles/native/extconf.rb +1 -1
  13. data/ext/twisty_puzzles/native/face_symbols.c +67 -0
  14. data/ext/twisty_puzzles/native/face_symbols.h +34 -0
  15. data/ext/twisty_puzzles/native/native.c +28 -0
  16. data/ext/twisty_puzzles/native/skewb_algorithm.c +331 -0
  17. data/ext/twisty_puzzles/native/skewb_algorithm.h +5 -0
  18. data/ext/twisty_puzzles/native/skewb_coordinate.c +237 -0
  19. data/ext/twisty_puzzles/native/skewb_coordinate.h +36 -0
  20. data/ext/twisty_puzzles/native/skewb_layer_fingerprint.c +271 -0
  21. data/ext/twisty_puzzles/native/skewb_layer_fingerprint.h +5 -0
  22. data/ext/twisty_puzzles/native/skewb_state.c +214 -0
  23. data/ext/twisty_puzzles/native/skewb_state.h +23 -0
  24. data/ext/twisty_puzzles/native/utils.c +76 -0
  25. data/ext/twisty_puzzles/native/utils.h +31 -0
  26. data/lib/twisty_puzzles.rb +38 -0
  27. data/lib/twisty_puzzles/abstract_direction.rb +38 -39
  28. data/lib/twisty_puzzles/abstract_move.rb +1 -2
  29. data/lib/twisty_puzzles/abstract_move_parser.rb +32 -33
  30. data/lib/twisty_puzzles/algorithm.rb +112 -113
  31. data/lib/twisty_puzzles/algorithm_transformation.rb +19 -21
  32. data/lib/twisty_puzzles/axis_face_and_direction_move.rb +56 -56
  33. data/lib/twisty_puzzles/cancellation_helper.rb +124 -125
  34. data/lib/twisty_puzzles/color_scheme.rb +1 -1
  35. data/lib/twisty_puzzles/commutator.rb +82 -80
  36. data/lib/twisty_puzzles/compiled_algorithm.rb +31 -32
  37. data/lib/twisty_puzzles/compiled_cube_algorithm.rb +49 -50
  38. data/lib/twisty_puzzles/compiled_skewb_algorithm.rb +18 -19
  39. data/lib/twisty_puzzles/coordinate.rb +243 -246
  40. data/lib/twisty_puzzles/cube.rb +494 -495
  41. data/lib/twisty_puzzles/cube_constants.rb +40 -41
  42. data/lib/twisty_puzzles/cube_direction.rb +15 -18
  43. data/lib/twisty_puzzles/cube_move.rb +285 -290
  44. data/lib/twisty_puzzles/cube_move_parser.rb +75 -76
  45. data/lib/twisty_puzzles/cube_print_helper.rb +133 -133
  46. data/lib/twisty_puzzles/cube_state.rb +80 -81
  47. data/lib/twisty_puzzles/move_type_creator.rb +17 -18
  48. data/lib/twisty_puzzles/parser.rb +176 -179
  49. data/lib/twisty_puzzles/part_cycle_factory.rb +39 -42
  50. data/lib/twisty_puzzles/puzzle.rb +16 -17
  51. data/lib/twisty_puzzles/reversible_applyable.rb +24 -25
  52. data/lib/twisty_puzzles/rotation.rb +76 -75
  53. data/lib/twisty_puzzles/skewb_direction.rb +14 -15
  54. data/lib/twisty_puzzles/skewb_move.rb +49 -49
  55. data/lib/twisty_puzzles/skewb_move_parser.rb +51 -51
  56. data/lib/twisty_puzzles/skewb_notation.rb +121 -118
  57. data/lib/twisty_puzzles/skewb_state.rb +120 -121
  58. data/lib/twisty_puzzles/state_helper.rb +20 -21
  59. data/lib/twisty_puzzles/sticker_cycle.rb +43 -44
  60. data/lib/twisty_puzzles/utils.rb +3 -0
  61. data/lib/twisty_puzzles/version.rb +3 -1
  62. metadata +30 -10
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4c23d462856d3827fe5386ca3d4d3816c2ce37cf6e06eb3876a9e4b18c4aef36
4
- data.tar.gz: 561a871d7287fde1d568a00b243dff15337c0ae785c3caa55e71a2e94a0d09a0
3
+ metadata.gz: 59a43662a260f2e013e0b59b6597dbfc4f6b5a04bc9d49b9f350b9c3a050babe
4
+ data.tar.gz: 98e23ce0bbcb677b9deb490ca02bb2e6c3fbd376c0c5a35dcb79d6e06afbd905
5
5
  SHA512:
6
- metadata.gz: b944ba73e3cf1c4cad55156237fd4c9a87888796ea5e6fbff6606c36a9d97b84a59e1c85a42318e70c71848399205dd117c8385a01d592be6f2bdbcdaeef3570
7
- data.tar.gz: 41894ab27afc69dffef137acb506819be9d94b3b27b339ba3ff1bc75731736575f412609a7e127920d60c9c5954f808ef98deafee6ec27f23856f1e794f67fdd
6
+ metadata.gz: fb187fa6e58c48444b5629e4ab195f2063d2ad9b0c8f07aa8d3a4e19486c177fb29b38a404e2a80f0690d6491466f94cd638d04fc6d76389ffb915671c2107a4
7
+ data.tar.gz: 9b89321ce8b01b446b992a318aaf43f2cdc54bc9745d16538f14e2bc93f038a413923a11c0e856e13712e20a29c6d0416344ed577ea5aac642ef295571d489a3
@@ -4,6 +4,26 @@ All notable changes to this project will be documented in this file.
4
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
5
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [0.0.5]
8
+ ### Changed
9
+ - Now `require 'twisty_puzzles'` also requires the native extensions.
10
+
11
+ ## [0.0.4]
12
+ ### Fixed
13
+ Fixed path in extconf.
14
+
15
+ ## [0.0.3]
16
+ ### Fixed
17
+ Added extension source files to gem files.
18
+
19
+ ## [0.0.2]
20
+ ### Changed
21
+ - Now a simple `require 'twisty_puzzles'` is enough, users don't need to require files separately.
22
+
23
+ ### Fixed
24
+ - Syntax error in file that wasn't included in tests previously.
25
+ - Rubocop fixes across the codebase.
26
+
7
27
  ## [0.0.1]
8
28
  ### Added
9
- Split off core twisty puzzles functionality from cube_trainer repo into a Gem.
29
+ - Split off core twisty puzzles functionality from cube_trainer repo into a Gem.
data/README.md CHANGED
@@ -1,3 +1,8 @@
1
+ ![Ruby](https://github.com/Lykos/twisty_puzzles/workflows/Ruby/badge.svg)
2
+ ![Rubocop](https://github.com/Lykos/twisty_puzzles/workflows/Rubocop/badge.svg)
3
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
+ [![Gem Version](https://badge.fury.io/rb/twisty_puzzles.svg)](https://badge.fury.io/rb/twisty_puzzles)
5
+
1
6
  # Twisty Puzzles
2
7
  Gem for my cube_trainer rails app. Some things are better left in a separate gem with no rails, e.g. native extensions. The main purpose is to support my Rails app, but if it's useful for someone else, feel free to use it at your own risk.
3
8
 
@@ -25,7 +30,7 @@ TODO: Write usage instructions here
25
30
 
26
31
  After checking out the repo, run `bundle install` to install dependencies. Then, run `bundle exec rake spec` to run the tests. You can also run `bundle exec bin/console` for an interactive prompt that will allow you to experiment.
27
32
 
28
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
33
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `lib/twisty_puzzles/version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
29
34
 
30
35
  ## Contributing
31
36
 
@@ -0,0 +1,267 @@
1
+ #include "cube_algorithm.h"
2
+
3
+ #include "face_symbols.h"
4
+ #include "cube_coordinate.h"
5
+ #include "cube_state.h"
6
+ #include "utils.h"
7
+
8
+ static ID slice_id;
9
+ static ID face_id;
10
+ static VALUE CubeAlgorithmClass = Qnil;
11
+
12
+ typedef enum {
13
+ SLICE,
14
+ FACE,
15
+ } CubeMoveType;
16
+
17
+ typedef struct {
18
+ CubeMoveType type;
19
+ face_index_t axis_face_index;
20
+ direction_t direction;
21
+ size_t slice_index;
22
+ } CubeMove;
23
+
24
+ typedef struct {
25
+ size_t size;
26
+ size_t cube_size;
27
+ CubeMove* moves;
28
+ } CubeAlgorithmData;
29
+
30
+ static void CubeAlgorithmData_free(void* const ptr) {
31
+ const CubeAlgorithmData* const data = ptr;
32
+ free(data->moves);
33
+ free(ptr);
34
+ }
35
+
36
+ static size_t CubeAlgorithmData_size(const void* const ptr) {
37
+ const CubeAlgorithmData* const data = ptr;
38
+ return sizeof(CubeAlgorithmData) + data->size * sizeof(CubeMove);
39
+ }
40
+
41
+ const rb_data_type_t CubeAlgorithmData_type = {
42
+ "TwistyPuzzles::Native::CubeAlgorithmData",
43
+ {NULL, CubeAlgorithmData_free, CubeAlgorithmData_size, NULL},
44
+ NULL, NULL,
45
+ RUBY_TYPED_FREE_IMMEDIATELY
46
+ };
47
+
48
+ static void check_moves(const CubeAlgorithmData* const data, const char* const name) {
49
+ for (size_t i = 0; i < data->size; ++i) {
50
+ const CubeMoveType type = data->moves[i].type;
51
+ if (type != SLICE && type != FACE) {
52
+ rb_raise(rb_eRuntimeError, "invalid move type %d in %s", type, name);
53
+ }
54
+ }
55
+ }
56
+
57
+ static CubeMove* malloc_moves(const size_t n) {
58
+ CubeMove* const moves = malloc(n * sizeof(CubeMove));
59
+ if (moves == NULL) {
60
+ rb_raise(rb_eNoMemError, "Allocating cube algorithm failed.");
61
+ }
62
+ return moves;
63
+ }
64
+
65
+ static VALUE CubeAlgorithm_alloc(const VALUE klass) {
66
+ CubeAlgorithmData* data;
67
+ const VALUE object = TypedData_Make_Struct(klass, CubeAlgorithmData, &CubeAlgorithmData_type, data);
68
+ data->size = 0;
69
+ data->cube_size = 0;
70
+ data->moves = NULL;
71
+ return object;
72
+ }
73
+
74
+ #define GetCubeAlgorithmData(obj, data) \
75
+ do { \
76
+ TypedData_Get_Struct((obj), CubeAlgorithmData, &CubeAlgorithmData_type, (data)); \
77
+ } while (0)
78
+
79
+ #define GetInitializedCubeAlgorithmData(obj, data) \
80
+ do { \
81
+ GetCubeAlgorithmData((obj), (data)); \
82
+ if (data->cube_size == 0) { \
83
+ rb_raise(rb_eRuntimeError, "Cube algorithm isn't initialized."); \
84
+ } \
85
+ } while(0)
86
+
87
+ static CubeMoveType extract_move_type(const VALUE move_symbol) {
88
+ Check_Type(move_symbol, T_SYMBOL);
89
+ const ID move_symbol_id = SYM2ID(move_symbol);
90
+ if (move_symbol_id == slice_id) {
91
+ return SLICE;
92
+ } else if (move_symbol_id == face_id) {
93
+ return FACE;
94
+ } else {
95
+ rb_raise(rb_eArgError, "Got invalid move symbol.");
96
+ }
97
+ }
98
+
99
+ static size_t components_for_move_type(const CubeMoveType type) {
100
+ switch (type) {
101
+ case SLICE:
102
+ return 4;
103
+ case FACE:
104
+ return 3;
105
+ default:
106
+ rb_raise(rb_eRuntimeError, "invalid move type %d in components_for_move_type", type);
107
+ }
108
+ }
109
+
110
+ static VALUE CubeAlgorithm_initialize(const VALUE self, const VALUE cube_size, const VALUE moves) {
111
+ Check_Type(moves, T_ARRAY);
112
+ CubeAlgorithmData* data;
113
+ GetCubeAlgorithmData(self, data);
114
+ data->size = RARRAY_LEN(moves);
115
+ data->cube_size = NUM2INT(cube_size);
116
+ data->moves = malloc_moves(data->size);
117
+ for (size_t i = 0; i < RARRAY_LEN(moves); ++i) {
118
+ const VALUE move = rb_ary_entry(moves, i);
119
+ if (RARRAY_LEN(move) < 1) {
120
+ rb_raise(rb_eArgError, "Moves cannot be empty.");
121
+ }
122
+ const CubeMoveType type = extract_move_type(rb_ary_entry(move, 0));
123
+ const size_t num_components = components_for_move_type(type);
124
+ if (RARRAY_LEN(move) != num_components) {
125
+ rb_raise(rb_eArgError, "Moves with the given type need to have %ld elements. Got %ld.", num_components, RARRAY_LEN(move));
126
+ }
127
+ data->moves[i].type = type;
128
+ data->moves[i].axis_face_index = face_index(rb_ary_entry(move, 1));
129
+ data->moves[i].direction = NUM2INT(rb_ary_entry(move, 2));
130
+ if (type == SLICE) {
131
+ const size_t slice_index = NUM2INT(rb_ary_entry(move, 3));
132
+ if (slice_index >= data->cube_size) {
133
+ rb_raise(rb_eArgError, "Invalid slice index %ld for cube size %ld.", slice_index, data->cube_size);
134
+ }
135
+ data->moves[i].slice_index = slice_index;
136
+ }
137
+ }
138
+ return self;
139
+ }
140
+
141
+ static void apply_move_to(const CubeMove move, const CubeStateData* const cube_state) {
142
+ switch (move.type) {
143
+ case SLICE:
144
+ rotate_slice_for_cube(move.axis_face_index, move.slice_index, move.direction, cube_state);
145
+ break;
146
+ case FACE:
147
+ rotate_face_for_cube(move.axis_face_index, move.direction, cube_state);
148
+ break;
149
+ default:
150
+ rb_raise(rb_eRuntimeError, "invalid move type %d in apply_move_to", move.type);
151
+ }
152
+ }
153
+
154
+ static VALUE CubeAlgorithm_apply_to(const VALUE self, const VALUE cube_state) {
155
+ const CubeStateData* cube_state_data;
156
+ GetInitializedCubeStateData(cube_state, cube_state_data);
157
+ const CubeAlgorithmData* data;
158
+ GetInitializedCubeAlgorithmData(self, data);
159
+ for (size_t i = 0; i < data->size; ++i) {
160
+ apply_move_to(data->moves[i], cube_state_data);
161
+ }
162
+ return Qnil;
163
+ }
164
+
165
+ static CubeMove rotate_move_by(const CubeMove move, const face_index_t rotation_face_index, const direction_t rotation_direction) {
166
+ CubeMove result = move;
167
+ if (!same_axis(move.axis_face_index, rotation_face_index)) {
168
+ const size_t index = neighbor_index(rotation_face_index, move.axis_face_index);
169
+ result.axis_face_index = neighbor_face_index(rotation_face_index, index + rotation_direction);
170
+ }
171
+ return result;
172
+ }
173
+
174
+ static VALUE CubeAlgorithm_rotate_by(const VALUE self, const VALUE rotation_face_symbol, const VALUE direction) {
175
+ const face_index_t rotation_face_index = face_index(rotation_face_symbol);
176
+ const direction_t rotation_direction = NUM2INT(direction);
177
+ const CubeAlgorithmData* data;
178
+ GetInitializedCubeAlgorithmData(self, data);
179
+ CubeAlgorithmData* rotated_data;
180
+ const VALUE rotated = TypedData_Make_Struct(CubeAlgorithmClass, CubeAlgorithmData, &CubeAlgorithmData_type, rotated_data);
181
+ rotated_data->size = data->size;
182
+ rotated_data->cube_size = data->cube_size;
183
+ rotated_data->moves = malloc_moves(rotated_data->size);
184
+ for (size_t i = 0; i < data->size; ++i) {
185
+ rotated_data->moves[i] = rotate_move_by(data->moves[i], rotation_face_index, rotation_direction);
186
+ }
187
+ return rotated;
188
+ }
189
+
190
+ static CubeMove mirror_move(const CubeMove move, const face_index_t normal_face_index) {
191
+ CubeMove result = move;
192
+ if (same_axis(move.axis_face_index, normal_face_index)) {
193
+ result.axis_face_index = opposite_face_index(move.axis_face_index);
194
+ }
195
+ result.direction = invert_cube_direction(move.direction);
196
+ return result;
197
+ }
198
+
199
+ static VALUE CubeAlgorithm_mirror(const VALUE self, const VALUE normal_face_symbol) {
200
+ const face_index_t normal_face_index = face_index(normal_face_symbol);
201
+ const CubeAlgorithmData* data;
202
+ GetInitializedCubeAlgorithmData(self, data);
203
+ CubeAlgorithmData* mirrored_data;
204
+ const VALUE mirrored = TypedData_Make_Struct(CubeAlgorithmClass, CubeAlgorithmData, &CubeAlgorithmData_type, mirrored_data);
205
+ mirrored_data->size = data->size;
206
+ mirrored_data->cube_size = data->cube_size;
207
+ mirrored_data->moves = malloc_moves(mirrored_data->size);
208
+ for (size_t i = 0; i < data->size; ++i) {
209
+ mirrored_data->moves[i] = mirror_move(data->moves[i], normal_face_index);
210
+ }
211
+ return mirrored;
212
+ }
213
+
214
+ static CubeMove invert_move(const CubeMove move) {
215
+ CubeMove result = move;
216
+ result.direction = invert_cube_direction(result.direction);
217
+ return result;
218
+ }
219
+
220
+ static VALUE CubeAlgorithm_inverse(const VALUE self) {
221
+ const CubeAlgorithmData* data;
222
+ GetInitializedCubeAlgorithmData(self, data);
223
+ CubeAlgorithmData* inverted_data;
224
+ const VALUE inverted = TypedData_Make_Struct(CubeAlgorithmClass, CubeAlgorithmData, &CubeAlgorithmData_type, inverted_data);
225
+ inverted_data->size = data->size;
226
+ inverted_data->cube_size = data->cube_size;
227
+ inverted_data->moves = malloc_moves(inverted_data->size);
228
+ for (size_t i = 0; i < data->size; ++i) {
229
+ inverted_data->moves[i] = invert_move(data->moves[data->size - 1 - i]);
230
+ }
231
+ return inverted;
232
+ }
233
+
234
+ static VALUE CubeAlgorithm_plus(const VALUE self, const VALUE other) {
235
+ const CubeAlgorithmData* self_data;
236
+ GetInitializedCubeAlgorithmData(self, self_data);
237
+ const CubeAlgorithmData* other_data;
238
+ GetInitializedCubeAlgorithmData(other, other_data);
239
+ if (self_data->cube_size != other_data->cube_size) {
240
+ rb_raise(rb_eArgError, "Cannot concatenate algorithms for different cube sizes %ld and %ld.", self_data->cube_size, other_data->cube_size);
241
+ }
242
+ CubeAlgorithmData* sum_data;
243
+ const VALUE sum = TypedData_Make_Struct(CubeAlgorithmClass, CubeAlgorithmData, &CubeAlgorithmData_type, sum_data);
244
+ sum_data->size = self_data->size + other_data->size;
245
+ sum_data->cube_size = self_data->cube_size;
246
+ sum_data->moves = malloc_moves(sum_data->size);
247
+ for (size_t i = 0; i < self_data->size; ++i) {
248
+ sum_data->moves[i] = self_data->moves[i];
249
+ }
250
+ for (size_t i = 0; i < other_data->size; ++i) {
251
+ sum_data->moves[self_data->size + i] = other_data->moves[i];
252
+ }
253
+ return sum;
254
+ }
255
+
256
+ void init_cube_algorithm_class_under(const VALUE module) {
257
+ slice_id = rb_intern("slice");
258
+ face_id = rb_intern("face");
259
+ CubeAlgorithmClass = rb_define_class_under(module, "CubeAlgorithm", rb_cObject);
260
+ rb_define_alloc_func(CubeAlgorithmClass, CubeAlgorithm_alloc);
261
+ rb_define_method(CubeAlgorithmClass, "initialize", CubeAlgorithm_initialize, 2);
262
+ rb_define_method(CubeAlgorithmClass, "apply_to", CubeAlgorithm_apply_to, 1);
263
+ rb_define_method(CubeAlgorithmClass, "rotate_by", CubeAlgorithm_rotate_by, 2);
264
+ rb_define_method(CubeAlgorithmClass, "mirror", CubeAlgorithm_mirror, 1);
265
+ rb_define_method(CubeAlgorithmClass, "inverse", CubeAlgorithm_inverse, 0);
266
+ rb_define_method(CubeAlgorithmClass, "+", CubeAlgorithm_plus, 1);
267
+ }
@@ -0,0 +1,5 @@
1
+ #pragma once
2
+
3
+ #include <ruby.h>
4
+
5
+ void init_cube_algorithm_class_under(VALUE module);
@@ -0,0 +1,184 @@
1
+ #include "cube_average.h"
2
+
3
+ #include <stdlib.h>
4
+
5
+ #include "utils.h"
6
+
7
+ VALUE CubeAverageClass = Qnil;
8
+ const double removed_fraction_per_side = 0.05;
9
+
10
+ typedef struct {
11
+ size_t capacity;
12
+ size_t size;
13
+ size_t insert_index;
14
+ double* values;
15
+ double average;
16
+ } CubeAverageData;
17
+
18
+ static size_t CubeAverageData_size(const void* const ptr) {
19
+ return sizeof(CubeAverageData);
20
+ }
21
+
22
+ const rb_data_type_t CubeAverageData_type = {
23
+ "TwistyPuzzles::Native::CubeAverageData",
24
+ {NULL, NULL, CubeAverageData_size, NULL},
25
+ NULL, NULL,
26
+ RUBY_TYPED_FREE_IMMEDIATELY
27
+ };
28
+
29
+ static double* malloc_values(const size_t n) {
30
+ double* const values = malloc(n * sizeof(double));
31
+ if (values == NULL) {
32
+ rb_raise(rb_eNoMemError, "Allocating values failed.");
33
+ }
34
+ return values;
35
+ }
36
+
37
+ #define GetCubeAverageData(obj, data) \
38
+ do { \
39
+ TypedData_Get_Struct((obj), CubeAverageData, &CubeAverageData_type, (data)); \
40
+ } while (0)
41
+ #define GetInitializedCubeAverageData(obj, data) \
42
+ do { \
43
+ GetCubeAverageData((obj), (data)); \
44
+ if (data->values == NULL) { \
45
+ rb_raise(rb_eArgError, "Cube average isn't initialized."); \
46
+ } \
47
+ } while (0)
48
+
49
+ static VALUE CubeAverage_alloc(const VALUE klass) {
50
+ CubeAverageData* data;
51
+ const VALUE object = TypedData_Make_Struct(klass, CubeAverageData, &CubeAverageData_type, data);
52
+ data->capacity = 0;
53
+ data->size = 0;
54
+ data->insert_index = 0;
55
+ data->values = NULL;
56
+ data->average = NAN;
57
+ return object;
58
+ }
59
+
60
+ static VALUE CubeAverage_initialize(const VALUE self, const VALUE capacity, const VALUE initial_average) {
61
+ Check_Type(capacity, T_FIXNUM);
62
+ const size_t n = FIX2INT(capacity);
63
+ if (n < 3) {
64
+ rb_raise(rb_eArgError, "The number of elements for a cube average has to be at least 3. Got %ld.", n);
65
+ }
66
+ if (n > 1000) {
67
+ rb_raise(rb_eArgError, "The number of elements for a cube average can be at most 1000, otherwise we need a better implementation. Got %ld.", n);
68
+ }
69
+
70
+ CubeAverageData* data;
71
+ GetCubeAverageData(self, data);
72
+
73
+ data->capacity = n;
74
+ data->size = 0;
75
+ data->values = malloc_values(n);
76
+ data->average = NUM2DBL(initial_average);
77
+
78
+ return self;
79
+ }
80
+
81
+ static VALUE CubeAverage_capacity(const VALUE self) {
82
+ const CubeAverageData* data;
83
+ GetInitializedCubeAverageData(self, data);
84
+ return INT2NUM(data->capacity);
85
+ }
86
+
87
+ static VALUE CubeAverage_length(const VALUE self) {
88
+ const CubeAverageData* data;
89
+ GetInitializedCubeAverageData(self, data);
90
+ return INT2NUM(data->size);
91
+ }
92
+
93
+ static int saturated(const CubeAverageData* const data) {
94
+ return data->size == data->capacity;
95
+ }
96
+
97
+ static int comp(const void* left_ptr, const void* right_ptr) {
98
+ const double left = *((double*)left_ptr);
99
+ const double right = *((double*)right_ptr);
100
+ if (left > right) { return 1; }
101
+ if (left < right) { return -1; }
102
+ return 0;
103
+ }
104
+
105
+ static double compute_average(const double* const values, const size_t size) {
106
+ double sum = 0;
107
+ for (size_t i = 0; i < size; ++i) {
108
+ sum += values[i];
109
+ }
110
+ return sum / size;
111
+ }
112
+
113
+ static double compute_cube_average(const double* const values, const size_t size) {
114
+ if (size <= 2) {
115
+ return compute_average(values, size);
116
+ }
117
+ double* const tmp = malloc_values(size);
118
+ memcpy(tmp, values, size * sizeof(double));
119
+ qsort(tmp, size, sizeof(double), comp);
120
+ const size_t num_removed = ceil(size * removed_fraction_per_side);
121
+ const double result = compute_average(tmp + num_removed, size - 2 * num_removed);
122
+ free(tmp);
123
+ return result;
124
+ }
125
+
126
+ static VALUE CubeAverage_push(const VALUE self, const VALUE new_value) {
127
+ CubeAverageData* data;
128
+ GetInitializedCubeAverageData(self, data);
129
+
130
+ data->values[data->insert_index] = NUM2DBL(new_value);
131
+ data->size = MIN(data->size + 1, data->capacity);
132
+ data->insert_index = (data->insert_index + 1) % data->capacity;
133
+ data->average = compute_cube_average(data->values, data->size);
134
+
135
+ return DBL2NUM(data->average);
136
+ }
137
+
138
+ static VALUE CubeAverage_push_all(const VALUE self, const VALUE new_values) {
139
+ Check_Type(new_values, T_ARRAY);
140
+ CubeAverageData* data;
141
+ GetInitializedCubeAverageData(self, data);
142
+
143
+ const size_t num_values = RARRAY_LEN(new_values);
144
+ if (num_values == 0) {
145
+ return DBL2NUM(data->average);
146
+ }
147
+ const size_t insert_index = data->insert_index;
148
+ const size_t capacity = data->capacity;
149
+ const size_t start = num_values > capacity ? num_values - capacity : 0;
150
+ for (size_t i = start; i < RARRAY_LEN(new_values); ++i) {
151
+ const VALUE new_value = rb_ary_entry(new_values, i);
152
+ data->values[(insert_index + i) % capacity] = NUM2DBL(new_value);
153
+ }
154
+
155
+ data->size = MIN(data->size + num_values, capacity);
156
+ data->insert_index = (insert_index + num_values) % capacity;
157
+ data->average = compute_cube_average(data->values, data->size);
158
+
159
+ return DBL2NUM(data->average);
160
+ }
161
+
162
+ static VALUE CubeAverage_saturated(const VALUE self) {
163
+ const CubeAverageData* data;
164
+ GetInitializedCubeAverageData(self, data);
165
+ return saturated(data) ? Qtrue : Qfalse;
166
+ }
167
+
168
+ static VALUE CubeAverage_average(const VALUE self) {
169
+ const CubeAverageData* data;
170
+ GetInitializedCubeAverageData(self, data);
171
+ return DBL2NUM(data->average);
172
+ }
173
+
174
+ void init_cube_average_class_under(const VALUE module) {
175
+ CubeAverageClass = rb_define_class_under(module, "CubeAverage", rb_cObject);
176
+ rb_define_alloc_func(CubeAverageClass, CubeAverage_alloc);
177
+ rb_define_method(CubeAverageClass, "initialize", CubeAverage_initialize, 2);
178
+ rb_define_method(CubeAverageClass, "capacity", CubeAverage_capacity, 0);
179
+ rb_define_method(CubeAverageClass, "length", CubeAverage_length, 0);
180
+ rb_define_method(CubeAverageClass, "push", CubeAverage_push, 1);
181
+ rb_define_method(CubeAverageClass, "push_all", CubeAverage_push_all, 1);
182
+ rb_define_method(CubeAverageClass, "saturated?", CubeAverage_saturated, 0);
183
+ rb_define_method(CubeAverageClass, "average", CubeAverage_average, 0);
184
+ }