ko1 / rubyhackchallenge

475 stars 83 forks source link

Hash#deep_has_key?(:key) #41

Closed rudy-on-rails closed 6 years ago

rudy-on-rails commented 6 years ago

The deep_search param can be used to recursively search within values of the hash object, when applicable

skateman commented 6 years ago

I think it should be named deep_has_key? or has_deep_key? instead.

skateman commented 6 years ago

Count me in with this!

skateman commented 6 years ago

It uses recursion, but I think it is a good start:

VALUE rb_hash_deep_has_key(VALUE hash, VALUE key)
{
    VALUE values;
    long i;
    if (rb_hash_has_key(hash, key) == Qtrue)
        return Qtrue;

    values = env_values();
    for (i=0; i<RARRAY_LEN(values); i++) {
        if (rb_hash_deep_has_key(RARRAY_AREF(values, i), key) == Qtrue)
            return Qtrue;
    }

    return Qfalse;
}
rudy-on-rails commented 6 years ago

Can be tested by:

  def test_deep_has_key
    z = @cls[@cls[1=>2]]
    assert_send([z, :deep_has_key?, 1])

    assert_equal(true, z.deep_has_key?(1))
  end
skateman commented 6 years ago

Try:

{
  :x => 3,
  :y => [1,2,3].
  :z => {
     :a => 3
   }
}

And search for :a...

rudy-on-rails commented 6 years ago

Some infinite recursion happens when:

    z = @cls[3=>@cls[1=>2]]
    assert_send([z, :deep_has_key?, 1])

is ran

skateman commented 6 years ago

There was a missing type test for the hash value:

VALUE rb_hash_deep_has_key(VALUE hash, VALUE key)
{
    VALUE values;
    long i;

    if (!RB_TYPE_P(hash, T_HASH))
        return Qfalse;
    if (rb_hash_has_key(hash, key) == Qtrue)
        return Qtrue;

    values = env_values();
    for (i=0; i<RARRAY_LEN(values); i++) {
        if (rb_hash_deep_has_key(RARRAY_AREF(values, i), key) == Qtrue)
            return Qtrue;
    }

    return Qfalse;
}
skateman commented 6 years ago

Oh my bad, rb_hash_values instead of env_values

skateman commented 6 years ago

Non-recursive implementation:

VALUE rb_hash_deep_has_key(VALUE hash, VALUE key)
{
    VALUE values;
    VALUE item;
    VALUE stack;
    VALUE children;
    long i;

    if (!RB_TYPE_P(hash, T_HASH))
        return Qfalse;

    stack = rb_hash_values(hash);
    while(RARRAY_LEN(stack) > 0) {
        item = rb_ary_pop(stack);

        if (RB_TYPE_P(item, T_HASH)) {
            if (rb_hash_has_key(item, key) == Qtrue)
                return Qtrue;

            children = rb_hash_values(hash);
            if (RB_TYPE_P(children, T_ARRAY)) {
                for (i = 0; i < RARRAY_LEN(children); i++) {
                    rb_ary_push(stack, RARRAY_AREF(children, i));
                }
            }
        }
    }
    return Qfalse;
}
rudy-on-rails commented 6 years ago

Cleaning up a bit and adding more tests:

MRI Implementation

rb_hash_deep_has_key(VALUE hash, VALUE key)
{
    VALUE item;
    VALUE stack;
    VALUE children;
    long i;

    if (!RB_TYPE_P(hash, T_HASH))
        return Qfalse;

    stack = rb_hash_values(hash);

    while(RARRAY_LEN(stack) > 0) {
        item = rb_ary_pop(stack);

        if (RB_TYPE_P(item, T_HASH)) {
            if (rb_hash_has_key(item, key) == Qtrue)
                return Qtrue;

            children = rb_hash_values(hash);
            if (RB_TYPE_P(children, T_ARRAY)) {
                for (i = 0; i < RARRAY_LEN(children); i++) {
                    rb_ary_push(stack, RARRAY_AREF(children, i));
                }
            }
        }
    }
    return Qfalse;
}

Tests:

def test_deep_has_key
    x = @cls[3=>@cls[1=>2]]
    y = @cls[x: 3, y: [1,2,3], z: @cls[a: 3]]
    z = @cls[]

    assert_send([x, :deep_has_key?, 1])
    assert_send([y, :deep_has_key?, :a])
    assert_equal(false, z.deep_has_key?(:any))
  end