mirror of
https://github.com/ruby/ruby.git
synced 2026-10-28 16:36:20 +08:00
Adjust the specs to handle the backwards compatibility breakage. While we will backport this to Ruby 4.0, to avoid temporary breakage, leave Ruby 4.0 behavior unspecified until the fixes are backported to it. Co-authored-by: Jeremy Evans <code@jeremyevans.net>
2664 lines
70 KiB
C
2664 lines
70 KiB
C
/* This implements sets using the same hash table implementation as in
|
|
st.c, but without a value for each hash entry. This results in the
|
|
same basic performance characteristics as when using an st table,
|
|
but uses 1/3 less memory.
|
|
*/
|
|
|
|
#include "id.h"
|
|
#include "internal.h"
|
|
#include "internal/bits.h"
|
|
#include "internal/error.h"
|
|
#include "internal/hash.h"
|
|
#include "internal/object.h"
|
|
#include "internal/proc.h"
|
|
#include "internal/sanitizers.h"
|
|
#include "internal/set.h"
|
|
#include "internal/set_table.h"
|
|
#include "internal/symbol.h"
|
|
#include "internal/variable.h"
|
|
#include "ruby_assert.h"
|
|
|
|
#include <stdio.h>
|
|
#ifdef HAVE_STDLIB_H
|
|
#include <stdlib.h>
|
|
#endif
|
|
#include <string.h>
|
|
|
|
#ifndef SET_DEBUG
|
|
#define SET_DEBUG 0
|
|
#endif
|
|
|
|
#if SET_DEBUG
|
|
#include "internal/gc.h"
|
|
#endif
|
|
|
|
static st_index_t
|
|
dbl_to_index(double d)
|
|
{
|
|
union {double d; st_index_t i;} u;
|
|
u.d = d;
|
|
return u.i;
|
|
}
|
|
|
|
static const uint64_t prime1 = ((uint64_t)0x2e0bb864 << 32) | 0xe9ea7df5;
|
|
static const uint32_t prime2 = 0x830fcab9;
|
|
|
|
static inline uint64_t
|
|
mult_and_mix(uint64_t m1, uint64_t m2)
|
|
{
|
|
#if defined HAVE_UINT128_T
|
|
uint128_t r = (uint128_t) m1 * (uint128_t) m2;
|
|
return (uint64_t) (r >> 64) ^ (uint64_t) r;
|
|
#else
|
|
uint64_t hm1 = m1 >> 32, hm2 = m2 >> 32;
|
|
uint64_t lm1 = m1, lm2 = m2;
|
|
uint64_t v64_128 = hm1 * hm2;
|
|
uint64_t v32_96 = hm1 * lm2 + lm1 * hm2;
|
|
uint64_t v1_32 = lm1 * lm2;
|
|
|
|
return (v64_128 + (v32_96 >> 32)) ^ ((v32_96 << 32) + v1_32);
|
|
#endif
|
|
}
|
|
|
|
static inline uint64_t
|
|
key64_hash(uint64_t key, uint32_t seed)
|
|
{
|
|
return mult_and_mix(key + seed, prime1);
|
|
}
|
|
|
|
/* Should cast down the result for each purpose */
|
|
#define set_index_hash(index) key64_hash(rb_hash_start(index), prime2)
|
|
|
|
static st_index_t
|
|
set_ident_hash(st_data_t n)
|
|
{
|
|
#ifdef USE_FLONUM /* RUBY */
|
|
/*
|
|
* - flonum (on 64-bit) is pathologically bad, mix the actual
|
|
* float value in, but do not use the float value as-is since
|
|
* many integers get interpreted as 2.0 or -2.0 [Bug #10761]
|
|
*/
|
|
if (FLONUM_P(n)) {
|
|
n ^= dbl_to_index(rb_float_value(n));
|
|
}
|
|
#endif
|
|
|
|
return (st_index_t)set_index_hash((st_index_t)n);
|
|
}
|
|
|
|
static const struct st_hash_type identhash = {
|
|
rb_st_numcmp,
|
|
set_ident_hash,
|
|
};
|
|
|
|
static const struct st_hash_type objhash = {
|
|
rb_any_cmp,
|
|
rb_any_hash,
|
|
};
|
|
|
|
VALUE rb_cSet;
|
|
static VALUE set_i_compare_by_identity(VALUE set);
|
|
|
|
#define id_each idEach
|
|
static ID id_each_entry;
|
|
static ID id_any_p;
|
|
static ID id_new;
|
|
static ID id_i_hash;
|
|
static ID id_set_iter_lev;
|
|
static ID id_subclass_compatible;
|
|
static ID id_class_methods;
|
|
|
|
#define RSET_INITIALIZED FL_USER1
|
|
#define RSET_LEV_MASK (FL_USER13 | FL_USER14 | FL_USER15 | /* FL 13..19 */ \
|
|
FL_USER16 | FL_USER17 | FL_USER18 | FL_USER19)
|
|
#define RSET_LEV_SHIFT (FL_USHIFT + 13)
|
|
#define RSET_LEV_MAX 127 /* 7 bits */
|
|
|
|
#define SET_ASSERT(expr) RUBY_ASSERT_MESG_WHEN(SET_DEBUG, expr, #expr)
|
|
|
|
#define RSET_SIZE(set) set_table_size(RSET_TABLE(set))
|
|
#define RSET_EMPTY(set) (RSET_SIZE(set) == 0)
|
|
#define RSET_SIZE_NUM(set) SIZET2NUM(RSET_SIZE(set))
|
|
#define RSET_IS_MEMBER(set, item) set_table_lookup(RSET_TABLE(set), (st_data_t)(item))
|
|
#define RSET_COMPARE_BY_IDENTITY(set) (RSET_TABLE(set)->type == &identhash)
|
|
|
|
struct set_object {
|
|
set_table table;
|
|
};
|
|
|
|
static int
|
|
mark_and_pin_key(st_data_t key, st_data_t data)
|
|
{
|
|
rb_gc_mark((VALUE)key);
|
|
|
|
return ST_CONTINUE;
|
|
}
|
|
|
|
static int
|
|
mark_key(st_data_t key, st_data_t data)
|
|
{
|
|
rb_gc_mark_movable((VALUE)key);
|
|
|
|
return ST_CONTINUE;
|
|
}
|
|
|
|
static void
|
|
set_mark(void *ptr)
|
|
{
|
|
struct set_object *sobj = ptr;
|
|
if (sobj->table.entries) {
|
|
if (sobj->table.type == &identhash) {
|
|
set_table_foreach(&sobj->table, mark_and_pin_key, 0);
|
|
}
|
|
else {
|
|
set_table_foreach(&sobj->table, mark_key, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
set_free(void *ptr)
|
|
{
|
|
struct set_object *sobj = ptr;
|
|
set_free_embedded_table(&sobj->table);
|
|
}
|
|
|
|
static size_t
|
|
set_size(const void *ptr)
|
|
{
|
|
const struct set_object *sobj = ptr;
|
|
/* Do not count the table size twice, as it is embedded */
|
|
return (unsigned long)set_memsize(&sobj->table) - sizeof(sobj->table);
|
|
}
|
|
|
|
static int
|
|
set_foreach_replace(st_data_t key, st_data_t argp, int error)
|
|
{
|
|
if (rb_gc_location((VALUE)key) != (VALUE)key) {
|
|
return ST_REPLACE;
|
|
}
|
|
|
|
return ST_CONTINUE;
|
|
}
|
|
|
|
static int
|
|
set_replace_ref(st_data_t *key, st_data_t argp, int existing)
|
|
{
|
|
rb_gc_mark_and_move((VALUE *)key);
|
|
|
|
return ST_CONTINUE;
|
|
}
|
|
|
|
static void
|
|
set_update_references(void *ptr)
|
|
{
|
|
struct set_object *sobj = ptr;
|
|
set_foreach_with_replace(&sobj->table, set_foreach_replace, set_replace_ref, 0);
|
|
}
|
|
|
|
static const rb_data_type_t set_data_type = {
|
|
.wrap_struct_name = "set",
|
|
.function = {
|
|
.dmark = set_mark,
|
|
.dfree = set_free,
|
|
.dsize = set_size,
|
|
.dcompact = set_update_references,
|
|
},
|
|
.flags = RUBY_TYPED_EMBEDDABLE | RUBY_TYPED_THREAD_SAFE_FREE | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FROZEN_SHAREABLE
|
|
};
|
|
|
|
static inline set_table *
|
|
RSET_TABLE(VALUE set)
|
|
{
|
|
struct set_object *sobj;
|
|
TypedData_Get_Struct(set, struct set_object, &set_data_type, sobj);
|
|
return &sobj->table;
|
|
}
|
|
|
|
static unsigned long
|
|
iter_lev_in_ivar(VALUE set)
|
|
{
|
|
VALUE levval = rb_ivar_get(set, id_set_iter_lev);
|
|
SET_ASSERT(FIXNUM_P(levval));
|
|
long lev = FIX2LONG(levval);
|
|
SET_ASSERT(lev >= 0);
|
|
return (unsigned long)lev;
|
|
}
|
|
|
|
void rb_ivar_set_internal(VALUE obj, ID id, VALUE val);
|
|
|
|
static void
|
|
iter_lev_in_ivar_set(VALUE set, unsigned long lev)
|
|
{
|
|
SET_ASSERT(lev >= RSET_LEV_MAX);
|
|
SET_ASSERT(POSFIXABLE(lev)); /* POSFIXABLE means fitting to long */
|
|
rb_ivar_set_internal(set, id_set_iter_lev, LONG2FIX((long)lev));
|
|
}
|
|
|
|
static inline unsigned long
|
|
iter_lev_in_flags(VALUE set)
|
|
{
|
|
return (unsigned long)((RBASIC(set)->flags >> RSET_LEV_SHIFT) & RSET_LEV_MAX);
|
|
}
|
|
|
|
static inline void
|
|
iter_lev_in_flags_set(VALUE set, unsigned long lev)
|
|
{
|
|
SET_ASSERT(lev <= RSET_LEV_MAX);
|
|
RBASIC(set)->flags = ((RBASIC(set)->flags & ~RSET_LEV_MASK) | ((VALUE)lev << RSET_LEV_SHIFT));
|
|
}
|
|
|
|
static inline bool
|
|
set_iterating_p(VALUE set)
|
|
{
|
|
return iter_lev_in_flags(set) > 0;
|
|
}
|
|
|
|
static void
|
|
set_iter_lev_inc(VALUE set)
|
|
{
|
|
unsigned long lev = iter_lev_in_flags(set);
|
|
if (lev == RSET_LEV_MAX) {
|
|
lev = iter_lev_in_ivar(set) + 1;
|
|
if (!POSFIXABLE(lev)) { /* paranoiac check */
|
|
rb_raise(rb_eRuntimeError, "too much nested iterations");
|
|
}
|
|
}
|
|
else {
|
|
lev += 1;
|
|
iter_lev_in_flags_set(set, lev);
|
|
if (lev < RSET_LEV_MAX) return;
|
|
}
|
|
iter_lev_in_ivar_set(set, lev);
|
|
}
|
|
|
|
static void
|
|
set_iter_lev_dec(VALUE set)
|
|
{
|
|
unsigned long lev = iter_lev_in_flags(set);
|
|
if (lev == RSET_LEV_MAX) {
|
|
lev = iter_lev_in_ivar(set);
|
|
if (lev > RSET_LEV_MAX) {
|
|
iter_lev_in_ivar_set(set, lev-1);
|
|
return;
|
|
}
|
|
rb_attr_delete(set, id_set_iter_lev);
|
|
}
|
|
else if (lev == 0) {
|
|
rb_raise(rb_eRuntimeError, "iteration level underflow");
|
|
}
|
|
iter_lev_in_flags_set(set, lev - 1);
|
|
}
|
|
|
|
static VALUE
|
|
set_foreach_ensure(VALUE set)
|
|
{
|
|
set_iter_lev_dec(set);
|
|
return 0;
|
|
}
|
|
|
|
typedef int set_foreach_func(VALUE, VALUE);
|
|
|
|
struct set_foreach_arg {
|
|
VALUE set;
|
|
set_foreach_func *func;
|
|
VALUE arg;
|
|
};
|
|
|
|
static int
|
|
set_iter_status_check(int status)
|
|
{
|
|
if (status == ST_CONTINUE) {
|
|
return ST_CHECK;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
static int
|
|
set_foreach_iter(st_data_t key, st_data_t argp, int error)
|
|
{
|
|
struct set_foreach_arg *arg = (struct set_foreach_arg *)argp;
|
|
|
|
if (error) return ST_STOP;
|
|
|
|
set_table *tbl = RSET_TABLE(arg->set);
|
|
int status = (*arg->func)((VALUE)key, arg->arg);
|
|
|
|
if (RSET_TABLE(arg->set) != tbl) {
|
|
rb_raise(rb_eRuntimeError, "reset occurred during iteration");
|
|
}
|
|
|
|
return set_iter_status_check(status);
|
|
}
|
|
|
|
static VALUE
|
|
set_foreach_call(VALUE arg)
|
|
{
|
|
VALUE set = ((struct set_foreach_arg *)arg)->set;
|
|
int ret = 0;
|
|
ret = set_foreach_check(RSET_TABLE(set), set_foreach_iter,
|
|
(st_data_t)arg, (st_data_t)Qundef);
|
|
if (ret) {
|
|
rb_raise(rb_eRuntimeError, "ret: %d, set modified during iteration", ret);
|
|
}
|
|
return Qnil;
|
|
}
|
|
|
|
static void
|
|
set_iter(VALUE set, set_foreach_func *func, VALUE farg)
|
|
{
|
|
struct set_foreach_arg arg;
|
|
|
|
if (RSET_EMPTY(set))
|
|
return;
|
|
arg.set = set;
|
|
arg.func = func;
|
|
arg.arg = farg;
|
|
if (RB_OBJ_FROZEN(set)) {
|
|
set_foreach_call((VALUE)&arg);
|
|
}
|
|
else {
|
|
set_iter_lev_inc(set);
|
|
rb_ensure(set_foreach_call, (VALUE)&arg, set_foreach_ensure, set);
|
|
}
|
|
}
|
|
|
|
NORETURN(static void no_new_item(void));
|
|
static void
|
|
no_new_item(void)
|
|
{
|
|
rb_raise(rb_eRuntimeError, "can't add a new item into set during iteration");
|
|
}
|
|
|
|
static void
|
|
set_compact_after_delete(VALUE set)
|
|
{
|
|
if (!set_iterating_p(set)) {
|
|
set_compact_table(RSET_TABLE(set));
|
|
}
|
|
}
|
|
|
|
static int
|
|
set_table_insert_wb(set_table *tab, VALUE set, VALUE key)
|
|
{
|
|
if (tab->type != &identhash && rb_obj_class(key) == rb_cString && !RB_OBJ_FROZEN(key)) {
|
|
key = rb_hash_key_str(key);
|
|
}
|
|
int ret = set_insert(tab, (st_data_t)key);
|
|
if (ret == 0) RB_OBJ_WRITTEN(set, Qundef, key);
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
set_insert_wb(VALUE set, VALUE key)
|
|
{
|
|
return set_table_insert_wb(RSET_TABLE(set), set, key);
|
|
}
|
|
|
|
static VALUE
|
|
set_alloc_with_size_and_type(VALUE klass, st_index_t size, const struct st_hash_type *type)
|
|
{
|
|
VALUE set;
|
|
struct set_object *sobj;
|
|
|
|
set = TypedData_Make_Struct(klass, struct set_object, &set_data_type, sobj);
|
|
set_init_table_with_size(&sobj->table, type, size);
|
|
|
|
return set;
|
|
}
|
|
|
|
static VALUE
|
|
set_alloc_with_size(VALUE klass, st_index_t size)
|
|
{
|
|
return set_alloc_with_size_and_type(klass, size, &objhash);
|
|
}
|
|
|
|
static VALUE
|
|
set_s_alloc(VALUE klass)
|
|
{
|
|
return set_alloc_with_size(klass, 0);
|
|
}
|
|
|
|
bool
|
|
rb_set_p(VALUE obj)
|
|
{
|
|
return rb_typeddata_is_instance_of(obj, &set_data_type);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* Set[*objects] -> new_set
|
|
*
|
|
* Returns a new set populated with the given +objects+:
|
|
*
|
|
* Set[1, 'one', :one, 1.0, %w[a b c], {foo: 0, bar: 1}]
|
|
* # => Set[1, "one", :one, 1.0, ["a", "b", "c"], {foo: 0, bar: 1}]
|
|
* Set[Set[0, 1, 2], Set[%w[a b c]]]
|
|
* # => Set[Set[0, 1, 2], Set[["a", "b", "c"]]]
|
|
* Set[] # => Set[]
|
|
*
|
|
* Related: see {Methods for Creating a Set}[rdoc-ref:Set@Methods+for+Creating+a+Set].
|
|
*
|
|
*/
|
|
static VALUE
|
|
set_s_create(int argc, VALUE *argv, VALUE klass)
|
|
{
|
|
VALUE set = set_alloc_with_size(klass, argc);
|
|
set_table *table = RSET_TABLE(set);
|
|
int i;
|
|
|
|
for (i=0; i < argc; i++) {
|
|
set_table_insert_wb(table, set, argv[i]);
|
|
}
|
|
|
|
return set;
|
|
}
|
|
|
|
static VALUE
|
|
set_s_inherited(VALUE klass, VALUE subclass)
|
|
{
|
|
if (klass == rb_cSet) {
|
|
// When subclassing directly from Set, include the compatibility layer
|
|
rb_require("set/subclass_compatible.rb");
|
|
VALUE subclass_compatible = rb_const_get(klass, id_subclass_compatible);
|
|
rb_include_module(subclass, subclass_compatible);
|
|
rb_extend_object(subclass, rb_const_get(subclass_compatible, id_class_methods));
|
|
}
|
|
return Qnil;
|
|
}
|
|
|
|
static void
|
|
check_set(VALUE arg)
|
|
{
|
|
if (!rb_obj_is_kind_of(arg, rb_cSet)) {
|
|
rb_raise(rb_eArgError, "value must be a set");
|
|
}
|
|
}
|
|
|
|
static ID
|
|
enum_method_id(VALUE other)
|
|
{
|
|
if (rb_respond_to(other, id_each_entry)) {
|
|
return id_each_entry;
|
|
}
|
|
else if (rb_respond_to(other, id_each)) {
|
|
return id_each;
|
|
}
|
|
else {
|
|
rb_raise(rb_eArgError, "value must be enumerable");
|
|
}
|
|
}
|
|
|
|
static VALUE
|
|
set_enum_size(VALUE set, VALUE args, VALUE eobj)
|
|
{
|
|
return RSET_SIZE_NUM(set);
|
|
}
|
|
|
|
static VALUE
|
|
set_initialize_without_block(RB_BLOCK_CALL_FUNC_ARGLIST(i, set))
|
|
{
|
|
VALUE element = i;
|
|
set_insert_wb(set, element);
|
|
return element;
|
|
}
|
|
|
|
static VALUE
|
|
set_initialize_with_block(RB_BLOCK_CALL_FUNC_ARGLIST(i, set))
|
|
{
|
|
VALUE element = rb_yield(i);
|
|
set_insert_wb(set, element);
|
|
return element;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* Set.new(object = nil) -> new_set
|
|
* Set.new(object = nil) {|element| ... } -> new_set
|
|
*
|
|
* Returns a new set based on the given +object+,
|
|
* which must be an Enumerable or +nil+.
|
|
*
|
|
* With argument +object+ given as +nil+,
|
|
* returns a new empty set:
|
|
*
|
|
* Set.new # => Set[]
|
|
* Set.new { fail 'Cannot happen' } # => Set[] # Block not called.
|
|
*
|
|
* With no block given and enumerable argument +object+ given,
|
|
* populates the new set with the elements of +object+:
|
|
*
|
|
* Set.new(%w[ a b c ]) # => Set["a", "b", "c"]
|
|
* Set.new({foo: 0, bar: 1}) # => Set[[:foo, 0], [:bar, 1]]
|
|
* Set.new(4..10) # => Set[4, 5, 6, 7, 8, 9, 10]
|
|
* Set.new(Dir.new('lib')).take(5)
|
|
* # => [".", "..", "bundled_gems.rb", "bundler", "bundler.rb"]
|
|
* Set.new(File.new('doc/NEWS/NEWS-4.0.0.md')).take(3)
|
|
* # => ["# NEWS for Ruby 4.0.0\n", "\n", "This document is a list of user-visible feature changes\n"]
|
|
*
|
|
* With a block given and enumerable argument +object+ given,
|
|
* calls the block with each element of +object+;
|
|
* adds the block's return value to the new set:
|
|
*
|
|
* Set.new(4..10) {|i| i * 2 } # => Set[8, 10, 12, 14, 16, 18, 20]
|
|
*
|
|
* Related: see {Methods for Creating a Set}[rdoc-ref:Set@Methods+for+Creating+a+Set].
|
|
*
|
|
*/
|
|
static VALUE
|
|
set_i_initialize(int argc, VALUE *argv, VALUE set)
|
|
{
|
|
if (RBASIC(set)->flags & RSET_INITIALIZED) {
|
|
rb_raise(rb_eRuntimeError, "cannot reinitialize set");
|
|
}
|
|
RBASIC(set)->flags |= RSET_INITIALIZED;
|
|
|
|
VALUE other;
|
|
rb_check_arity(argc, 0, 1);
|
|
|
|
if (argc > 0 && (other = argv[0]) != Qnil) {
|
|
if (RB_TYPE_P(other, T_ARRAY)) {
|
|
long i;
|
|
int block_given = rb_block_given_p();
|
|
set_table *into = RSET_TABLE(set);
|
|
for (i=0; i<RARRAY_LEN(other); i++) {
|
|
VALUE key = RARRAY_AREF(other, i);
|
|
if (block_given) key = rb_yield(key);
|
|
set_table_insert_wb(into, set, key);
|
|
}
|
|
}
|
|
else {
|
|
rb_block_call(other, enum_method_id(other), 0, 0,
|
|
rb_block_given_p() ? set_initialize_with_block : set_initialize_without_block,
|
|
set);
|
|
}
|
|
}
|
|
|
|
return set;
|
|
}
|
|
|
|
/* :nodoc: */
|
|
static VALUE
|
|
set_i_initialize_copy(VALUE set, VALUE other)
|
|
{
|
|
if (set == other) return set;
|
|
|
|
if (set_iterating_p(set)) {
|
|
rb_raise(rb_eRuntimeError, "cannot replace set during iteration");
|
|
}
|
|
|
|
struct set_object *sobj;
|
|
TypedData_Get_Struct(set, struct set_object, &set_data_type, sobj);
|
|
|
|
set_free_embedded_table(&sobj->table);
|
|
set_copy(&sobj->table, RSET_TABLE(other));
|
|
rb_gc_writebarrier_remember(set);
|
|
|
|
return set;
|
|
}
|
|
|
|
static int
|
|
set_inspect_i(st_data_t key, st_data_t arg)
|
|
{
|
|
VALUE *args = (VALUE*)arg;
|
|
VALUE str = args[0];
|
|
if (args[1] == Qtrue) {
|
|
rb_str_buf_cat_ascii(str, ", ");
|
|
}
|
|
else {
|
|
args[1] = Qtrue;
|
|
}
|
|
rb_str_buf_append(str, rb_inspect((VALUE)key));
|
|
|
|
return ST_CONTINUE;
|
|
}
|
|
|
|
static VALUE
|
|
set_inspect(VALUE set, VALUE dummy, int recur)
|
|
{
|
|
VALUE str;
|
|
VALUE klass_name = rb_class_path(CLASS_OF(set));
|
|
|
|
if (recur) {
|
|
str = rb_sprintf("%"PRIsVALUE"[...]", klass_name);
|
|
return rb_str_export_to_enc(str, rb_usascii_encoding());
|
|
}
|
|
|
|
str = rb_sprintf("%"PRIsVALUE"[", klass_name);
|
|
VALUE args[2] = {str, Qfalse};
|
|
set_iter(set, set_inspect_i, (st_data_t)args);
|
|
rb_str_buf_cat2(str, "]");
|
|
|
|
return str;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* inspect -> string
|
|
*
|
|
* Returns a string representation of +self+:
|
|
*
|
|
* Set[*%w[foo bar], {foo: 0, bar: 1}].inspect
|
|
* # => "Set[\"foo\", \"bar\", {foo: 0, bar: 1}]"
|
|
*
|
|
* Related: see {Methods for Converting}[rdoc-ref:Set@Methods+for+Converting].
|
|
*/
|
|
static VALUE
|
|
set_i_inspect(VALUE set)
|
|
{
|
|
return rb_exec_recursive(set_inspect, set, 0);
|
|
}
|
|
|
|
static int
|
|
set_to_a_i(st_data_t key, st_data_t arg)
|
|
{
|
|
rb_ary_push((VALUE)arg, (VALUE)key);
|
|
return ST_CONTINUE;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* to_a -> array
|
|
*
|
|
* Returns an array containing the elements of +self+:
|
|
*
|
|
* Set[1, 2].to_a # => [1, 2]
|
|
* Set[1, 'c', :s].to_a # => [1, "c", :s]
|
|
*
|
|
* Related: {Methods for Converting}[rdoc-ref:Set@Methods+for+Converting].
|
|
*/
|
|
static VALUE
|
|
set_i_to_a(VALUE set)
|
|
{
|
|
st_index_t size = RSET_SIZE(set);
|
|
VALUE ary = rb_ary_new_capa(size);
|
|
|
|
if (size == 0) return ary;
|
|
|
|
if (ST_DATA_COMPATIBLE_P(VALUE)) {
|
|
RARRAY_PTR_USE(ary, ptr, {
|
|
size = set_keys(RSET_TABLE(set), ptr, size);
|
|
});
|
|
rb_gc_writebarrier_remember(ary);
|
|
rb_ary_set_len(ary, size);
|
|
}
|
|
else {
|
|
set_iter(set, set_to_a_i, (st_data_t)ary);
|
|
}
|
|
return ary;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* to_set {|element| ... } -> new_set
|
|
* to_set -> self or new_set
|
|
*
|
|
* With a block given, creates and returns a new set;
|
|
* calls the block with each element of +self+,
|
|
* and adds the block's returns value to the new set:
|
|
*
|
|
* set = Set[*0..9] # => Set[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
|
* set.to_set {|i| i * 2 } # => Set[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
|
|
*
|
|
* With no block given, when +self+ is an instance of +Set+,
|
|
* returns +self+:
|
|
*
|
|
* set = Set[*0..9]
|
|
* set.to_set
|
|
* set.to_set.equal?(set) # => true
|
|
*
|
|
* With no block given, when +self+ is an instance of a subclass of +Set+,
|
|
* returns a set containing the elements of +self+:
|
|
*
|
|
* class MySet < Set; end
|
|
* my_set = MySet[*0..9] # => #<MySet: {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}>
|
|
* set = my_set.to_set # => Set[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
|
*
|
|
* Related: see {Methods for Converting}[rdoc-ref:Set@Methods+for+Converting].
|
|
*/
|
|
static VALUE
|
|
set_i_to_set(VALUE set)
|
|
{
|
|
if (rb_obj_is_instance_of(set, rb_cSet) && !rb_block_given_p()) {
|
|
return set;
|
|
}
|
|
|
|
return rb_funcall_passing_block(rb_cSet, id_new, 1, &set);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* join(separator = $,) -> string
|
|
*
|
|
* Returns the string formed by joining the string-converted elements of +self+
|
|
* with the given +separator+ (defaults to <tt>$,</tt>):
|
|
*
|
|
* $, # => nil
|
|
* Set[*%w[foo bar baz]].join
|
|
* # => "foobarbaz"
|
|
* Set[*%w[foo bar baz]].join(', ')
|
|
* # => "foo, bar, baz"
|
|
*
|
|
* Flattens nested arrays:
|
|
*
|
|
* Set[[:foo, [:bar, [:baz, :bat]]]].join
|
|
* # => "foobarbazbat"
|
|
*
|
|
* Does not flatten nested sets:
|
|
*
|
|
* Set[Set[:foo, Set[:bar, Set[:baz, :bat]]]].join
|
|
* # => "Set[:foo, Set[:bar, Set[:baz, :bat]]]"
|
|
*
|
|
* Related: see {Methods for Converting}[rdoc-ref:Set@Methods+for+Converting].
|
|
*/
|
|
static VALUE
|
|
set_i_join(int argc, VALUE *argv, VALUE set)
|
|
{
|
|
rb_check_arity(argc, 0, 1);
|
|
return rb_ary_join(set_i_to_a(set), argc == 0 ? Qnil : argv[0]);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* add(object) -> self
|
|
*
|
|
* Adds the given +object+ to +self+; returns +self+:
|
|
*
|
|
* set = Set[0, 1, 2]
|
|
* set.add(%w[a b c]) # => Set[0, 1, 2, ["a", "b", "c"]]
|
|
* set.add(0) # => Set[0, 1, 2, ["a", "b", "c"]]
|
|
*
|
|
* Related: see {Methods for Assigning}[rdoc-ref:Set@Methods+for+Assigning].
|
|
*/
|
|
static VALUE
|
|
set_i_add(VALUE set, VALUE item)
|
|
{
|
|
rb_check_frozen(set);
|
|
if (set_iterating_p(set)) {
|
|
if (!set_table_lookup(RSET_TABLE(set), (st_data_t)item)) {
|
|
no_new_item();
|
|
}
|
|
}
|
|
else {
|
|
set_insert_wb(set, item);
|
|
}
|
|
return set;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* add?(object) -> self or nil
|
|
*
|
|
* Like #add, but returns +nil+ if the given +object+ is already in +self+:
|
|
*
|
|
* set = Set[0, 1, 2]
|
|
* set.add?(:foo) # => Set[0, 1, 2, :foo]
|
|
* set.add?(0..9) # => Set[0, 1, 2, :foo, 0..9]
|
|
* set.add?(2) # => nil
|
|
*
|
|
* Related: see {Methods for Assigning}[rdoc-ref:Set@Methods+for+Assigning].
|
|
*/
|
|
static VALUE
|
|
set_i_add_p(VALUE set, VALUE item)
|
|
{
|
|
rb_check_frozen(set);
|
|
if (set_iterating_p(set)) {
|
|
if (!set_table_lookup(RSET_TABLE(set), (st_data_t)item)) {
|
|
no_new_item();
|
|
}
|
|
return Qnil;
|
|
}
|
|
else {
|
|
return set_insert_wb(set, item) ? Qnil : set;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* delete(object) -> self
|
|
*
|
|
* Removes the given +object+ from +self+ if +self+ includes the object;
|
|
* returns +self+:
|
|
*
|
|
* set = Set[0, 'zero', :zero]
|
|
* set.delete(0) # => Set["zero", :zero]
|
|
* set.delete(:nosuch) # => Set["zero", :zero]
|
|
*
|
|
* Related: see {Methods for Deleting}[rdoc-ref:Set@Methods+for+Deleting].
|
|
*/
|
|
static VALUE
|
|
set_i_delete(VALUE set, VALUE item)
|
|
{
|
|
rb_check_frozen(set);
|
|
if (set_table_delete(RSET_TABLE(set), (st_data_t *)&item)) {
|
|
set_compact_after_delete(set);
|
|
}
|
|
return set;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* delete?(object) -> self or nil
|
|
*
|
|
* Like #delete, but returns +nil+ if the object is not in +self+:
|
|
*
|
|
* set = Set[0, 'zero', :zero]
|
|
* set.delete?(0) # => Set["zero", :zero]
|
|
* set.delete?(0) # => nil
|
|
*
|
|
* Related: see {Methods for Deleting}[rdoc-ref:Set@Methods+for+Deleting].
|
|
*/
|
|
static VALUE
|
|
set_i_delete_p(VALUE set, VALUE item)
|
|
{
|
|
rb_check_frozen(set);
|
|
if (set_table_delete(RSET_TABLE(set), (st_data_t *)&item)) {
|
|
set_compact_after_delete(set);
|
|
return set;
|
|
}
|
|
return Qnil;
|
|
}
|
|
|
|
static int
|
|
set_delete_if_i(st_data_t key, st_data_t dummy)
|
|
{
|
|
return RTEST(rb_yield((VALUE)key)) ? ST_DELETE : ST_CONTINUE;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* delete_if {|element| ... } -> self
|
|
* delete_if -> enumerator
|
|
*
|
|
* With a block given, calls the block with each element in +self+;
|
|
* removes the element if the block returns a truthy value:
|
|
*
|
|
* set = Set[*0..9]
|
|
* # => Set[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
|
* set.delete_if {|element| element.even? }
|
|
* # => Set[1, 3, 5, 7, 9]
|
|
*
|
|
* With no block given, returns an Enumerator.
|
|
*
|
|
* Related: {Methods for Deleting}[rdoc-ref:Set@Methods+for+Deleting].
|
|
*/
|
|
static VALUE
|
|
set_i_delete_if(VALUE set)
|
|
{
|
|
RETURN_SIZED_ENUMERATOR(set, 0, 0, set_enum_size);
|
|
rb_check_frozen(set);
|
|
set_iter(set, set_delete_if_i, 0);
|
|
set_compact_after_delete(set);
|
|
return set;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* reject! {|element| ... } -> self or nil
|
|
* reject! -> enumerator
|
|
*
|
|
* With a block given, like #delete_if, but returns +nil+ if no changes were made:
|
|
*
|
|
* set = Set[*0..9] # => Set[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
|
* set.reject! {|element| element.even? } # => Set[1, 3, 5, 7, 9]
|
|
* set.reject! {|element| element.even? } # => nil
|
|
* set.reject! {|element| element.odd? } # => Set[]
|
|
*
|
|
* With no block given, returns an Enumerator.
|
|
*
|
|
* Related: see {Methods for Deleting}[rdoc-ref:Set@Methods+for+Deleting].
|
|
*/
|
|
static VALUE
|
|
set_i_reject(VALUE set)
|
|
{
|
|
RETURN_SIZED_ENUMERATOR(set, 0, 0, set_enum_size);
|
|
rb_check_frozen(set);
|
|
|
|
set_table *table = RSET_TABLE(set);
|
|
size_t n = set_table_size(table);
|
|
set_iter(set, set_delete_if_i, 0);
|
|
|
|
if (n == set_table_size(table)) return Qnil;
|
|
|
|
set_compact_after_delete(set);
|
|
return set;
|
|
}
|
|
|
|
static int
|
|
set_classify_i(st_data_t key, st_data_t tmp)
|
|
{
|
|
VALUE* args = (VALUE*)tmp;
|
|
VALUE hash = args[0];
|
|
VALUE hash_key = rb_yield(key);
|
|
VALUE set = rb_hash_lookup2(hash, hash_key, Qundef);
|
|
if (set == Qundef) {
|
|
set = set_s_alloc(args[1]);
|
|
if (RTEST(args[2])) {
|
|
set_i_compare_by_identity(set);
|
|
}
|
|
rb_hash_aset(hash, hash_key, set);
|
|
}
|
|
set_i_add(set, key);
|
|
|
|
return ST_CONTINUE;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* classify {|element| ... } -> hash
|
|
* classify -> enumerator
|
|
*
|
|
* With a block given, calls the block with each element of +self+;
|
|
* returns a hash whose keys are the block's return values.
|
|
* The value for each key is a set containing the elements
|
|
* for which the block returned that key.
|
|
*
|
|
* This example classifies elements by their classes:
|
|
*
|
|
* set = Set[*(5..7), *%w[foo bar]] # => Set[5, 6, 7, "foo", "bar"]
|
|
* set.classify {|element| element.class }
|
|
* # => {Integer => Set[5, 6, 7], String => Set["foo", "bar"]}
|
|
*
|
|
* With no block given, returns an Enumerator.
|
|
*
|
|
* Related: see {Methods for Converting}[rdoc-ref:Set@Methods+for+Converting].
|
|
*/
|
|
static VALUE
|
|
set_i_classify(VALUE set)
|
|
{
|
|
RETURN_SIZED_ENUMERATOR(set, 0, 0, set_enum_size);
|
|
VALUE args[3];
|
|
args[0] = rb_hash_new();
|
|
args[1] = rb_obj_class(set);
|
|
args[2] = RBOOL(RSET_COMPARE_BY_IDENTITY(set));
|
|
set_iter(set, set_classify_i, (st_data_t)args);
|
|
return args[0];
|
|
}
|
|
|
|
// Union-find with path compression
|
|
static long
|
|
set_divide_union_find_root(long *uf_parents, long index, long *tmp_array)
|
|
{
|
|
long root = uf_parents[index];
|
|
long update_size = 0;
|
|
while (root != index) {
|
|
tmp_array[update_size++] = index;
|
|
index = root;
|
|
root = uf_parents[index];
|
|
}
|
|
for (long j = 0; j < update_size; j++) {
|
|
long idx = tmp_array[j];
|
|
uf_parents[idx] = root;
|
|
}
|
|
return root;
|
|
}
|
|
|
|
static void
|
|
set_divide_union_find_merge(long *uf_parents, long i, long j, long *tmp_array)
|
|
{
|
|
long root_i = set_divide_union_find_root(uf_parents, i, tmp_array);
|
|
long root_j = set_divide_union_find_root(uf_parents, j, tmp_array);
|
|
if (root_i != root_j) uf_parents[root_j] = root_i;
|
|
}
|
|
|
|
static VALUE
|
|
set_divide_arity2(VALUE set)
|
|
{
|
|
VALUE tmp, uf;
|
|
long size, *uf_parents, *tmp_array;
|
|
VALUE set_class = rb_obj_class(set);
|
|
VALUE items = set_i_to_a(set);
|
|
rb_ary_freeze(items);
|
|
size = RARRAY_LEN(items);
|
|
tmp_array = ALLOCV_N(long, tmp, size);
|
|
uf_parents = ALLOCV_N(long, uf, size);
|
|
for (long i = 0; i < size; i++) {
|
|
uf_parents[i] = i;
|
|
}
|
|
for (long i = 0; i < size - 1; i++) {
|
|
VALUE item1 = RARRAY_AREF(items, i);
|
|
for (long j = i + 1; j < size; j++) {
|
|
VALUE item2 = RARRAY_AREF(items, j);
|
|
if (RTEST(rb_yield_values(2, item1, item2)) &&
|
|
RTEST(rb_yield_values(2, item2, item1))) {
|
|
set_divide_union_find_merge(uf_parents, i, j, tmp_array);
|
|
}
|
|
}
|
|
}
|
|
VALUE final_set = set_s_create(0, 0, rb_cSet);
|
|
VALUE hash = rb_hash_new();
|
|
for (long i = 0; i < size; i++) {
|
|
VALUE v = RARRAY_AREF(items, i);
|
|
long root = set_divide_union_find_root(uf_parents, i, tmp_array);
|
|
VALUE subset = rb_hash_aref(hash, LONG2FIX(root));
|
|
if (subset == Qnil) {
|
|
subset = set_s_alloc(set_class);
|
|
if (RSET_COMPARE_BY_IDENTITY(set)) {
|
|
set_i_compare_by_identity(subset);
|
|
}
|
|
rb_hash_aset(hash, LONG2FIX(root), subset);
|
|
set_i_add(final_set, subset);
|
|
}
|
|
set_i_add(subset, v);
|
|
}
|
|
ALLOCV_END(tmp);
|
|
ALLOCV_END(uf);
|
|
return final_set;
|
|
}
|
|
|
|
static void set_merge_enum_into(VALUE set, VALUE arg);
|
|
|
|
/*
|
|
* call-seq:
|
|
* divide {|ele| ... } -> new_set
|
|
* divide {|ele0, ele1| ... } -> new_set
|
|
* divide -> enumerator
|
|
*
|
|
* With a block given, returns a set of sets.
|
|
*
|
|
* For a block that accepts one argument,
|
|
* calls the block with each element;
|
|
* creates a set for each distinct block return value:
|
|
*
|
|
* set = Set[*0..9]
|
|
* # => Set[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
|
* # Divide into mod 3 sets.
|
|
* set.divide {|ele| ele % 3 }
|
|
* # => Set[Set[0, 3, 6, 9], Set[1, 4, 7], Set[2, 5, 8]]
|
|
* # Divide into mod 5 sets.
|
|
* set.divide {|ele| ele % 5 }
|
|
* # => Set[Set[0, 5], Set[1, 6], Set[2, 7], Set[3, 8], Set[4, 9]]
|
|
*
|
|
* Set[0].divide {|ele| anything } # => Set[Set[0]]
|
|
* Set[].divide {|ele| not called } # => Set[]
|
|
*
|
|
* For a block that accepts two arguments,
|
|
* divides +self+ into connected components based on the binary
|
|
* relation defined by the block, calling the block with each 2-element
|
|
* permutation of the elements of +self+:
|
|
*
|
|
* set = Set[*0..9]
|
|
* # => Set[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
|
* # Divide into mod 2 sets.
|
|
* set.divide {|i, j| (i - j) % 2 == 0 }
|
|
* # => Set[Set[0, 2, 4, 6, 8], Set[1, 3, 5, 7, 9]]
|
|
* # Divide into mod 3 sets.
|
|
* set.divide {|i, j| (i - j) % 3 == 0 }
|
|
* # => Set[Set[0, 3, 6, 9], Set[1, 4, 7], Set[2, 5, 8]]
|
|
*
|
|
* Set[0].divide {|i, j| not called } # => Set[Set[0]]
|
|
* Set[].divide {|i, j| not called } # => Set[]
|
|
*
|
|
* With no block given, returns an Enumerator.
|
|
*
|
|
* Related: see {Methods for Converting}[rdoc-ref:Set@Methods+for+Converting].
|
|
*/
|
|
static VALUE
|
|
set_i_divide(VALUE set)
|
|
{
|
|
RETURN_SIZED_ENUMERATOR(set, 0, 0, set_enum_size);
|
|
|
|
if (rb_block_arity() == 2) {
|
|
return set_divide_arity2(set);
|
|
}
|
|
|
|
VALUE values = rb_hash_values(set_i_classify(set));
|
|
set = set_alloc_with_size(rb_cSet, RARRAY_LEN(values));
|
|
set_merge_enum_into(set, values);
|
|
return set;
|
|
}
|
|
|
|
static int
|
|
set_clear_i(st_data_t key, st_data_t dummy)
|
|
{
|
|
return ST_DELETE;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* clear -> self
|
|
*
|
|
* Returns +self+ with all elements removed:
|
|
*
|
|
* Set[1, :one, 'one', 1.0].clear # => Set[]
|
|
*
|
|
* Related: see {Methods for Deleting}[rdoc-ref:Set@Methods+for+Deleting].
|
|
*/
|
|
static VALUE
|
|
set_i_clear(VALUE set)
|
|
{
|
|
rb_check_frozen(set);
|
|
if (RSET_SIZE(set) == 0) return set;
|
|
if (set_iterating_p(set)) {
|
|
set_iter(set, set_clear_i, 0);
|
|
}
|
|
else {
|
|
set_table_clear(RSET_TABLE(set));
|
|
set_compact_after_delete(set);
|
|
}
|
|
return set;
|
|
}
|
|
|
|
struct set_intersection_data {
|
|
VALUE set;
|
|
set_table *into;
|
|
set_table *other;
|
|
};
|
|
|
|
static int
|
|
set_intersection_i(st_data_t key, st_data_t tmp)
|
|
{
|
|
struct set_intersection_data *data = (struct set_intersection_data *)tmp;
|
|
if (set_table_lookup(data->other, key)) {
|
|
set_table_insert_wb(data->into, data->set, key);
|
|
}
|
|
|
|
return ST_CONTINUE;
|
|
}
|
|
|
|
static VALUE
|
|
set_intersection_block(RB_BLOCK_CALL_FUNC_ARGLIST(i, data))
|
|
{
|
|
set_intersection_i((st_data_t)i, (st_data_t)data);
|
|
return i;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* self & enumerable -> new_set
|
|
*
|
|
* Returns a new set containing the {intersection}[https://en.wikipedia.org/wiki/Intersection_(set_theory)]
|
|
* of +self+ and +enumerable+;
|
|
* that is, containing all elements common to both, with no duplicates.
|
|
* Argument +enumerable+ must be an Enumerable object:
|
|
*
|
|
* set = Set[*(0..6), *%w[ a b c]] # => Set[0, 1, 2, 3, 4, 5, 6, "a", "b", "c"]
|
|
* set & ['c', 6, 8, 4] # => Set["c", 6, 4]
|
|
* set & [:foo, :bar] # => Set[] # No elements in common.
|
|
*
|
|
* Related: see {Methods for Set Operations}[rdoc-ref:Set@Methods+for+Set+Operations].
|
|
*/
|
|
static VALUE
|
|
set_i_intersection(VALUE set, VALUE other)
|
|
{
|
|
VALUE new_set = set_s_alloc(rb_obj_class(set));
|
|
if (RSET_COMPARE_BY_IDENTITY(set)) {
|
|
set_i_compare_by_identity(new_set);
|
|
}
|
|
set_table *stable = RSET_TABLE(set);
|
|
set_table *ntable = RSET_TABLE(new_set);
|
|
|
|
if (rb_obj_is_kind_of(other, rb_cSet)) {
|
|
set_table *otable = RSET_TABLE(other);
|
|
if (set_table_size(stable) >= set_table_size(otable)) {
|
|
/* Swap so we iterate over the smaller set */
|
|
otable = stable;
|
|
set = other;
|
|
}
|
|
|
|
struct set_intersection_data data = {
|
|
.set = new_set,
|
|
.into = ntable,
|
|
.other = otable
|
|
};
|
|
set_iter(set, set_intersection_i, (st_data_t)&data);
|
|
}
|
|
else {
|
|
struct set_intersection_data data = {
|
|
.set = new_set,
|
|
.into = ntable,
|
|
.other = stable
|
|
};
|
|
rb_block_call(other, enum_method_id(other), 0, 0, set_intersection_block, (VALUE)&data);
|
|
}
|
|
|
|
return new_set;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* include?(object) -> true or false
|
|
*
|
|
* Returns whether the given +object+ is an element of +self+:
|
|
*
|
|
* set = Set[0, :zero, '0']
|
|
* set.include?('0') # => true
|
|
* set.include?('zero') # => false
|
|
*
|
|
* Tests equality using `hash` and `eql?`.
|
|
*
|
|
* Aliased as #===, which means that sets may be used in +case+ expressions:
|
|
*
|
|
* case :apple
|
|
* when Set[:potato, :carrot]
|
|
* 'vegetable'
|
|
* when Set[:apple, :banana]
|
|
* 'fruit'
|
|
* else
|
|
* 'unknown'
|
|
* end
|
|
* # => "fruit"
|
|
*
|
|
* Related: see {Methods for Querying}[rdoc-ref:Set@Methods+for+Querying].
|
|
*/
|
|
static VALUE
|
|
set_i_include(VALUE set, VALUE item)
|
|
{
|
|
return RBOOL(RSET_IS_MEMBER(set, item));
|
|
}
|
|
|
|
struct set_merge_args {
|
|
VALUE set;
|
|
set_table *into;
|
|
};
|
|
|
|
static int
|
|
set_merge_i(st_data_t key, st_data_t data)
|
|
{
|
|
struct set_merge_args *args = (struct set_merge_args *)data;
|
|
set_table_insert_wb(args->into, args->set, key);
|
|
return ST_CONTINUE;
|
|
}
|
|
|
|
static VALUE
|
|
set_merge_block(RB_BLOCK_CALL_FUNC_ARGLIST(key, set))
|
|
{
|
|
VALUE element = key;
|
|
set_insert_wb(set, element);
|
|
return element;
|
|
}
|
|
|
|
static void
|
|
set_merge_enum_into(VALUE set, VALUE arg)
|
|
{
|
|
if (rb_obj_is_kind_of(arg, rb_cSet)) {
|
|
struct set_merge_args args = {
|
|
.set = set,
|
|
.into = RSET_TABLE(set)
|
|
};
|
|
set_iter(arg, set_merge_i, (st_data_t)&args);
|
|
}
|
|
else if (RB_TYPE_P(arg, T_ARRAY)) {
|
|
long i;
|
|
set_table *into = RSET_TABLE(set);
|
|
for (i=0; i<RARRAY_LEN(arg); i++) {
|
|
set_table_insert_wb(into, set, RARRAY_AREF(arg, i));
|
|
}
|
|
}
|
|
else {
|
|
rb_block_call(arg, enum_method_id(arg), 0, 0, set_merge_block, (VALUE)set);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* merge(*enumerables, **nil) -> self
|
|
*
|
|
* Adds each element of each of the given +enumerables+ to +self+;
|
|
* returns +self+:
|
|
*
|
|
* set = Set[*0..2] # => Set[0, 1, 2]
|
|
* set.merge('a'..'c', %w[foo bar]) # => Set[0, 1, 2, "a", "b", "c", "foo", "bar"]
|
|
* set.merge('a'..'c', %w[foo bar]) # => Set[0, 1, 2, "a", "b", "c", "foo", "bar"]
|
|
*
|
|
* Related: see {Methods for Assigning}[rdoc-ref:Set@Methods+for+Assigning].
|
|
*
|
|
*/
|
|
static VALUE
|
|
set_i_merge(int argc, VALUE *argv, VALUE set)
|
|
{
|
|
if (rb_keyword_given_p()) {
|
|
rb_raise(rb_eArgError, "no keywords accepted");
|
|
}
|
|
|
|
if (set_iterating_p(set)) {
|
|
rb_raise(rb_eRuntimeError, "cannot add to set during iteration");
|
|
}
|
|
|
|
rb_check_frozen(set);
|
|
|
|
int i;
|
|
|
|
for (i=0; i < argc; i++) {
|
|
set_merge_enum_into(set, argv[i]);
|
|
}
|
|
|
|
return set;
|
|
}
|
|
|
|
static VALUE
|
|
set_reset_table_with_type(VALUE set, const struct st_hash_type *type)
|
|
{
|
|
rb_check_frozen(set);
|
|
|
|
struct set_object *sobj;
|
|
TypedData_Get_Struct(set, struct set_object, &set_data_type, sobj);
|
|
set_table *old = &sobj->table;
|
|
|
|
size_t size = set_table_size(old);
|
|
if (size > 0) {
|
|
set_table *new = set_init_table_with_size(NULL, type, size);
|
|
struct set_merge_args args = {
|
|
.set = set,
|
|
.into = new
|
|
};
|
|
set_iter(set, set_merge_i, (st_data_t)&args);
|
|
set_free_embedded_table(&sobj->table);
|
|
memcpy(&sobj->table, new, sizeof(*new));
|
|
SIZED_FREE(new);
|
|
}
|
|
else {
|
|
sobj->table.type = type;
|
|
}
|
|
|
|
return set;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* compare_by_identity -> self
|
|
*
|
|
* Sets +self+ to compare by object identity
|
|
* (rather than by object content, which is the initial setting);
|
|
* returns +self+:
|
|
*
|
|
* set = Set.new
|
|
* set.compare_by_identity
|
|
* str = +"foo"
|
|
* set.add(str)
|
|
* # => Set["foo"]
|
|
* set.include?(str)
|
|
* # => true
|
|
* set.add(str)
|
|
* # => Set["foo"])
|
|
* set.include?(+"foo")
|
|
* # => false
|
|
* set.add(+"foo")
|
|
* # => Set["foo", "foo"])
|
|
*
|
|
* Once set, the compare-by-identity property may not be unset.
|
|
*
|
|
* Related: #compare_by_identity?.
|
|
*/
|
|
static VALUE
|
|
set_i_compare_by_identity(VALUE set)
|
|
{
|
|
if (RSET_COMPARE_BY_IDENTITY(set)) return set;
|
|
|
|
if (set_iterating_p(set)) {
|
|
rb_raise(rb_eRuntimeError, "compare_by_identity during iteration");
|
|
}
|
|
|
|
return set_reset_table_with_type(set, &identhash);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* compare_by_identity? -> true or false
|
|
*
|
|
* Returns whether +self+ compares elements by object identity
|
|
* (rather than by content):
|
|
*
|
|
* set = Set[]
|
|
* set.compare_by_identity? # => false
|
|
* set.compare_by_identity
|
|
* set.compare_by_identity? # => true
|
|
*
|
|
* Related: #compare_by_identity;
|
|
* see also {Methods for Querying}[rdoc-ref:Set@Methods+for+Querying].
|
|
*/
|
|
static VALUE
|
|
set_i_compare_by_identity_p(VALUE set)
|
|
{
|
|
return RBOOL(RSET_COMPARE_BY_IDENTITY(set));
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* size -> integer
|
|
*
|
|
* Returns the number of elements in +self+:
|
|
*
|
|
* Set[*0..9].size # => 10
|
|
*
|
|
* Related: see {Methods for Querying}[rdoc-ref:Set@Methods+for+Querying].
|
|
*/
|
|
static VALUE
|
|
set_i_size(VALUE set)
|
|
{
|
|
return RSET_SIZE_NUM(set);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* empty? -> true or false
|
|
*
|
|
* Returns whether +self+ contains no elements:
|
|
*
|
|
* Set[].empty? # => true
|
|
* Set[0].empty? # => false
|
|
*
|
|
* Related: see {Methods for Querying}[rdoc-ref:Set@Methods+for+Querying].
|
|
*/
|
|
static VALUE
|
|
set_i_empty(VALUE set)
|
|
{
|
|
return RBOOL(RSET_EMPTY(set));
|
|
}
|
|
|
|
static int
|
|
set_xor_i(st_data_t key, st_data_t data)
|
|
{
|
|
VALUE element = (VALUE)key;
|
|
VALUE set = (VALUE)data;
|
|
set_table *table = RSET_TABLE(set);
|
|
if (set_table_insert_wb(table, set, element)) {
|
|
set_table_delete(table, &element);
|
|
}
|
|
return ST_CONTINUE;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* self ^ enumerable -> new_set
|
|
*
|
|
* Returns a new set containing
|
|
* the {exclusive OR}[https://en.wikipedia.org/wiki/Exclusive_or]
|
|
* of +self+ and the given +enumerable+;
|
|
* that is, containing each element that is in either +self+ or +enumerable+,
|
|
* but not in both:
|
|
*
|
|
* set = Set[0, 1, 2]
|
|
* set ^ Set[1, 2, 3] # => Set[0, 3]
|
|
* set ^ Set[2, 1] # => Set[0]
|
|
* set ^ Set[2, *('a'..'c')] # => Set[0, 1, "a", "b", "c"]
|
|
* set ^ Set[2, 1, 0] # => Set[]
|
|
*
|
|
* For \Set +set+ and \Enumerable +enumerable+, these expressions are equivalent:
|
|
*
|
|
* set ^ enumerable
|
|
* ((set | enumerable) - (set & enumerable))
|
|
*
|
|
* Related: see {Methods for Set Operations}[rdoc-ref:Set@Methods+for+Set+Operations].
|
|
*/
|
|
static VALUE
|
|
set_i_xor(VALUE set, VALUE other)
|
|
{
|
|
VALUE new_set = rb_obj_dup(set);
|
|
|
|
if (rb_obj_is_kind_of(other, rb_cSet)) {
|
|
set_iter(other, set_xor_i, (st_data_t)new_set);
|
|
}
|
|
else {
|
|
VALUE tmp = set_s_alloc(rb_obj_class(new_set));
|
|
if (RSET_COMPARE_BY_IDENTITY(new_set)) {
|
|
set_i_compare_by_identity(tmp);
|
|
}
|
|
set_merge_enum_into(tmp, other);
|
|
set_iter(tmp, set_xor_i, (st_data_t)new_set);
|
|
}
|
|
set_compact_after_delete(set);
|
|
|
|
return new_set;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* self | enumerable -> new_set
|
|
*
|
|
* Returns a new set containing
|
|
* the {union}[https://en.wikipedia.org/wiki/Union_(set_theory)]
|
|
* of +self+ and the given +enumerable+;
|
|
* that is, containing the elements of both +self+ and +enumerable+.
|
|
*
|
|
* set = Set[0, 1, 2]
|
|
* set | Set[2, 1, 'a'] # => Set[0, 1, 2, "a"]
|
|
* set | set # => Set[0, 1, 2]
|
|
*
|
|
* Related: see {Methods for Set Operations}[rdoc-ref:Set@Methods+for+Set+Operations].
|
|
*/
|
|
static VALUE
|
|
set_i_union(VALUE set, VALUE other)
|
|
{
|
|
set = rb_obj_dup(set);
|
|
set_merge_enum_into(set, other);
|
|
return set;
|
|
}
|
|
|
|
static int
|
|
set_remove_i(st_data_t key, st_data_t from)
|
|
{
|
|
set_table_delete((struct set_table *)from, (st_data_t *)&key);
|
|
return ST_CONTINUE;
|
|
}
|
|
|
|
static VALUE
|
|
set_remove_block(RB_BLOCK_CALL_FUNC_ARGLIST(key, set))
|
|
{
|
|
rb_check_frozen(set);
|
|
set_table_delete(RSET_TABLE(set), (st_data_t *)&key);
|
|
return key;
|
|
}
|
|
|
|
static void
|
|
set_remove_enum_from(VALUE set, VALUE arg)
|
|
{
|
|
if (rb_obj_is_kind_of(arg, rb_cSet)) {
|
|
set_iter(arg, set_remove_i, (st_data_t)RSET_TABLE(set));
|
|
}
|
|
else {
|
|
rb_block_call(arg, enum_method_id(arg), 0, 0, set_remove_block, (VALUE)set);
|
|
}
|
|
set_compact_after_delete(set);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* subtract(enumerable) -> self
|
|
*
|
|
* Deletes from +self+ every element found in the given +enumerable+;
|
|
* returns +self+:
|
|
*
|
|
* set = Set[*0..9] # => Set[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
|
* set.subtract(5..14) # => Set[0, 1, 2, 3, 4]
|
|
* set.subtract(Set[6, 2]) # => Set[0, 1, 3, 4]
|
|
*
|
|
* Related: see {Methods for Deleting}[rdoc-ref:Set@Methods+for+Deleting].
|
|
*/
|
|
static VALUE
|
|
set_i_subtract(VALUE set, VALUE other)
|
|
{
|
|
rb_check_frozen(set);
|
|
set_remove_enum_from(set, other);
|
|
return set;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* self - enumerable -> new_set
|
|
*
|
|
* Returns a new set containing the
|
|
* {difference}[https://en.wikipedia.org/wiki/Complement_(set_theory)#Relative_complement]
|
|
* of +self+ and argument +enumerable+;
|
|
* that is, containing all elements in +self+ that are not in +enumerable+.
|
|
*
|
|
*
|
|
* set = Set[*(0..6), *%w[ a b c]] # => Set[0, 1, 2, 3, 4, 5, 6, "a", "b", "c"]
|
|
* set - ['b', 6, 4, 1] # => Set[0, 2, 3, 5, "a", "c"]
|
|
* set - ['d', 7, 9] # => Set[0, 1, 2, 3, 4, 5, 6, "a", "b", "c"]
|
|
*
|
|
* Related: see {Methods for Set Operations}[rdoc-ref:Set@Methods+for+Set+Operations].
|
|
*/
|
|
static VALUE
|
|
set_i_difference(VALUE set, VALUE other)
|
|
{
|
|
return set_i_subtract(rb_obj_dup(set), other);
|
|
}
|
|
|
|
static int
|
|
set_each_i(st_data_t key, st_data_t dummy)
|
|
{
|
|
rb_yield(key);
|
|
return ST_CONTINUE;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* each {|element| ... } -> self
|
|
* each -> enumerator
|
|
*
|
|
* With a block given, calls the block once for each element in the set,
|
|
* passing the element as a parameter;
|
|
* returns +self+:
|
|
*
|
|
* sum = 0
|
|
* Set[1, 2, 3].each {|i| sum += i }
|
|
* sum # => 6
|
|
*
|
|
* With no block given, returns an Enumerator.
|
|
*/
|
|
static VALUE
|
|
set_i_each(VALUE set)
|
|
{
|
|
RETURN_SIZED_ENUMERATOR(set, 0, 0, set_enum_size);
|
|
set_iter(set, set_each_i, 0);
|
|
return set;
|
|
}
|
|
|
|
static int
|
|
set_collect_i(st_data_t key, st_data_t data)
|
|
{
|
|
set_insert_wb((VALUE)data, rb_yield((VALUE)key));
|
|
return ST_CONTINUE;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* collect! {|element| ... } -> self
|
|
* collect! -> enumerator
|
|
*
|
|
* With a block given, calls the block with each element in +self+;
|
|
* replaces the element with the block's return value:
|
|
*
|
|
* Set[1, :one, 'one', 1.0].collect! {|element| element.class }
|
|
* # => Set[Integer, Symbol, String, Float]
|
|
*
|
|
* With no block given, returns an Enumerator.
|
|
*
|
|
* Related: see {Methods for Converting}[rdoc-ref:Set@Methods+for+Converting].
|
|
*/
|
|
static VALUE
|
|
set_i_collect(VALUE set)
|
|
{
|
|
RETURN_SIZED_ENUMERATOR(set, 0, 0, set_enum_size);
|
|
rb_check_frozen(set);
|
|
|
|
VALUE new_set = set_s_alloc(rb_obj_class(set));
|
|
if (RSET_COMPARE_BY_IDENTITY(set)) {
|
|
set_i_compare_by_identity(new_set);
|
|
}
|
|
set_iter(set, set_collect_i, (st_data_t)new_set);
|
|
set_i_initialize_copy(set, new_set);
|
|
|
|
return set;
|
|
}
|
|
|
|
static int
|
|
set_keep_if_i(st_data_t key, st_data_t into)
|
|
{
|
|
if (!RTEST(rb_yield((VALUE)key))) {
|
|
set_table_delete((set_table *)into, &key);
|
|
}
|
|
return ST_CONTINUE;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* keep_if {|element| ... } -> self
|
|
* keep_if -> enumerator
|
|
*
|
|
* With a block given,
|
|
* calls the block with each element in +self+,
|
|
* deleting the element if the block returns +false+ or +nil+;
|
|
* returns +self+:
|
|
*
|
|
* set = Set[*0..9] # => Set[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
|
* set.keep_if {|i| i.even? } # => Set[0, 2, 4, 6, 8]
|
|
* set.keep_if {|i| i.odd? } # => Set[]
|
|
*
|
|
* With no block given, returns an Enumerator.
|
|
*
|
|
* Related: see {Methods for Deleting}[rdoc-ref:Set@Methods+for+Deleting].
|
|
*/
|
|
static VALUE
|
|
set_i_keep_if(VALUE set)
|
|
{
|
|
RETURN_SIZED_ENUMERATOR(set, 0, 0, set_enum_size);
|
|
rb_check_frozen(set);
|
|
|
|
set_iter(set, set_keep_if_i, (st_data_t)RSET_TABLE(set));
|
|
set_compact_after_delete(set);
|
|
|
|
return set;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* select! {|element| ... } -> self or nil
|
|
* select! -> enumerator
|
|
*
|
|
* With a block given, like #keep_if, but returns +nil+ if no changes were made:
|
|
*
|
|
* set = Set[*0..9] # => Set[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
|
* set.select! {|i| i.even? } # => Set[0, 2, 4, 6, 8]
|
|
* set.select! {|i| i.even? } # => nil
|
|
* set.select! {|i| i.odd? } # => Set[]
|
|
*
|
|
* With no block given, returns an Enumerator.
|
|
*
|
|
* Related: see {Methods for Deleting}[rdoc-ref:Set@Methods+for+Deleting].
|
|
*/
|
|
static VALUE
|
|
set_i_select(VALUE set)
|
|
{
|
|
RETURN_SIZED_ENUMERATOR(set, 0, 0, set_enum_size);
|
|
rb_check_frozen(set);
|
|
|
|
set_table *table = RSET_TABLE(set);
|
|
size_t n = set_table_size(table);
|
|
set_iter(set, set_keep_if_i, (st_data_t)table);
|
|
set_compact_after_delete(set);
|
|
|
|
return (n == set_table_size(table)) ? Qnil : set;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* replace(enumerable) -> self
|
|
*
|
|
* Replaces the contents +self+ with the contents of the given +enumerable+;
|
|
* returns +self+:
|
|
*
|
|
* set = Set[1, 'c', :s] # => Set[1, "c", :s]
|
|
* set.replace([1, 2]) # => Set[1, 2]
|
|
*
|
|
* Related: see {Methods for Assigning}[rdoc-ref:Set@Methods+for+Assigning].
|
|
*/
|
|
static VALUE
|
|
set_i_replace(VALUE set, VALUE other)
|
|
{
|
|
rb_check_frozen(set);
|
|
|
|
if (rb_obj_is_kind_of(other, rb_cSet)) {
|
|
set_i_initialize_copy(set, other);
|
|
}
|
|
else {
|
|
if (set_iterating_p(set)) {
|
|
rb_raise(rb_eRuntimeError, "cannot replace set during iteration");
|
|
}
|
|
|
|
// make sure enum is enumerable before calling clear
|
|
enum_method_id(other);
|
|
|
|
set_table_clear(RSET_TABLE(set));
|
|
set_merge_enum_into(set, other);
|
|
}
|
|
set_compact_after_delete(set);
|
|
|
|
return set;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* reset -> self
|
|
*
|
|
* Resets the internal state of +self+; returns +self+.
|
|
*
|
|
* A set relies on the #hash results of each element being consistent.
|
|
* Modifying an element in a way that changes the results of #hash
|
|
* may allow duplicate elements in the set:
|
|
*
|
|
* array = [1]
|
|
* set = Set[array] # => Set[[1]]
|
|
* array << 2
|
|
* set.add(array) # => Set[[1, 2], [1, 2]]
|
|
*
|
|
* Calling #reset will recalculate all of the hash values and remove
|
|
* duplicate elements:
|
|
*
|
|
* set.reset # => Set[[1, 2]]
|
|
*
|
|
*/
|
|
static VALUE
|
|
set_i_reset(VALUE set)
|
|
{
|
|
if (set_iterating_p(set)) {
|
|
rb_raise(rb_eRuntimeError, "reset during iteration");
|
|
}
|
|
|
|
return set_reset_table_with_type(set, RSET_TABLE(set)->type);
|
|
}
|
|
|
|
static void set_flatten_merge(VALUE set, VALUE from, VALUE seen);
|
|
|
|
static int
|
|
set_flatten_merge_i(st_data_t item, st_data_t arg)
|
|
{
|
|
VALUE *args = (VALUE *)arg;
|
|
VALUE set = args[0];
|
|
if (rb_obj_is_kind_of(item, rb_cSet)) {
|
|
VALUE e_id = rb_obj_id(item);
|
|
VALUE hash = args[2];
|
|
switch(rb_hash_aref(hash, e_id)) {
|
|
case Qfalse:
|
|
return ST_CONTINUE;
|
|
case Qtrue:
|
|
rb_raise(rb_eArgError, "tried to flatten recursive Set");
|
|
default:
|
|
break;
|
|
}
|
|
|
|
rb_hash_aset(hash, e_id, Qtrue);
|
|
set_flatten_merge(set, item, hash);
|
|
rb_hash_aset(hash, e_id, Qfalse);
|
|
}
|
|
else {
|
|
set_i_add(set, item);
|
|
}
|
|
return ST_CONTINUE;
|
|
}
|
|
|
|
static void
|
|
set_flatten_merge(VALUE set, VALUE from, VALUE hash)
|
|
{
|
|
VALUE args[3] = {set, from, hash};
|
|
set_iter(from, set_flatten_merge_i, (st_data_t)args);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* flatten -> new_set
|
|
*
|
|
* Returns a new set that is a copy of +self+,
|
|
* but with +self+ and its nested sets flattened;
|
|
* that is, their elements become elements of +self+:
|
|
*
|
|
* Set[Set[0, 1], Set[2, 3]].flatten
|
|
* # => Set[0, 1, 2, 3]
|
|
* Set[Set[0, 1], Set[Set[2, 3], Set[3, 4]]].flatten
|
|
* # => Set[0, 1, 2, 3, 4]
|
|
*
|
|
* Does not flatten nested arrays or hashes:
|
|
*
|
|
* Set[%w[foo bar]].flatten # => Set[["foo", "bar"]]
|
|
* Set[{foo: 0, bar: 1}].flatten # => Set[{foo: 0, bar: 1}]
|
|
*
|
|
* Related: see {Methods for Converting}[rdoc-ref:Set@Methods+for+Converting].
|
|
*/
|
|
static VALUE
|
|
set_i_flatten(VALUE set)
|
|
{
|
|
VALUE new_set = set_s_alloc(rb_obj_class(set));
|
|
if (RSET_COMPARE_BY_IDENTITY(set)) {
|
|
set_i_compare_by_identity(new_set);
|
|
}
|
|
set_flatten_merge(new_set, set, rb_hash_new());
|
|
return new_set;
|
|
}
|
|
|
|
static int
|
|
set_contains_set_i(st_data_t item, st_data_t arg)
|
|
{
|
|
if (rb_obj_is_kind_of(item, rb_cSet)) {
|
|
*(bool *)arg = true;
|
|
return ST_STOP;
|
|
}
|
|
return ST_CONTINUE;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* flatten! -> self or nil
|
|
*
|
|
* Like #flatten, but if any changes were made
|
|
* replaces +self+ with the result and returns +self+:
|
|
*
|
|
* Set[Set[0, 1], Set[2, 3]].flatten!
|
|
* # => Set[0, 1, 2, 3]
|
|
* Set[Set[0, 1], Set[Set[2, 3], Set[3, 4]]].flatten!
|
|
* # => Set[0, 1, 2, 3, 4]
|
|
*
|
|
* Returns +nil+ if no changes were made:
|
|
*
|
|
* Set[0, 1, 2].flatten! # => nil
|
|
*
|
|
* Related: see {Methods for Assigning}[rdoc-ref:Set@Methods+for+Assigning].
|
|
*/
|
|
static VALUE
|
|
set_i_flatten_bang(VALUE set)
|
|
{
|
|
bool contains_set = false;
|
|
set_iter(set, set_contains_set_i, (st_data_t)&contains_set);
|
|
if (!contains_set) return Qnil;
|
|
rb_check_frozen(set);
|
|
return set_i_replace(set, set_i_flatten(set));
|
|
}
|
|
|
|
struct set_subset_data {
|
|
set_table *table;
|
|
VALUE result;
|
|
};
|
|
|
|
static int
|
|
set_le_i(st_data_t key, st_data_t arg)
|
|
{
|
|
struct set_subset_data *data = (struct set_subset_data *)arg;
|
|
if (set_table_lookup(data->table, key)) return ST_CONTINUE;
|
|
data->result = Qfalse;
|
|
return ST_STOP;
|
|
}
|
|
|
|
static VALUE
|
|
set_le(VALUE set, VALUE other)
|
|
{
|
|
struct set_subset_data data = {
|
|
.table = RSET_TABLE(other),
|
|
.result = Qtrue
|
|
};
|
|
set_iter(set, set_le_i, (st_data_t)&data);
|
|
return data.result;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* proper_subset?(other_set) -> true or false
|
|
*
|
|
* Returns whether +self+ is
|
|
* a {proper subset}[https://en.wikipedia.org/wiki/Subset]
|
|
* of the given +other_set+:
|
|
*
|
|
* set = Set[*'b'..'e']
|
|
* set.proper_subset?(set) # => false
|
|
* set.proper_subset?(Set[*'a'..'f']) # => true
|
|
*
|
|
* Related: {Methods for Querying}[rdoc-ref:Set@Methods+for+Querying].
|
|
*/
|
|
static VALUE
|
|
set_i_proper_subset(VALUE set, VALUE other)
|
|
{
|
|
check_set(other);
|
|
if (RSET_SIZE(set) >= RSET_SIZE(other)) return Qfalse;
|
|
return set_le(set, other);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* subset?(other_set) -> true or false
|
|
*
|
|
* Returns whether +self+ is a {subset}[https://en.wikipedia.org/wiki/Subset]
|
|
* of the given +other_set+:
|
|
*
|
|
* set = Set[*'b'..'e']
|
|
* set.subset?(set) # => true
|
|
* set.subset?(Set[*'a'..'f']) # => true
|
|
* set.subset?(Set[*'c'..'e']) # => false
|
|
*
|
|
* Related: {Methods for Querying}[rdoc-ref:Set@Methods+for+Querying].
|
|
*/
|
|
static VALUE
|
|
set_i_subset(VALUE set, VALUE other)
|
|
{
|
|
check_set(other);
|
|
if (RSET_SIZE(set) > RSET_SIZE(other)) return Qfalse;
|
|
return set_le(set, other);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* proper_superset?(other_set) -> true or false
|
|
*
|
|
* Returns whether +self+ is
|
|
* a {proper superset}[https://en.wikipedia.org/wiki/Subset]
|
|
* of the given +other_set+:
|
|
*
|
|
* set = Set[*'a'..'f']
|
|
* set.proper_superset?(set) # => false
|
|
* set.proper_superset?(Set[*'b'..'e']) # => true
|
|
*
|
|
* Related: {Methods for Querying}[rdoc-ref:Set@Methods+for+Querying].
|
|
*/
|
|
static VALUE
|
|
set_i_proper_superset(VALUE set, VALUE other)
|
|
{
|
|
check_set(other);
|
|
if (RSET_SIZE(set) <= RSET_SIZE(other)) return Qfalse;
|
|
return set_le(other, set);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* superset?(other_set) -> true or false
|
|
*
|
|
* Returns whether +self+ is a {superset}[https://en.wikipedia.org/wiki/Subset]
|
|
* of the given +other_set+:
|
|
*
|
|
* set = Set[*'a'..'f'] # => Set["a", "b", "c", "d", "e", "f"]
|
|
* set.superset?(set) # => true
|
|
* set.superset?(Set[*'b'..'e']) # => true
|
|
* set.superset?(Set[*'b'..'x']) # => false
|
|
*
|
|
* Related: {Methods for Querying}[rdoc-ref:Set@Methods+for+Querying].
|
|
*/
|
|
static VALUE
|
|
set_i_superset(VALUE set, VALUE other)
|
|
{
|
|
check_set(other);
|
|
if (RSET_SIZE(set) < RSET_SIZE(other)) return Qfalse;
|
|
return set_le(other, set);
|
|
}
|
|
|
|
static int
|
|
set_intersect_i(st_data_t key, st_data_t arg)
|
|
{
|
|
VALUE *args = (VALUE *)arg;
|
|
if (set_table_lookup((set_table *)args[0], key)) {
|
|
args[1] = Qtrue;
|
|
return ST_STOP;
|
|
}
|
|
return ST_CONTINUE;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* intersect?(enumerable) -> true or false
|
|
*
|
|
* Returns whether +self+ and +enumerable+ have any elements in common:
|
|
*
|
|
* set = Set[0, 'zero', :zero]
|
|
* set.intersect?([0, 1, 2]) # => true
|
|
* set.intersect?(%w[zero one two]) # => true
|
|
* set.intersect?(Set[3]) # => false
|
|
*
|
|
* Related: see {Methods for Querying}[rdoc-ref:Set@Methods+for+Querying].
|
|
*/
|
|
static VALUE
|
|
set_i_intersect(VALUE set, VALUE other)
|
|
{
|
|
if (rb_obj_is_kind_of(other, rb_cSet)) {
|
|
size_t set_size = RSET_SIZE(set);
|
|
size_t other_size = RSET_SIZE(other);
|
|
VALUE args[2];
|
|
args[1] = Qfalse;
|
|
VALUE iter_arg;
|
|
|
|
if (set_size < other_size) {
|
|
iter_arg = set;
|
|
args[0] = (VALUE)RSET_TABLE(other);
|
|
}
|
|
else {
|
|
iter_arg = other;
|
|
args[0] = (VALUE)RSET_TABLE(set);
|
|
}
|
|
set_iter(iter_arg, set_intersect_i, (st_data_t)args);
|
|
return args[1];
|
|
}
|
|
else if (rb_obj_is_kind_of(other, rb_mEnumerable)) {
|
|
return rb_funcall(other, id_any_p, 1, set);
|
|
}
|
|
else {
|
|
rb_raise(rb_eArgError, "value must be enumerable");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* disjoint?(enumerable) -> true or false
|
|
*
|
|
* Returns whether no element of +enumerable+ is present in +self+:
|
|
*
|
|
* set = Set[0, 'zero', :zero]
|
|
* set.disjoint?([1, 2, 3]) # => true
|
|
* set.disjoint?([0, 1, 2, 3]) # => false
|
|
*
|
|
* Related: see {Methods for Querying}[rdoc-ref:Set@Methods+for+Querying].
|
|
*/
|
|
static VALUE
|
|
set_i_disjoint(VALUE set, VALUE other)
|
|
{
|
|
return RBOOL(!RTEST(set_i_intersect(set, other)));
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* self <=> object -> -1, 0, 1, or nil
|
|
*
|
|
* Compares +self+ and +object+.
|
|
*
|
|
* If +object+ is another set, returns:
|
|
*
|
|
* - +-1+, if +self+ is a proper subset of +object+.
|
|
* - +0+, if +self+ and +object+ have the same elements.
|
|
* - +1+, if +self+ is a proper superset of +object+.
|
|
* - +nil+, if none of the above;
|
|
* that is, if +self+ and +object+ each have one or more elements
|
|
* not included in the other.
|
|
*
|
|
* Examples:
|
|
*
|
|
* set = Set[0, 1, 2]
|
|
* set <=> Set[3, 2, 1, 0] # => -1
|
|
* set <=> Set[2, 1, 0] # => 0
|
|
* set <=> Set[1, 0] # => 1
|
|
* set <=> Set[1, 0, 3] # => nil
|
|
*
|
|
* Returns +nil+ if +object+ is not a set:
|
|
*
|
|
* set <=> [2, 1, 0] # => nil # Array, not Set.
|
|
*
|
|
* Related: see {Methods for Comparing}[rdoc-ref:Set@Methods+for+Comparing].
|
|
*/
|
|
static VALUE
|
|
set_i_compare(VALUE set, VALUE other)
|
|
{
|
|
if (rb_obj_is_kind_of(other, rb_cSet)) {
|
|
size_t set_size = RSET_SIZE(set);
|
|
size_t other_size = RSET_SIZE(other);
|
|
|
|
if (set_size < other_size) {
|
|
if (set_le(set, other) == Qtrue) {
|
|
return INT2NUM(-1);
|
|
}
|
|
}
|
|
else if (set_size > other_size) {
|
|
if (set_le(other, set) == Qtrue) {
|
|
return INT2NUM(1);
|
|
}
|
|
}
|
|
else if (set_le(set, other) == Qtrue) {
|
|
return INT2NUM(0);
|
|
}
|
|
}
|
|
|
|
return Qnil;
|
|
}
|
|
|
|
struct set_equal_data {
|
|
VALUE result;
|
|
VALUE set;
|
|
};
|
|
|
|
static int
|
|
set_eql_i(st_data_t item, st_data_t arg)
|
|
{
|
|
struct set_equal_data *data = (struct set_equal_data *)arg;
|
|
|
|
if (!set_table_lookup(RSET_TABLE(data->set), item)) {
|
|
data->result = Qfalse;
|
|
return ST_STOP;
|
|
}
|
|
return ST_CONTINUE;
|
|
}
|
|
|
|
static VALUE
|
|
set_recursive_eql(VALUE set, VALUE dt, int recur)
|
|
{
|
|
if (recur) return Qtrue;
|
|
struct set_equal_data *data = (struct set_equal_data*)dt;
|
|
data->result = Qtrue;
|
|
set_iter(set, set_eql_i, dt);
|
|
return data->result;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* self == object -> true or false
|
|
*
|
|
* Returns whether +object+ is a set, and has the same elements as +self+:
|
|
*
|
|
* set = Set[0, 1, 2]
|
|
* set == Set[1, 2, 0] # => true
|
|
* set == [1, 2, 3] # => false
|
|
* set == Set[1, 2, '3'] # => false
|
|
*
|
|
* Related: see {Methods for Comparing}[rdoc-ref:Set@Methods+for+Comparing].
|
|
*/
|
|
static VALUE
|
|
set_i_eq(VALUE set, VALUE other)
|
|
{
|
|
if (!rb_obj_is_kind_of(other, rb_cSet)) return Qfalse;
|
|
if (set == other) return Qtrue;
|
|
|
|
set_table *stable = RSET_TABLE(set);
|
|
set_table *otable = RSET_TABLE(other);
|
|
size_t ssize = set_table_size(stable);
|
|
size_t osize = set_table_size(otable);
|
|
|
|
if (ssize != osize) return Qfalse;
|
|
if (ssize == 0 && osize == 0) return Qtrue;
|
|
if (stable->type != otable->type) return Qfalse;
|
|
|
|
struct set_equal_data data;
|
|
data.set = other;
|
|
return rb_exec_recursive_paired(set_recursive_eql, set, other, (VALUE)&data);
|
|
}
|
|
|
|
static int
|
|
set_hash_i(st_data_t item, st_data_t(arg))
|
|
{
|
|
st_index_t *hval = (st_index_t *)arg;
|
|
st_index_t ival = rb_hash(item);
|
|
*hval ^= rb_st_hash(&ival, sizeof(st_index_t), 0);
|
|
return ST_CONTINUE;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* hash -> integer
|
|
*
|
|
* Returns the integer hash value for +self+.
|
|
*
|
|
* Two sets with the same content have the same hash value.
|
|
*
|
|
* Set[0, 1].hash == Set[1, 0].hash # => true
|
|
* Set[0, 1].hash == Set[0].hash # => false
|
|
*/
|
|
static VALUE
|
|
set_i_hash(VALUE set)
|
|
{
|
|
st_index_t size = RSET_SIZE(set);
|
|
st_index_t hval = rb_st_hash_start(size);
|
|
hval = rb_hash_uint(hval, (st_index_t)set_i_hash);
|
|
if (size) {
|
|
set_iter(set, set_hash_i, (VALUE)&hval);
|
|
}
|
|
hval = rb_st_hash_end(hval);
|
|
return ST2FIX(hval);
|
|
}
|
|
|
|
/* :nodoc: */
|
|
static int
|
|
set_to_hash_i(st_data_t key, st_data_t arg)
|
|
{
|
|
rb_hash_aset((VALUE)arg, (VALUE)key, Qtrue);
|
|
return ST_CONTINUE;
|
|
}
|
|
|
|
static VALUE
|
|
set_i_to_h(VALUE set)
|
|
{
|
|
long size = RSET_SIZE(set);
|
|
VALUE hash;
|
|
if (RSET_COMPARE_BY_IDENTITY(set)) {
|
|
hash = rb_ident_hash_new_capa(size);
|
|
}
|
|
else {
|
|
hash = rb_hash_new_capa(size);
|
|
}
|
|
rb_hash_set_default(hash, Qfalse);
|
|
|
|
if (size == 0) return hash;
|
|
|
|
set_iter(set, set_to_hash_i, (st_data_t)hash);
|
|
return hash;
|
|
}
|
|
|
|
static VALUE
|
|
compat_dumper(VALUE set)
|
|
{
|
|
VALUE dumper = rb_class_allocate_instance_capa(rb_cObject, 1);
|
|
rb_ivar_set(dumper, id_i_hash, set_i_to_h(set));
|
|
return dumper;
|
|
}
|
|
|
|
static int
|
|
set_i_from_hash_i(st_data_t key, st_data_t val, st_data_t set)
|
|
{
|
|
if ((VALUE)val != Qtrue) {
|
|
rb_raise(rb_eRuntimeError, "expect true as Set value: %"PRIsVALUE, rb_obj_class((VALUE)val));
|
|
}
|
|
set_i_add((VALUE)set, (VALUE)key);
|
|
return ST_CONTINUE;
|
|
}
|
|
|
|
static VALUE
|
|
set_i_from_hash(VALUE set, VALUE hash)
|
|
{
|
|
Check_Type(hash, T_HASH);
|
|
if (rb_hash_compare_by_id_p(hash)) set_i_compare_by_identity(set);
|
|
rb_hash_stlike_foreach(hash, set_i_from_hash_i, (st_data_t)set);
|
|
return set;
|
|
}
|
|
|
|
static VALUE
|
|
compat_loader(VALUE self, VALUE a)
|
|
{
|
|
return set_i_from_hash(self, rb_ivar_get(a, id_i_hash));
|
|
}
|
|
|
|
/* Internal C-API functions */
|
|
|
|
VALUE
|
|
rb_ident_set_new(void)
|
|
{
|
|
return set_alloc_with_size_and_type(rb_cSet, 0, &identhash);
|
|
}
|
|
|
|
bool
|
|
rb_set_add_no_check(VALUE set, VALUE element)
|
|
{
|
|
if (set_insert(RSET_TABLE(set), (st_data_t)element) == 0) {
|
|
RB_OBJ_WRITTEN(set, Qundef, element);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
rb_set_delete_no_check(VALUE set, VALUE element)
|
|
{
|
|
return set_table_delete(RSET_TABLE(set), (st_data_t *)&element) != 0;
|
|
}
|
|
|
|
VALUE
|
|
rb_set_to_a(VALUE set)
|
|
{
|
|
return set_i_to_a(set);
|
|
}
|
|
|
|
/* C-API functions */
|
|
|
|
void
|
|
rb_set_foreach(VALUE set, int (*func)(VALUE element, VALUE arg), VALUE arg)
|
|
{
|
|
set_iter(set, func, arg);
|
|
}
|
|
|
|
VALUE
|
|
rb_set_new(void)
|
|
{
|
|
return set_alloc_with_size(rb_cSet, 0);
|
|
}
|
|
|
|
VALUE
|
|
rb_set_new_capa(size_t capa)
|
|
{
|
|
return set_alloc_with_size(rb_cSet, (st_index_t)capa);
|
|
}
|
|
|
|
bool
|
|
rb_set_lookup(VALUE set, VALUE element)
|
|
{
|
|
return RSET_IS_MEMBER(set, element);
|
|
}
|
|
|
|
bool
|
|
rb_set_add(VALUE set, VALUE element)
|
|
{
|
|
return set_i_add_p(set, element) != Qnil;
|
|
}
|
|
|
|
VALUE
|
|
rb_set_clear(VALUE set)
|
|
{
|
|
return set_i_clear(set);
|
|
}
|
|
|
|
bool
|
|
rb_set_delete(VALUE set, VALUE element)
|
|
{
|
|
return set_i_delete_p(set, element) != Qnil;
|
|
}
|
|
|
|
size_t
|
|
rb_set_size(VALUE set)
|
|
{
|
|
return RSET_SIZE(set);
|
|
}
|
|
|
|
/*
|
|
* Document-class: Set
|
|
*
|
|
* An instance of class \Set contains a collection
|
|
* of objects (elements), with no duplicates.
|
|
*
|
|
* By default:
|
|
*
|
|
* - Set determines equality via Object#eql? and Object#hash,
|
|
* and assumes that these values do not change for a stored element.
|
|
* If these values do change, the set enters an unreliable state;
|
|
* see #reset.
|
|
* - A String instance added to a set is stored as a frozen copy of the string,
|
|
* unless it is already frozen.
|
|
*
|
|
* Calling #compare_by_identity causes:
|
|
*
|
|
* - All following determinations of equality
|
|
* to use object identity instead of the methods mentioned above.
|
|
* - A String added to a set is stored "as is", whether or not frozen.
|
|
*
|
|
* \Set includes module Enumerable, and is easy to use with other enumerable objects.
|
|
* Many of its methods accept enumerable objects as arguments;
|
|
* any enumerable object may be converted to a set via #to_set.
|
|
*
|
|
* == Contact
|
|
*
|
|
* - Akinori MUSHA <knu@iDaemons.org> (current maintainer)
|
|
*
|
|
* == Inheriting from \Set
|
|
*
|
|
* Before Ruby 4.0 (released in December, 2025),
|
|
* class \Set had a different, less efficient implementation.
|
|
* In Ruby 4.0, the class was reimplemented in C,
|
|
* and the behaviors of some methods were adjusted.
|
|
*
|
|
* When compatibility with the older implementation is needed,
|
|
* a \Set subclass should inherit directly from class +Set+;
|
|
* this automatically includes module +Set::SubclassCompatible+,
|
|
* which makes behaviors closer to those in the older implementation.
|
|
*
|
|
* A difference may be seen as follows:
|
|
*
|
|
* Set[[1, 2, 3]] # => Set[[1, 2, 3]]
|
|
* class MySet < Set; end
|
|
* MySet[[1, 2, 3]] # => #<MySet: {[1, 2, 3]}> # Same as in Ruby 3.4.
|
|
*
|
|
* When backward compatibility is not needed,
|
|
* a \Set subclass should inherit from +Set::CoreSet+,
|
|
* which avoids including the compatibility layer:
|
|
*
|
|
* class MyCoreSet < Set::CoreSet; end
|
|
* MyCoreSet[[1, 2, 3]] # => MyCoreSet[[1, 2, 3]]
|
|
*
|
|
* == What's Here
|
|
*
|
|
* First, what's elsewhere. \Class \Set:
|
|
*
|
|
* - Inherits from {class Object}[rdoc-ref:Object@Whats+Here].
|
|
* - Includes {module Enumerable}[rdoc-ref:Enumerable@Whats+Here],
|
|
* which provides dozens of additional methods.
|
|
*
|
|
* In particular, class \Set does not have many methods of its own
|
|
* for fetching or for iterating.
|
|
* Instead, it relies on those in \Enumerable.
|
|
*
|
|
* Here, class \Set provides methods that are useful for:
|
|
*
|
|
* - {Creating a Set}[rdoc-ref:Set@Methods+for+Creating+a+Set]
|
|
* - {Set Operations}[rdoc-ref:Set@Methods+for+Set+Operations]
|
|
* - {Comparing}[rdoc-ref:Set@Methods+for+Comparing]
|
|
* - {Querying}[rdoc-ref:Set@Methods+for+Querying]
|
|
* - {Assigning}[rdoc-ref:Set@Methods+for+Assigning]
|
|
* - {Deleting}[rdoc-ref:Set@Methods+for+Deleting]
|
|
* - {Converting}[rdoc-ref:Set@Methods+for+Converting]
|
|
* - {And more....}[rdoc-ref:Set@Other+Methods]
|
|
*
|
|
* === Methods for Creating a \Set
|
|
*
|
|
* - ::[]:
|
|
* Returns a new set populated with the given objects.
|
|
* - ::new:
|
|
* Returns a new set based on the given object (if no block given),
|
|
* or on the return values from the called block (if a block given).
|
|
*
|
|
* === Methods for \Set Operations
|
|
*
|
|
* - #& (aliased as #intersection):
|
|
* Returns a new set containing the intersection of +self+ and the given enumerable.
|
|
* - #- (aliased as #difference):
|
|
* Returns a new set containing the difference of +self+ and the given enumerable.
|
|
* - #^: Returns a new set containing the exclusive OR of +self+ and the given enumerable.
|
|
* - #| (aliased as #union and #+):
|
|
* Returns a new set containing the union of +self+ and the given enumerable.
|
|
*
|
|
* === Methods for Comparing
|
|
*
|
|
* - #<=>: Returns -1, 0, or 1 as +self+ is less than, equal to,
|
|
* or greater than a given object.
|
|
* - #==: Returns whether +self+ and a given enumerable are equal,
|
|
* as determined by Object#eql?.
|
|
* - #compare_by_identity?:
|
|
* Returns whether +self+ considers only identity
|
|
* when comparing elements.
|
|
* - #proper_subset? (aliased as #<):
|
|
* Returns whether the given enumerable is a proper subset of +self+.
|
|
* - #proper_superset? (aliased as #>):
|
|
* Returns whether the given enumerable is a proper superset of +self+.
|
|
* - #subset? (aliased as #<=):
|
|
* Returns whether the given object is a subset of +self+.
|
|
* - #superset? (aliased as #>=):
|
|
* Returns whether the given enumerable is a superset of +self+.
|
|
*
|
|
* === Methods for Querying
|
|
*
|
|
* - #disjoint?:
|
|
* Returns whether no element of the given enumerable is present in +self+.
|
|
* - #empty?:
|
|
* Returns whether +self+ contains no elements.
|
|
* - #include? (aliased as #member? and #===):
|
|
* Returns whether the given object is an element of +self+.
|
|
* - #intersect?:
|
|
* Returns whether +self+ and the given enumerable have any elements in common.
|
|
* - #size (aliased as #length):
|
|
* Returns the number of elements in +self+.
|
|
*
|
|
* === Methods for Assigning
|
|
*
|
|
* - #add (aliased as #<<):
|
|
* Adds the given object to +self+; returns +self+.
|
|
* - #add?:
|
|
* Like #add, but returns +nil+ if the given object is already in +self+.
|
|
* - #merge:
|
|
* Adds each element of each of the given enumerables to +self+; returns +self+.
|
|
* - #replace:
|
|
* Replaces the contents of +self+ with the contents of the given enumerable;
|
|
* returns +self+.
|
|
*
|
|
* === Methods for Deleting
|
|
*
|
|
* - #clear:
|
|
* Removes all elements from +self+; returns +self+.
|
|
* - #delete:
|
|
* Removes the given object from +self+ if +self+ includes the object; returns +self+.
|
|
* - #delete?:
|
|
* Like #delete, but returns +nil+ if the object is not in +self+.
|
|
* - #delete_if:
|
|
* Calls the block with each element in +self+;
|
|
* removes the element if the block returns a truthy value.
|
|
* - #keep_if:
|
|
* Calls the block with each element in +self+,
|
|
* deleting the element if the block returns +false+ or +nil+; returns +self+.
|
|
* - #reject!
|
|
* Like #delete_if, but returns +nil+ if no changes were made.
|
|
* - #select! (aliased as #filter!):
|
|
* Like #keep_if, but returns +nil+ if no changes were made.
|
|
* - #subtract:
|
|
* Deletes from +self+ every element found in the given enumerable; returns +self+:
|
|
*
|
|
* === Methods for Converting
|
|
*
|
|
* - #classify:
|
|
* Returns a hash that partitions the elements,
|
|
* as determined by the given block.
|
|
* - #collect! (aliased as #map!):
|
|
* Replaces each element with a block return-value.
|
|
* - #divide:
|
|
* Returns a set of sets that partition the elements,
|
|
* as determined by the given block.
|
|
* - #flatten:
|
|
* Returns a new set that is a recursive flattening of +self+.
|
|
* - #flatten!: Like #flatten, but if any changes were made
|
|
* replaces +self+ with the result and returns +self+.
|
|
* - #inspect (aliased as #to_s):
|
|
* Returns a string representation of +self+.
|
|
* - #join:
|
|
* Returns the string formed by joining the string-converted elements of +self+
|
|
* with the given separator.
|
|
* - #to_a:
|
|
* Returns an array containing the elements of +self+.
|
|
* - #to_set:
|
|
* With a block given, creates and returns a new set;
|
|
* calls the block with each element of +self+,
|
|
* and adds the block's returns value to the new set.
|
|
*
|
|
* === Other Methods
|
|
*
|
|
* - #compare_by_identity:
|
|
* Sets +self+ to compare by object identity (rather than by object content).
|
|
* - #each:
|
|
* Calls the block with each successive element of +self+; returns +self+.
|
|
* - #reset:
|
|
* Resets the internal state of +self+; returns +self+.
|
|
* Useful if an element has been modified while an element in the set.
|
|
*
|
|
*/
|
|
void
|
|
Init_Set(void)
|
|
{
|
|
rb_cSet = rb_define_class("Set", rb_cObject);
|
|
rb_include_module(rb_cSet, rb_mEnumerable);
|
|
|
|
id_each_entry = rb_intern_const("each_entry");
|
|
id_any_p = rb_intern_const("any?");
|
|
id_new = rb_intern_const("new");
|
|
id_i_hash = rb_intern_const("@hash");
|
|
id_subclass_compatible = rb_intern_const("SubclassCompatible");
|
|
id_class_methods = rb_intern_const("ClassMethods");
|
|
id_set_iter_lev = rb_make_internal_id();
|
|
|
|
rb_define_alloc_func(rb_cSet, set_s_alloc);
|
|
rb_define_singleton_method(rb_cSet, "[]", set_s_create, -1);
|
|
|
|
rb_define_method(rb_cSet, "initialize", set_i_initialize, -1);
|
|
rb_define_method(rb_cSet, "initialize_copy", set_i_initialize_copy, 1);
|
|
|
|
rb_define_method(rb_cSet, "&", set_i_intersection, 1);
|
|
rb_define_alias(rb_cSet, "intersection", "&");
|
|
rb_define_method(rb_cSet, "-", set_i_difference, 1);
|
|
rb_define_alias(rb_cSet, "difference", "-");
|
|
rb_define_method(rb_cSet, "^", set_i_xor, 1);
|
|
rb_define_method(rb_cSet, "|", set_i_union, 1);
|
|
rb_define_alias(rb_cSet, "+", "|");
|
|
rb_define_alias(rb_cSet, "union", "|");
|
|
rb_define_method(rb_cSet, "<=>", set_i_compare, 1);
|
|
rb_define_method(rb_cSet, "==", set_i_eq, 1);
|
|
rb_define_alias(rb_cSet, "eql?", "==");
|
|
rb_define_method(rb_cSet, "add", set_i_add, 1);
|
|
rb_define_alias(rb_cSet, "<<", "add");
|
|
rb_define_method(rb_cSet, "add?", set_i_add_p, 1);
|
|
rb_define_method(rb_cSet, "classify", set_i_classify, 0);
|
|
rb_define_method(rb_cSet, "clear", set_i_clear, 0);
|
|
rb_define_method(rb_cSet, "collect!", set_i_collect, 0);
|
|
rb_define_alias(rb_cSet, "map!", "collect!");
|
|
rb_define_method(rb_cSet, "compare_by_identity", set_i_compare_by_identity, 0);
|
|
rb_define_method(rb_cSet, "compare_by_identity?", set_i_compare_by_identity_p, 0);
|
|
rb_define_method(rb_cSet, "delete", set_i_delete, 1);
|
|
rb_define_method(rb_cSet, "delete?", set_i_delete_p, 1);
|
|
rb_define_method(rb_cSet, "delete_if", set_i_delete_if, 0);
|
|
rb_define_method(rb_cSet, "disjoint?", set_i_disjoint, 1);
|
|
rb_define_method(rb_cSet, "divide", set_i_divide, 0);
|
|
rb_define_method(rb_cSet, "each", set_i_each, 0);
|
|
rb_define_method(rb_cSet, "empty?", set_i_empty, 0);
|
|
rb_define_method(rb_cSet, "flatten", set_i_flatten, 0);
|
|
rb_define_method(rb_cSet, "flatten!", set_i_flatten_bang, 0);
|
|
rb_define_method(rb_cSet, "hash", set_i_hash, 0);
|
|
rb_define_method(rb_cSet, "include?", set_i_include, 1);
|
|
rb_define_alias(rb_cSet, "member?", "include?");
|
|
rb_define_alias(rb_cSet, "===", "include?");
|
|
rb_define_method(rb_cSet, "inspect", set_i_inspect, 0);
|
|
rb_define_alias(rb_cSet, "to_s", "inspect");
|
|
rb_define_method(rb_cSet, "intersect?", set_i_intersect, 1);
|
|
rb_define_method(rb_cSet, "join", set_i_join, -1);
|
|
rb_define_method(rb_cSet, "keep_if", set_i_keep_if, 0);
|
|
rb_define_method(rb_cSet, "merge", set_i_merge, -1);
|
|
rb_define_method(rb_cSet, "proper_subset?", set_i_proper_subset, 1);
|
|
rb_define_alias(rb_cSet, "<", "proper_subset?");
|
|
rb_define_method(rb_cSet, "proper_superset?", set_i_proper_superset, 1);
|
|
rb_define_alias(rb_cSet, ">", "proper_superset?");
|
|
rb_define_method(rb_cSet, "reject!", set_i_reject, 0);
|
|
rb_define_method(rb_cSet, "replace", set_i_replace, 1);
|
|
rb_define_method(rb_cSet, "reset", set_i_reset, 0);
|
|
rb_define_method(rb_cSet, "size", set_i_size, 0);
|
|
rb_define_alias(rb_cSet, "length", "size");
|
|
rb_define_method(rb_cSet, "select!", set_i_select, 0);
|
|
rb_define_alias(rb_cSet, "filter!", "select!");
|
|
rb_define_method(rb_cSet, "subset?", set_i_subset, 1);
|
|
rb_define_alias(rb_cSet, "<=", "subset?");
|
|
rb_define_method(rb_cSet, "subtract", set_i_subtract, 1);
|
|
rb_define_method(rb_cSet, "superset?", set_i_superset, 1);
|
|
rb_define_alias(rb_cSet, ">=", "superset?");
|
|
rb_define_method(rb_cSet, "to_a", set_i_to_a, 0);
|
|
rb_define_method(rb_cSet, "to_set", set_i_to_set, 0);
|
|
|
|
/* :nodoc: */
|
|
VALUE compat = rb_define_class_under(rb_cSet, "compatible", rb_cObject);
|
|
rb_marshal_define_compat(rb_cSet, compat, compat_dumper, compat_loader);
|
|
|
|
// Create Set::CoreSet before defining inherited, so it does not include
|
|
// the backwards compatibility layer.
|
|
rb_define_class_under(rb_cSet, "CoreSet", rb_cSet);
|
|
rb_define_private_method(rb_singleton_class(rb_cSet), "inherited", set_s_inherited, 1);
|
|
|
|
rb_provide("set.rb");
|
|
}
|