Я, вероятно, написал бы это так:
class Object
def all_variables(root=true)
vars = {}
self.instance_variables.each do |var|
ivar = self.instance_variable_get(var)
vars[var] = [ivar, ivar.all_variables(false)]
end
root ? [self, vars] : vars
end
end
def string_variables(vars, lb="\n", indent="\t", current_indent="")
out = "#{vars[0].inspect}#{lb}"
current_indent += indent
out += vars[1].map do |var, ivar|
ivstr = string_variables(ivar, lb, indent, current_indent)
"#{current_indent}#{var}: #{ivstr}"
end.join
return out
end
def inspect_variables(obj, lb="\n", indent="\t", current_indent="")
string_variables(obj.all_variables, lb, indent, current_indent)
end
Метод Object#all_variables
создает массив, содержащий (0) заданный объект и (1) имена переменных экземпляра хеш-сопоставления, в массивы, содержащие (0) переменная экземпляра и (1) отображение хеша….Таким образом, это дает вам хорошую рекурсивную структуру.Функция string_variables
хорошо распечатывает этот хеш;inspect_variables
- это просто удобная обертка.Таким образом, print inspect_variables(foo)
дает вам опцию, разделенную символом новой строки, а print inspect_variables(foo, "<br />\n")
дает вам версию с переносом строки HTML.Если вы хотите указать отступ, вы также можете сделать это: print inspect_variables(foo, "\n", "|---")
создает (бесполезный) формат искусственного дерева вместо отступа на основе табуляции.
Должен быть разумный способнаписать функцию each_variable
, для которой вы предоставляете обратный вызов (который не должен был бы выделять промежуточное хранилище);Я отредактирую этот ответ, чтобы включить его, если я о чем-то подумаю. Редактировать 1: Я кое о чем подумал.
Вот еще один способ написать его, который я считаю слегкаприятнее:
class Object
def each_variable(name=nil, depth=0, parent=nil, &block)
yield name, self, depth, parent
self.instance_variables.each do |var|
self.instance_variable_get(var).each_variable(var, depth+1, self, &block)
end
end
end
def inspect_variables(obj, nl="\n", indent="\t", sep=': ')
out = ''
obj.each_variable do |name, var, depth, _parent|
out += [indent*depth, name, name ? sep : '', var.inspect, nl].join
end
return out
end
Метод Object#each_variable
принимает ряд необязательных аргументов, которые не предназначены для указания пользователем;вместо этого они используются рекурсией для поддержания состояния.Указанному блоку передаются (а) имя переменной экземпляра или nil
, если переменная является корнем рекурсии;(б) переменная;(c) глубина, до которой спускалась рекурсия;и (d) родительский элемент текущей переменной или nil
, если указанная переменная является корнем рекурсии.Рекурсия в глубину.Функция inspect_variables
использует это для создания строки.Аргумент obj
- это объект для итерации;nl
- разделитель строк;indent
- отступ, применяемый на каждом уровне;и sep
разделяет имя и значение.
Редактировать 2: Это на самом деле ничего не добавляет к ответу на ваш вопрос, но: просто чтобы доказать, что у нас нетпотерял что-то в переопределении, вот переопределение all_variables
в терминах each_variables
.
def all_variables(obj)
cur_depth = 0
root = [obj, {}]
tree = root
parents = []
prev = root
obj.each_variable do |name, var, depth, _parent|
next unless name
case depth <=> cur_depth
when -1 # We've gone back up
tree = parents.pop(cur_depth - depth)[0]
when +1 # We've gone down
parents << tree
tree = prev
else # We're at the same level
# Do nothing
end
cur_depth = depth
prev = tree[1][name] = [var, {}]
end
return root
end
Я чувствую, что это должно быть короче, но это может быть невозможно;поскольку у нас сейчас нет рекурсии, мы должны явно поддерживать стек (в parents
).Но это возможно, поэтому метод each_variable
работает так же хорошо (и я думаю, что он немного лучше).