From e7a375d4bed82531898636403a53ec143c80b03f Mon Sep 17 00:00:00 2001 From: dearblue Date: Wed, 6 May 2026 18:19:50 +0900 Subject: [PATCH 1/2] Preparations for integrating the implementation of `Array#{permutation,combination}` - Modify the `mrb_combination_state` structure to accommodate feature extensions - Rename `Array#__repeated_combination` to `__combination` - Consolidate integer checks for arguments into `__combination` - Since checking for integer types using both `__to_int` and `0 <=>` is redundant, use only `__to_int` - Since `__combination` now accepts symbols instead of booleans, the call to `to_enum` has also been consolidated --- mrbgems/mruby-array-ext/mrblib/array.rb | 15 +++--- mrbgems/mruby-array-ext/src/array.c | 65 ++++++++++++++++--------- 2 files changed, 47 insertions(+), 33 deletions(-) diff --git a/mrbgems/mruby-array-ext/mrblib/array.rb b/mrbgems/mruby-array-ext/mrblib/array.rb index a9cfa8a76..5f74489f8 100644 --- a/mrbgems/mruby-array-ext/mrblib/array.rb +++ b/mrbgems/mruby-array-ext/mrblib/array.rb @@ -642,9 +642,7 @@ class Array # a = [1, 2, 3] # a.repeated_combination(2).to_a #=> [[1,1],[1,2],[1,3],[2,2],[2,3],[3,3]] def repeated_combination(n, &block) - raise TypeError, "no implicit conversion into Integer" unless 0 <=> n - return to_enum(:repeated_combination, n) unless block - __repeated_combination(n, false, &block) + __combination(:repeated_combination, n, &block) end ## @@ -667,14 +665,13 @@ class Array # a = [1, 2] # a.repeated_permutation(2).to_a #=> [[1,1],[1,2],[2,1],[2,2]] def repeated_permutation(n, &block) - n = n.__to_int - raise TypeError, "no implicit conversion into Integer" unless 0 <=> n - return to_enum(:repeated_permutation, n) unless block - __repeated_combination(n, true, &block) + __combination(:repeated_permutation, n, &block) end - def __repeated_combination(k, permutation, &block) + def __combination(mode, k, &block) k = k.__to_int + return to_enum(mode, k) unless block + case k when 0 yield [] @@ -686,7 +683,7 @@ class Array i += 1 end else - if state = __combination_init(k, permutation) + if state = __combination_init(mode, k) # Use C iterator for complex cases while tmp = __combination_next(state) yield tmp diff --git a/mrbgems/mruby-array-ext/src/array.c b/mrbgems/mruby-array-ext/src/array.c index fb137d65f..8819e665d 100644 --- a/mrbgems/mruby-array-ext/src/array.c +++ b/mrbgems/mruby-array-ext/src/array.c @@ -33,8 +33,7 @@ typedef khash_t(ary_set) ary_set_t; struct mrb_combination_state { mrb_int *indices; mrb_int n, k; /* nPk, nCk */ - mrb_bool permutation; - mrb_bool finished; + int mode; }; static void @@ -1532,6 +1531,11 @@ ary_deconstruct(mrb_state *mrb, mrb_value ary) return ary; } +enum { + comb_finished = 0, + comb_repeated_permutation = 1, + comb_repeated_combination = 2, +}; /* * Internal method to initialize combination state. @@ -1541,9 +1545,9 @@ static mrb_value ary_combination_init(mrb_state *mrb, mrb_value self) { mrb_int k; - mrb_bool permutation; + mrb_sym mode_sym; - mrb_get_args(mrb, "ib", &k, &permutation); + mrb_get_args(mrb, "ni", &mode_sym, &k); #if MRB_INT_MAX > SIZE_MAX if (k > SIZE_MAX) { mrb_raise(mrb, E_ARGUMENT_ERROR, "number too large"); @@ -1554,6 +1558,18 @@ ary_combination_init(mrb_state *mrb, mrb_value self) return mrb_nil_value(); } + int mode; + switch (mode_sym) { + case MRB_SYM(repeated_permutation): + mode = comb_repeated_permutation; + break; + case MRB_SYM(repeated_combination): + mode = comb_repeated_combination; + break; + default: + mrb_raise(mrb, E_ARGUMENT_ERROR, "wrong mode"); + } + struct RData *d; struct mrb_combination_state *state; Data_Make_Struct(mrb, mrb->object_class, struct mrb_combination_state, @@ -1561,8 +1577,7 @@ ary_combination_init(mrb_state *mrb, mrb_value self) state->k = k; state->n = RARRAY_LEN(self); - state->permutation = permutation; - state->finished = FALSE; + state->mode = mode; state->indices = (mrb_int*)mrb_calloc(mrb, k, sizeof(mrb_int)); return mrb_obj_value(d); @@ -1579,7 +1594,7 @@ ary_combination_next(mrb_state *mrb, mrb_value self) mrb_get_args(mrb, "d", &state, &mrb_combination_state_type); /* Check if iteration is complete */ - if (state->finished) return mrb_nil_value(); + if (state->mode == comb_finished) return mrb_nil_value(); /* Validate array hasn't been modified during iteration */ if (RARRAY_LEN(self) != state->n) { @@ -1589,7 +1604,7 @@ ary_combination_next(mrb_state *mrb, mrb_value self) /* Validate current indices are still in bounds */ for (mrb_int i = 0; i < state->k; i++) { if (state->indices[i] >= state->n) { - state->finished = TRUE; + state->mode = comb_finished; return mrb_nil_value(); } } @@ -1601,25 +1616,27 @@ ary_combination_next(mrb_state *mrb, mrb_value self) mrb_ary_push(mrb, result, p[state->indices[i]]); } - mrb_int pos = state->k - 1; - - while (pos >= 0) { - state->indices[pos]++; - if (state->indices[pos] < state->n) break; - pos--; - } - - if (pos < 0) { - state->finished = TRUE; - } - else { - /* Reset dependent indices */ - mrb_int reset = state->permutation ? 0 : state->indices[pos]; - for (pos++; pos < state->k; pos++) { - state->indices[pos] = reset; + switch (state->mode) { + case comb_repeated_permutation: + case comb_repeated_combination: + for (mrb_int i = state->k - 1; i >= 0; i--) { + state->indices[i]++; + if (state->indices[i] < state->n) { + /* Reset dependent indices */ + mrb_int reset = (state->mode == comb_repeated_permutation) ? 0 : state->indices[i]; + for (i++; i < state->k; i++) { + state->indices[i] = reset; + } + return result; + } } + break; + default: // it probably won’t happen, but just in case + result = mrb_nil_value(); + break; } + state->mode = comb_finished; return result; } From 3db11cef09dd0ab8120775cfc3de1b53e68a9dc9 Mon Sep 17 00:00:00 2001 From: dearblue Date: Wed, 6 May 2026 18:19:50 +0900 Subject: [PATCH 2/2] Integrate `Array#{permutation,combination}` into `Array#__combination` Compared to a pure Ruby implementation, this results in faster performance, eliminates recursive calls, and removes the creation of intermediate objects. The "permutation" implementation in `ary_combination_next()` is slow for C. However, it does not require a heap other than the index array. --- mrbgems/mruby-array-ext/mrblib/array.rb | 45 +----------------- mrbgems/mruby-array-ext/src/array.c | 61 +++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 43 deletions(-) diff --git a/mrbgems/mruby-array-ext/mrblib/array.rb b/mrbgems/mruby-array-ext/mrblib/array.rb index 5f74489f8..35abf95b4 100644 --- a/mrbgems/mruby-array-ext/mrblib/array.rb +++ b/mrbgems/mruby-array-ext/mrblib/array.rb @@ -450,27 +450,7 @@ class Array # a.permutation(0).to_a #=> [[]] # one permutation of length 0 # a.permutation(4).to_a #=> [] # no permutations of length 4 def permutation(n=self.size, &block) - n = n.__to_int - return to_enum(:permutation, n) unless block - size = self.size - if n == 0 - yield [] - elsif 0 < n && n <= size - i = 0 - while i 0 - ary = self[0...i] + self[i+1..-1] - ary.permutation(n-1) do |c| - yield result + c - end - else - yield result - end - i += 1 - end - end - self + __combination(:permutation, n, &block) end ## @@ -497,28 +477,7 @@ class Array # a.combination(5).to_a #=> [] # no combinations of length 5 def combination(n, &block) - n = n.__to_int - return to_enum(:combination, n) unless block - size = self.size - if n == 0 - yield [] - elsif n == 1 - i = 0 - while i RARRAY_LEN(self)) { + return mrb_nil_value(); + } + mode = comb_permutation; + break; + case MRB_SYM(combination): + if (k > RARRAY_LEN(self)) { + return mrb_nil_value(); + } + mode = comb_combination; + break; default: mrb_raise(mrb, E_ARGUMENT_ERROR, "wrong mode"); } @@ -1580,9 +1594,26 @@ ary_combination_init(mrb_state *mrb, mrb_value self) state->mode = mode; state->indices = (mrb_int*)mrb_calloc(mrb, k, sizeof(mrb_int)); + if (mode == comb_permutation || mode == comb_combination) { + for (mrb_int i = 0; i < k; i++) { + state->indices[i] = i; + } + } + return mrb_obj_value(d); } +static void +adjust_next_permutation_index(struct mrb_combination_state *state, mrb_int i) +{ + for (mrb_int j = i - 1; j >= 0; j--) { + if (state->indices[i] == state->indices[j]) { + state->indices[i]++; + j = i; + } + } +} + /* * Internal method to get next combination as index array. * Returns array of indices or nil when iteration is complete. @@ -1631,6 +1662,36 @@ ary_combination_next(mrb_state *mrb, mrb_value self) } } break; + case comb_permutation: + for (mrb_int i = state->k - 1; i >= 0; i--) { + state->indices[i]++; + + // adjust so that it does not overlap with the leading index + adjust_next_permutation_index(state, i); + + if (state->indices[i] < state->n) { + // adjust all trailing indexes to complete the function + for (i++; i < state->k; i++) { + state->indices[i] = 0; + adjust_next_permutation_index(state, i); + } + return result; + } + } + break; + case comb_combination: + for (mrb_int i = state->k - 1; i >= 0; i--) { + state->indices[i]++; + + if (state->indices[i] <= state->n - state->k + i) { + // replace each overflowed indices with an index incremented by 1 from the previous one + for (i++; i < state->k; i++) { + state->indices[i] = state->indices[i - 1] + 1; + } + return result; + } + } + break; default: // it probably won’t happen, but just in case result = mrb_nil_value(); break;