Ruby - массив подклассов для рандомизации при выравнивании - PullRequest
2 голосов
/ 24 марта 2010

Я пытаюсь создать подкласс Array в ruby, чтобы он сглаживал свои элементы при сглаживании! называется. Глядя на исходный код Array # flatten (http://ruby -doc.org / core / classes / Array.src / M002218.html ), похоже, что он должен рекурсивно вызывать flatten! на любой массив, содержащийся в массиве. Итак, я попытался сделать что-то вроде этого:

class RandArray < Array
    def randomize!
        self.sort!{rand(3)-1}
    end
    def flatten!
        randomize!
        super
    end
end

Однако, когда обычный массив содержит мой RandArray, и вызывается flatten для обычного массива, flatten! никогда не вызывается в моем массиве. Я полагаю, что ruby ​​просто вызывает какой-то другой метод для рекурсивного выравнивания массивов, но я не могу понять, что это такое. Любые советы?

Ответы [ 2 ]

3 голосов
/ 24 марта 2010

Я не абсолютный эксперт в этом, но Array Ruby написан как C-код. вот код для сглаживания! :

static VALUE
rb_ary_flatten_bang(ary)
    VALUE ary;
{
    long i = 0;
    int mod = 0;
    VALUE memo = Qnil;

    while (i<RARRAY(ary)->len) {
        VALUE ary2 = RARRAY(ary)->ptr[i];
        VALUE tmp;

        tmp = rb_check_array_type(ary2);
        if (!NIL_P(tmp)) {
            if (NIL_P(memo)) {
                memo = rb_ary_new();
            }
            i += flatten(ary, i, tmp, memo);
            mod = 1;
        }
        i++;
    }
    if (mod == 0) return Qnil;
    return ary;
}

Как вы видите на этой строке,

i += flatten(ary, i, tmp, memo);

и вот реализация этой функции С сглаживания:

static long
flatten(ary, idx, ary2, memo)
    VALUE ary;
    long idx;
    VALUE ary2, memo;
{
    VALUE id;
    long i = idx;
    long n, lim = idx + RARRAY(ary2)->len;

    id = rb_obj_id(ary2);
    if (rb_ary_includes(memo, id)) {
    rb_raise(rb_eArgError, "tried to flatten recursive array");
    }
    rb_ary_push(memo, id);
    rb_ary_splice(ary, idx, 1, ary2);
    while (i < lim) {
    VALUE tmp;

    tmp = rb_check_array_type(rb_ary_elt(ary, i));
    if (!NIL_P(tmp)) {
        n = flatten(ary, i, tmp, memo);
        i += n; lim += n;
    }
    i++;
    }
    rb_ary_pop(memo);

    return lim - idx - 1;   /* returns number of increased items */
}

Свести! код напрямую вызывает функцию C flatten для любого элемента массива, который проверяет rb_check_array_type , он не возвращается к ruby-коду. Вместо этого он обращается к базовой структуре C напрямую, минуя вашу перегруженную реализацию.

Не уверен, как это переопределить, я думаю, что одним из способов может быть открытие Массива и переписывание сглаживания и сглаживания! функционировать как чистый рубин. Вы получите удар по производительности, но затем сможете перегрузить его, как считаете нужным. И вы всегда можете использовать псевдонимы, чтобы иметь «flatten_native» и «flatten_native!» функция в вашем модифицированном массиве, чтобы вернуть perfs в некоторых случаях.

1 голос
/ 24 марта 2010

Джин верна, сглаживание вызывает функцию C за кадром. Вы можете исправить класс Array и переопределить стандартное выравнивание! метод, сохраняя при этом доступ к исходному методу.

class Array
  alias_method :old_flatten!, :flatten!
  def flatten!
    self.old_flatten!
    self.sort!{rand(3)-1}
  end
end

Или вы можете просто добавить flatten_with_randomize! к классу Array и используйте его вместо этого и сохраняйте исходное значение! метод не поврежден.

...