# Hash#identifed\_by

**URL:** <https://www.ruby-forum.com/t/hash-identifed-by/129322>\
**Category:** Development\
**Created:** [February 16, 2008, 6:15pm UTC](https://www.ruby-forum.com/t/hash-identifed-by/129322 "2008-02-16T18:15:44Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![nobu](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/n/e9c0ed/32.png) [@nobu](https://www.ruby-forum.com/u/nobu)\
**Post date:** [February 16, 2008, 6:15pm UTC](https://www.ruby-forum.com/t/hash-identifed-by/129322/1 "2008-02-16T18:15:44Z")

</div>

e$B$J$+$@$G$9!#e(B

compared\_bye$B$H$$$&L\>A0$K$7$h$&$+$H;W$$$^$7$?$,!"Hf3S$@$1$G$O$J$$e(B  
e$B$N$Ge(Bidentifed\_bye$B$H$7$F$\_$^$7$?!#e(B

marshal.ce$B$Ne(Bw\_encoding()e$B$b@0M}$7$F$"$j$^$9!#e(B

# Index: hash.c

— hash.c (revision 15489)  
+++ hash.c (working copy)  
@@ -25,4 +25,5 @@ static VALUE rb\_hash\_s\_try\_convert(VALUE  
#define HASH\_DELETED FL\_USER1  
#define HASH\_PROC\_DEFAULT FL\_USER2  
+#define HASH\_IDENT\_BY\_METHOD FL\_USER3

VALUE  
@@ -410,5 +411,6 @@ static VALUE  
rb\_hash\_rehash(VALUE hash)  
{

- st\_table \*tbl;

- st\_table \*tbl, \*ntbl;

- VALUE htmp;

- if (!RHASH(hash)-\>ntbl)

- if (!(ntbl = RHASH(hash)-\>ntbl))  
return hash;

- tbl = st\_init\_table\_with\_size(RHASH(hash)-\>ntbl-\>type,  
RHASH(hash)-\>ntbl-\>num\_entries);

- htmp = hash\_alloc(0);
- tbl = st\_init\_table\_with\_size(ntbl-\>type, ntbl-\>num\_entries);
- RHASH(htmp)-\>ntbl = tbl;  
rb\_hash\_foreach(hash, rb\_hash\_rehash\_i, (st\_data\_t)tbl);

- st\_free\_table(RHASH(hash)-\>ntbl);

- ntbl = RHASH(hash)-\>ntbl;

- RHASH(htmp)-\>ntbl = 0;  
RHASH(hash)-\>ntbl = tbl;

- st\_free\_table(ntbl);

- rb\_gc\_force\_recycle(htmp);

-static const struct st\_hash\_type identhash = {

- st\_numcmp,
- st\_numhash,  
+struct hash\_ident {

- ID equal, hash;  
+};
- 

+static int  
+compare\_by\_method(st\_data\_t x, st\_data\_t y, st\_table \*t)  
+{

- struct hash\_ident \*ids = st\_extra\_data(t);
- VALUE other = (VALUE)y;
- VALUE val = rb\_funcall2((VALUE)x, ids-\>equal, 1, &other);
- return RTEST(val);  
+}
- 

+static int  
+hash\_by\_method(st\_data\_t x, st\_table \*t)  
+{

- struct hash\_ident \*ids = st\_extra\_data(t);
- VALUE val = rb\_funcall2((VALUE)x, ids-\>hash, 0, 0);
- return NUM2INT(val);  
+}
- 

+static const struct st\_hash\_type hash\_ident\_type = {

- compare\_by\_method,
- hash\_by\_method,  
};

/\*

- call-seq:

- 
  - 

```
hsh.compare_by_identity => hsh

```

- 
  - 

```
Hash.identified_by(equ, hash) => hsh

```

  - 

- 
  - Makes _hsh_ to compare its keys by their identity, i.e. it

- 
  - will consider exact same objects as same keys.

- 
- 
  - Makes _hsh_ which identifies its keys with +equ+ and +hash+

- 
  - methods.

- 
  - 

- 
  - 

```
h1 = { "a" => 100, "b" => 200, :c => "c" }

```

- 
  - 

```
h1["a"] #=> 100

```

- 
  - 

```
h1.compare_by_identity

```

- 
  - 

```
h1.compare_by_identity? #=> true

```

- 
  - 

```
h1 = Hash.identified_by(:equal?, :object_id)

```

- 
  - 

```
h1["a"] = 100

```

- 
  - 

```
key_a = h1.key(100)

```

- 
  - 

```
h1["b"] = 200

```

- 
  - 

```
h1[:c] = "c"

```

  - 

```
h1["a"] #=> nil # different objects.

```

- 
  - 

```
h1[key_a] #=> 100 # same object as a key.

```

  - 

```
h1[:c] #=> "c" # same symbols are all same.

```

  - 

@@ -1705,30 +1737,75 @@ static const struct st\_hash\_type identha

static VALUE  
-rb\_hash\_compare\_by\_id(VALUE hash)  
+rb\_hash\_s\_identified\_by(VALUE klass, VALUE equal\_mid, VALUE hash\_mid)  
{

- rb\_hash\_modify(hash);
- RHASH(hash)-\>ntbl-\>type = &identhash;
- rb\_hash\_rehash(hash);
- return hash;

- struct hash\_ident ids;
- VALUE h;
- st\_table \*tbl;
- 
- ids.equal = rb\_to\_id(equal\_mid);
- ids.hash = rb\_to\_id(hash\_mid);
- h = hash\_alloc(klass);
- RBASIC(h)-\>flags |= HASH\_IDENT\_BY\_METHOD;
- tbl = st\_init\_table\_extra(&hash\_ident\_type, sizeof(ids));
- RHASH(h)-\>ntbl = tbl;
- \*(struct hash\_ident \*)st\_extra\_data(tbl) = ids;
- return h;  
}

/\*

- call-seq:

- 
  - 

```
hsh.compare_by_identity? => true or false

```

- 
  - 

- 
  - Returns `true` if _hsh_ will compare its keys by

- 
  - their identity. Also see `Hash#compare_by_identity`.

- 
  - 

```
hsh.methods_to_identify => a pair of symbols or nil

```

  - 

- 
  - Returns `Symbol`s to be used to identify the items if

- 
  - _hsh_ is created with `Hash#identified_by`.

- 
  - Otherwise, returns nil.  
\*/

-static VALUE  
-rb\_hash\_compare\_by\_id\_p(VALUE hash)  
+VALUE  
+rb\_hash\_get\_ident\_methods(VALUE hash)  
{

- if (!RHASH(hash)-\>ntbl)
- 

```
   return Qfalse;

```

- if (RHASH(hash)-\>ntbl-\>type == &identhash) {
- return Qtrue;
- }
- return Qfalse;

- struct hash\_ident \*ids;
- 
- if (!FL\_TEST(hash, HASH\_IDENT\_BY\_METHOD)) return Qnil;
- ids = st\_extra\_data(RHASH(hash)-\>ntbl);
- return rb\_assoc\_new(ID2SYM(ids-\>equal), ID2SYM(ids-\>hash));  
+}
- 

+ID  
+rb\_hash\_ident\_methods\_id(void)  
+{

- return rb\_intern(“methods\_to\_identify”);  
+}
- 

+void  
+rb\_hash\_set\_ident\_method(VALUE hash, VALUE val)  
+{

- struct hash\_ident ids;
- st\_table \*tbl, \*ntbl;
- VALUE htmp;
- 
- if (TYPE(val) != T\_ARRAY || RARRAY\_LEN(val) != 2 ||
- !SYMBOL\_P(RARRAY\_PTR(val)[0]) ||
- !SYMBOL\_P(RARRAY\_PTR(val)[1])) {
- rb\_raise(rb\_eArgError, “wrong methods to identify”);
- }
- ids.equal = SYM2ID(RARRAY\_PTR(val)[0]);
- ids.hash = SYM2ID(RARRAY\_PTR(val)[1]);
- 
- ntbl = RHASH(hash)-\>ntbl;
- htmp = hash\_alloc(0);
- tbl = st\_init\_table\_extra\_with\_size(&hash\_ident\_type,
- 

```
     sizeof(ids),

```

- 

```
     ntbl->num_entries);

```

- RBASIC(htmp)-\>flags |= HASH\_IDENT\_BY\_METHOD;
- RHASH(htmp)-\>ntbl = tbl;
- \*(struct hash\_ident \*)st\_extra\_data(tbl) = ids;
- rb\_hash\_foreach(hash, rb\_hash\_rehash\_i, (st\_data\_t)tbl);
- ntbl = RHASH(hash)-\>ntbl;
- RHASH(htmp)-\>ntbl = 0;
- RHASH(hash)-\>ntbl = tbl;
- RBASIC(hash)-\>flags |= HASH\_IDENT\_BY\_METHOD;
- st\_free\_table(ntbl);
- rb\_gc\_force\_recycle(htmp);  
}

@@ -2610,6 +2687,6 @@ Init\_Hash(void)  
rb\_define\_method(rb\_cHash,“value?”, rb\_hash\_has\_value, 1);

- rb\_define\_method(rb\_cHash,“compare\_by\_identity”,  
rb\_hash\_compare\_by\_id, 0);
- rb\_define\_method(rb\_cHash,“compare\_by\_identity?”,  
rb\_hash\_compare\_by\_id\_p, 0);

- rb\_define\_singleton\_method(rb\_cHash, “identified\_by”,  
rb\_hash\_s\_identified\_by, 2);
- rb\_define\_method(rb\_cHash, “methods\_to\_identify”,  
rb\_hash\_get\_ident\_methods, 0);

# #ifndef **MACOS** /\* environment variables nothing on MacOS. \*/ Index: marshal.c

— marshal.c (revision 15489)  
+++ marshal.c (working copy)  
@@ -465,34 +465,91 @@ w\_obj\_each(ID id, VALUE value, struct du  
}

-static void  
-w\_encoding(VALUE obj, long num, struct dump\_call\_arg \*arg)  
+ID rb\_hash\_ident\_methods\_id(void);  
+VALUE rb\_hash\_get\_ident\_methods(VALUE);  
+void rb\_hash\_set\_ident\_method(VALUE, VALUE);  
+  
+struct extra\_ivar {

- VALUE obj;
- int count;
- VALUE encoding;
- union {
- struct {
- 

```
 VALUE ident_methods;

```

- } hash;
- } as;  
+};
- 

+static int  
+has\_extra\_ivars(VALUE obj, struct extra\_ivar \*exivars, struct dump\_arg  
\*arg)  
{

- int encidx = rb\_enc\_get\_index(obj);
- rb\_encoding \*enc = 0;
- st\_data\_t name;

- int count = 0, encidx;
- rb\_encoding \*enc;

- if (encidx \<= 0 || !(enc = rb\_enc\_from\_index(encidx))) {
- w\_long(num, arg-\>arg);
- return;

- if (!ENCODING\_IS\_ASCII8BIT(obj) &&
- (encidx = rb\_enc\_get\_index(obj)) \> 0 &&
- (enc = rb\_enc\_from\_index(encidx)) != 0) {
- st\_data\_t name;
- 
- do {
- 

```
 if (!arg->encodings)

```

- arg-\>encodings = st\_init\_strcasetable();
- 

```
 else if (st_lookup(arg->encodings, (st_data_t)rb_enc_name(enc), 

```

&name))

- break;
- 

```
 name = (st_data_t)rb_str_new2(rb_enc_name(enc));

```

- 

```
 st_insert(arg->encodings, (st_data_t)rb_enc_name(enc), name);

```

- } while (0);
- ++count;
- exivars-\>encoding = (VALUE)name;
- }
- else {
- exivars-\>encoding = 0;
- }
- 
- switch (BUILTIN\_TYPE(obj)) {
- 

```
 case T_HASH: {

```

- VALUE ids = rb\_hash\_get\_ident\_methods(obj);
- if (RTEST(ids)) {
- 

```
 exivars->as.hash.ident_methods = ids;

```

- 

```
 ++count;

```

- }
- else {
- 

```
 exivars->as.hash.ident_methods = 0;

```

- }
- break;
- 

```
 }

```

- }
- 
- exivars-\>obj = obj;
- return exivars-\>count = count;  
+}
- 

+static void  
+w\_extra\_ivars(struct extra\_ivar \*exivars, struct dump\_call\_arg \*arg)  
+{

- if (exivars-\>encoding) {
- w\_symbol(rb\_id\_encoding(), arg-\>arg);
- w\_object(exivars-\>encoding, arg-\>arg, arg-\>limit);
- }
- 
- switch (BUILTIN\_TYPE(exivars-\>obj)) {
- 

```
 case T_HASH:

```

- if (exivars-\>as.hash.ident\_methods) {
- 

```
 w_symbol(rb_hash_ident_methods_id(), arg->arg);

```

- 

```
 w_object(exivars->as.hash.ident_methods, arg->arg, arg->limit);

```

- }
- break;  
}

- w\_long(num + 1, arg-\>arg);
- w\_symbol(rb\_id\_encoding(), arg-\>arg);
- do {
- if (!arg-\>arg-\>encodings)
- 

```
 arg->arg->encodings = st_init_strcasetable();

```

- else if (st\_lookup(arg-\>arg-\>encodings, (st\_data\_t)rb\_enc\_name(enc),  
&name))
- 

```
 break;

```

- name = (st\_data\_t)rb\_str\_new2(rb\_enc\_name(enc));
- st\_insert(arg-\>arg-\>encodings, (st\_data\_t)rb\_enc\_name(enc), name);
- } while (0);
- w\_object(name, arg-\>arg, arg-\>limit);  
}

static void  
-w\_ivar(VALUE obj, st\_table \*tbl, struct dump\_call\_arg \*arg)  
+w\_ivar(struct extra\_ivar \*exivars, st\_table \*tbl, struct dump\_call\_arg  
\*arg)  
{  
long num = tbl ? tbl-\>num\_entries : 0;

- w\_encoding(obj, num, arg);

- num += exivars-\>count;
- w\_long(num, arg-\>arg);
- if (exivars-\>count) {
- w\_extra\_ivars(exivars, arg);
- }  
if (tbl) {  
st\_foreach\_safe(tbl, w\_obj\_each, (st\_data\_t)arg);  
@@ -501,17 +558,20 @@ w\_ivar(VALUE obj, st\_table \*tbl, struct

static void  
-w\_objivar(VALUE obj, struct dump\_call\_arg \*arg)  
+w\_objivar(struct extra\_ivar \*exivars, struct dump\_call\_arg \*arg)  
{

- VALUE \*ptr;

- VALUE \*ptr, obj = exivars-\>obj;  
long i, len, num;

- num = 0;

- num = exivars-\>count;  
for (i = 0; i \< len; i++)  
if (ptr[i] != Qundef)  
num += 1;

- w\_encoding(obj, num, arg);

- w\_long(num, arg-\>arg);
- if (exivars-\>count) {
- w\_extra\_ivars(exivars, arg);
- }  
if (num != 0) {  
rb\_ivar\_foreach(obj, w\_obj\_each, (st\_data\_t)arg);  
@@ -526,6 +586,8 @@ w\_object(VALUE obj, struct dump\_arg \*arg  
st\_data\_t num;  
int hasiv = 0;  
-#define has\_ivars(obj, ivtbl) ((ivtbl = rb\_generic\_ivar\_table(obj)) !=  
0 || \

- 

```
        (!SPECIAL_CONST_P(obj) && !ENCODING_IS_ASCII8BIT(obj)))

```

- struct extra\_ivar exivar;  
+#define has\_ivars(obj, ivtbl, exivar) \

- (((ivtbl = rb\_generic\_ivar\_table(obj)) != 0) | \

- 

```
(!SPECIAL_CONST_P(obj) && has_extra_ivars(obj, &exivar, arg)))

```

if (limit == 0) {  
@@ -543,5 +605,5 @@ w\_object(VALUE obj, struct dump\_arg \*arg  
}

- if ((hasiv = has\_ivars(obj, ivtbl)) != 0) {

- if ((hasiv = has\_ivars(obj, ivtbl, exivar)) != 0) {  
w\_byte(TYPE\_IVAR, arg);  
}  
@@ -586,4 +648,5 @@ w\_object(VALUE obj, struct dump\_arg \*arg  
VALUE real\_obj = obj;  
obj = compat-\>dumper(real\_obj);
- 

```
           exivar.obj = obj;
           st_insert(arg->compat_tbl, (st_data_t)obj, 

```

(st\_data\_t)real\_obj);  
}  
@@ -596,5 +659,5 @@ w\_object(VALUE obj, struct dump\_arg \*arg  
w\_class(TYPE\_USRMARSHAL, obj, arg, Qfalse);  
w\_object(v, arg, limit);

- 

```
 if (hasiv) w_ivar(obj, 0, &c_arg);

```

- 

```
 if (hasiv) w_ivar(&exivar, 0, &c_arg);
 return;

```

}  
@@ -602,4 +665,5 @@ w\_object(VALUE obj, struct dump\_arg \*arg  
VALUE v;  
st\_table \*ivtbl2 = 0;
- 

```
       struct extra_ivar exivar2;
       int hasiv2;

```

@@ -608,5 +672,5 @@ w\_object(VALUE obj, struct dump\_arg \*arg  
rb\_raise(rb\_eTypeError, “\_dump() must return string”);  
}

- 

```
 if ((hasiv2 = has_ivars(v, ivtbl2)) != 0 && !hasiv) {

```

- 

```
 if ((hasiv2 = has_ivars(v, ivtbl2, exivar2)) != 0 && !hasiv) {

```

w\_byte(TYPE\_IVAR, arg);  
}  
@@ -614,8 +678,8 @@ w\_object(VALUE obj, struct dump\_arg \*arg  
w\_bytes(RSTRING\_PTR(v), RSTRING\_LEN(v), arg);  
if (hasiv2) {

- w\_ivar(v, ivtbl2, &c\_arg);

- w\_ivar(&exivar2, ivtbl2, &c\_arg);  
}  
else if (hasiv) {

- w\_ivar(obj, ivtbl, &c\_arg);

- w\_ivar(&exivar, ivtbl, &c\_arg);  
}  
return;  
@@ -739,5 +803,5 @@ w\_object(VALUE obj, struct dump\_arg \*arg  
case T\_OBJECT:  
w\_class(TYPE\_OBJECT, obj, arg, Qtrue);

- 

```
 w_objivar(obj, &c_arg);

```

- 

```
 w_objivar(&exivar, &c_arg);
 break;

```

@@ -764,5 +828,5 @@ w\_object(VALUE obj, struct dump\_arg \*arg  
}  
if (hasiv) {

- w\_ivar(obj, ivtbl, &c\_arg);

- w\_ivar(&exivar, ivtbl, &c\_arg);  
}  
}  
@@ -1093,4 +1159,8 @@ r\_ivar(VALUE obj, struct load\_arg \*arg)  
if (idx \> 0) rb\_enc\_associate\_index(obj, idx);  
}
- 

```
 else if (TYPE(obj) == T_HASH &&

```

- 

```
    id == rb_hash_ident_methods_id()) {

```

- rb\_hash\_set\_ident\_method(obj, val);
- 

```
 }
 else {

```

rb\_ivar\_set(obj, id, val);  
Index: st.c  
===================================================================  
— st.c (revision 15489)  
+++ st.c (working copy)  
@@ -69,7 +69,8 @@ static void rehash(st\_table _);  
 #define Calloc(n,s) (char_)calloc((n),(s))

-#define EQUAL(table,x,y) ((x)==(y) || (\*table-\>type-\>compare)((x),(y))  
== 0)  
+#define EQUAL(table,x,y) ((x)==(y) || \

- 

```
   (*table->type->compare)((x),(y),(table)) == 0)

```

-#define do\_hash(key,table) (unsigned int)(_(table)-\>type-\>hash)((key))  
+#define do\_hash(key,table) (unsigned  
int)(_(table)-\>type-\>hash)((key),(table))  
#define do\_hash\_bin(key,table) (do\_hash(key, table)%(table)-\>num\_bins)

@@ -154,19 +155,17 @@ stat\_col()  
#define MAX\_PACKED\_NUMHASH 5

-st\_table\*  
-st\_init\_table\_with\_size(const struct st\_hash\_type \*type, int size)  
-{

- st\_table \*tbl;
- 

#ifdef HASH\_LOG

- if (init\_st == 0) {
- init\_st = 1;
- atexit(stat\_col);
- }  
+#define INIT\_HASH\_LOG() \

- (void)((init\_st == 0) ? \
- 

```
(init_st = 1, atexit(stat_col)) : 0)

```

+#else  
+#define INIT\_HASH\_LOG() ((void)0)  
#endif

+static st\_table \*  
+st\_table\_init(st\_table \*tbl, const struct st\_hash\_type _type, int size)  
+{  
size = new\_size(size); /_ round up to prime number \*/

- tbl = alloc(st\_table);  
tbl-\>type = type;  
tbl-\>num\_entries = 0;  
@@ -180,4 +179,23 @@ st\_init\_table\_with\_size(const struct st\_

st\_table\*  
+st\_init\_table\_with\_size(const struct st\_hash\_type \*type, int size)  
+{

- return st\_table\_init(alloc(st\_table), type, size);  
+}
- 

+st\_table \*  
+st\_init\_table\_extra(const struct st\_hash\_type \*type, int extra)  
+{

- return st\_table\_init(malloc(sizeof(st\_table) + extra), type, 0);  
+}
- 

+st\_table \*  
+st\_init\_table\_extra\_with\_size(const struct st\_hash\_type \*type, int  
extra, int size)  
+{

- return st\_table\_init(malloc(sizeof(st\_table) + extra),
- 

```
  type, size);

```

# +} + +st\_table\* st\_init\_table(const struct st\_hash\_type \*type) { Index: include/ruby/st.h

— include/ruby/st.h (revision 15489)  
+++ include/ruby/st.h (working copy)  
@@ -74,4 +74,6 @@ st\_table \*st\_init\_strtable\_with\_size(int  
st\_table \*st\_init\_strcasetable(void);  
st\_table \*st\_init\_strcasetable\_with\_size(int);  
+st\_table \*st\_init\_table\_extra(const struct st\_hash\_type \*, int);  
+st\_table \*st\_init\_table\_extra\_with\_size(const struct st\_hash\_type \*,  
int, int);  
int st\_delete(st\_table \*, st\_data\_t \*, st\_data\_t \*);  
int st\_delete\_safe(st\_table \*, st\_data\_t \*, st\_data\_t \*, st\_data\_t);  
@@ -91,4 +93,6 @@ int st\_strcasecmp(const char \*s1, const  
int st\_strncasecmp(const char \*s1, const char \*s2, size\_t n);

# +static inline void \*st\_extra\_data(st\_table \*tbl) {return tbl + 1;} + #if defined(\_\_cplusplus) #if 0 Index: test/ruby/test\_hash.rb

— test/ruby/test\_hash.rb (revision 15511)  
+++ test/ruby/test\_hash.rb (working copy)  
@@ -818,12 +818,11 @@ class TestHash \< Test::Unit::TestCase  
end

- def test\_compare\_by\_identity
- a = “foo”
- assert(!{}.compare\_by\_identity?)
- h = { a =\> “bar” }
- assert(!h.compare\_by\_identity?)
- h.compare\_by\_identity
- assert(h.compare\_by\_identity?)
- #assert\_equal(“bar”, h[a])

- def test\_identified\_by
- a = “foo”.freeze
- assert(!{}.methods\_to\_identify)
- h = Hash.identified\_by(:equal?, :object\_id)
- assert\_equal([:equal?, :object\_id], h.methods\_to\_identify)
- h[a] = “bar”
- assert\_equal(“bar”, h[a])  
assert\_nil(h[“foo”])  
end
