diff --git a/hash.c b/hash.c index 91a4e9f..8240659 100644 --- a/hash.c +++ b/hash.c @@ -222,8 +222,18 @@ hash_foreach_call(VALUE arg) return Qnil; } -void -rb_hash_foreach(VALUE hash, int (*func)(ANYARGS), VALUE farg) +static VALUE +hash_reverse_foreach_call(VALUE arg) +{ + VALUE hash = ((struct hash_foreach_arg *)arg)->hash; + if (st_reverse_foreach_check(RHASH(hash)->ntbl, hash_foreach_iter, (st_data_t)arg, (st_data_t)Qundef)) { + rb_raise(rb_eRuntimeError, "hash modified during iteration"); + } + return Qnil; +} + +static void +do_hash_foreach(VALUE hash, int (*func)(ANYARGS), VALUE farg, int reverse) { struct hash_foreach_arg arg; @@ -233,7 +243,22 @@ rb_hash_foreach(VALUE hash, int (*func)(ANYARGS), VALUE farg) arg.hash = hash; arg.func = (rb_foreach_func *)func; arg.arg = farg; - rb_ensure(hash_foreach_call, (VALUE)&arg, hash_foreach_ensure, hash); + if (reverse) + rb_ensure(hash_reverse_foreach_call, (VALUE)&arg, hash_foreach_ensure, hash); + else + rb_ensure(hash_foreach_call, (VALUE)&arg, hash_foreach_ensure, hash); +} + +void +rb_hash_foreach(VALUE hash, int (*func)(ANYARGS), VALUE farg) +{ + do_hash_foreach(hash, func, farg, 0); +} + +static void +rb_hash_reverse_foreach(VALUE hash, int (*func)(ANYARGS), VALUE farg) +{ + do_hash_foreach(hash, func, farg, 1); } static VALUE @@ -1534,6 +1559,37 @@ rb_hash_each_pair(VALUE hash) return hash; } +/* + * call-seq: + * hsh.reverse_each {| key, value | block } -> hsh + * hsh.reverse_each -> an_enumerator + * + * Calls block once for each key in hsh, passing the key-value + * pair as parameters. + * + * If no block is given, an enumerator is returned instead. + * + * h = { "a" => 100, "b" => 200 } + * h.reverse_each {|key, value| puts "#{key} is #{value}" } + * + * produces: + * + * b is 200 + * a is 100 + * + */ + +static VALUE +rb_hash_reverse_each_pair(VALUE hash) +{ + RETURN_SIZED_ENUMERATOR(hash, 0, 0, hash_enum_size); + if (rb_block_arity() > 1) + rb_hash_reverse_foreach(hash, each_pair_i_fast, 0); + else + rb_hash_reverse_foreach(hash, each_pair_i, 0); + return hash; +} + static int to_a_i(VALUE key, VALUE value, VALUE ary) { @@ -3643,6 +3699,7 @@ Init_Hash(void) rb_define_method(rb_cHash,"each_key", rb_hash_each_key, 0); rb_define_method(rb_cHash,"each_pair", rb_hash_each_pair, 0); rb_define_method(rb_cHash,"each", rb_hash_each_pair, 0); + rb_define_method(rb_cHash,"reverse_each", rb_hash_reverse_each_pair, 0); rb_define_method(rb_cHash,"keys", rb_hash_keys, 0); rb_define_method(rb_cHash,"values", rb_hash_values, 0); diff --git a/include/ruby/st.h b/include/ruby/st.h index e71301b..8f2a2f3 100644 --- a/include/ruby/st.h +++ b/include/ruby/st.h @@ -111,7 +111,7 @@ typedef int st_update_callback_func(st_data_t *key, st_data_t *value, st_data_t int st_update(st_table *table, st_data_t key, st_update_callback_func *func, st_data_t arg); int st_foreach(st_table *, int (*)(ANYARGS), st_data_t); int st_foreach_check(st_table *, int (*)(ANYARGS), st_data_t, st_data_t); -int st_reverse_foreach(st_table *, int (*)(ANYARGS), st_data_t); +int st_reverse_foreach_check(st_table *, int (*)(ANYARGS), st_data_t, st_data_t); void st_add_direct(st_table *, st_data_t, st_data_t); void st_free_table(st_table *); void st_cleanup_safe(st_table *, st_data_t); diff --git a/st.c b/st.c index 6e3df62..5a2aef6 100644 --- a/st.c +++ b/st.c @@ -1091,54 +1091,67 @@ st_foreach(st_table *table, int (*func)(ANYARGS), st_data_t arg) return 0; } -#if 0 /* unused right now */ int -st_reverse_foreach(st_table *table, int (*func)(ANYARGS), st_data_t arg) +st_reverse_foreach_check(st_table *table, int (*func)(ANYARGS), st_data_t arg, st_data_t never) { st_table_entry *ptr, **last, *tmp; enum st_retval retval; int i; if (table->entries_packed) { - for (i = table->num_entries-1; 0 <= i; i--) { - int j; - st_data_t key, val; - key = PKEY(table, i); - val = PVAL(table, i); - retval = (*func)(key, val, arg); - switch (retval) { + for (i = table->num_entries-1; i >= 0; i--) { + st_data_t key, val; + st_index_t hash; + key = PKEY(table, i); + val = PVAL(table, i); + hash = PHASH(table, i); + if (key == never) continue; + retval = (*func)(key, val, arg); + if (!table->entries_packed) { + FIND_ENTRY(table, ptr, hash, i); + if (retval == ST_CHECK) { + if (!ptr) goto deleted; + goto unpacked_continue; + } + goto unpacked; + } + switch (retval) { case ST_CHECK: /* check if hash is modified during iteration */ - for (j = 0; j < table->num_entries; j++) { - if (PKEY(table, j) == key) - break; - } - if (j == table->num_entries) { - /* call func with error notice */ - retval = (*func)(0, 0, arg, 1); - return 1; - } + if (PHASH(table, i) == 0 && PKEY(table, i) == never) { + break; + } + i = find_packed_index(table, hash, key); + if (i == table->real_entries) { + goto deleted; + } /* fall through */ case ST_CONTINUE: break; case ST_STOP: return 0; case ST_DELETE: - remove_packed_entry(table, i); - break; - } + remove_safe_packed_entry(table, i, never); + break; + } } return 0; } + else { + ptr = table->tail; + } - if ((ptr = table->head) != 0) { - ptr = ptr->back; + if (ptr != 0) { do { - retval = (*func)(ptr->key, ptr->record, arg, 0); + if (ptr->key == never) + goto unpacked_continue; + i = ptr->hash % table->num_bins; + retval = (*func)(ptr->key, ptr->record, arg); + unpacked: switch (retval) { case ST_CHECK: /* check if hash is modified during iteration */ - i = ptr->hash % table->num_bins; for (tmp = table->bins[i]; tmp != ptr; tmp = tmp->next) { if (!tmp) { + deleted: /* call func with error notice */ retval = (*func)(0, 0, arg, 1); return 1; @@ -1146,6 +1159,7 @@ st_reverse_foreach(st_table *table, int (*func)(ANYARGS), st_data_t arg) } /* fall through */ case ST_CONTINUE: + unpacked_continue: ptr = ptr->back; break; case ST_STOP: @@ -1155,22 +1169,18 @@ st_reverse_foreach(st_table *table, int (*func)(ANYARGS), st_data_t arg) for (; (tmp = *last) != 0; last = &tmp->next) { if (ptr == tmp) { tmp = ptr->back; - *last = ptr->next; remove_entry(table, ptr); - st_free_entry(ptr); + ptr->key = ptr->record = never; + ptr->hash = 0; ptr = tmp; break; } } - ptr = ptr->next; - free(tmp); - table->num_entries--; } } while (ptr && table->head); } return 0; } -#endif /* * hash_32 - 32 bit Fowler/Noll/Vo FNV-1a hash code diff --git a/test/ruby/test_hash.rb b/test/ruby/test_hash.rb index 21fbf41..685a969 100644 --- a/test/ruby/test_hash.rb +++ b/test/ruby/test_hash.rb @@ -400,6 +400,23 @@ class TestHash < Test::Unit::TestCase assert_equal([], res - expected) end + def test_reverse_each + count = 0 + @cls[].reverse_each { |k, v| count + 1 } + assert_equal(0, count) + + h = @h + h.reverse_each do |k, v| + assert_equal(v, h.delete(k)) + end + assert_equal(@cls[], h) + + h = @cls[] + h[1] = 1 + h[2] = 2 + assert_equal([[2,2],[1,1]], h.reverse_each.to_a) + end + def test_empty? assert_empty(@cls[]) assert_not_empty(@h)