Как перебрать глубокий вложенный хеш без известной глубины в Ruby

У меня есть несколько файлов YAML (локализация). Я разбираю их и конвертирую в хэш на Ruby.

Например, это один из них:

hello: Hallo
messages:
  alerts:
    yay: Da!
    no: Nein
  deep:
    nested:
      another:
        level:
          hi: Hi!
test: Test!

По сути, это похоже на файл локали в приложении Rails с использованием YAML.

Что я хочу сделать, так это рекурсивно повторить этот хэш и получить ключ и значение. Чтобы я мог переводить значения одно за другим из конечной точки API, например Google Translate. Я хочу сохранить вложенные хэши в той же схеме, чтобы Rails мог найти их по ключам.

Я знаю, что могу использовать вложенные циклы, но нет гарантии, что количество вложенных хэшей известно. Как я могу рекурсивно повторять этот хеш, чтобы я мог манипулировать значениями (переводить/заменять)?

Ожидаемый результат: (после использования службы перевода из вызова API)

hello: Hello
messages:
  alerts:
    yay: Yup!
    no: No
  deep:
    nested:
      another:
        level:
          hi: Hi!
test: Test!

Что я пробовал до сих пор:

hash = YAML.load('de.yml') # parse source Deutsch locale 
new_hash = {}

hash.each |key, value| do
  new_hash[key] = translate_func(value) # here... translate value then assign very same key including parents.

  # Do more loops....
end

# Now write this new_hash to yaml file...

Но это только манипулировать только hello. Чтобы работать с другими, мне нужно сделать петлю. Но сколько ключей вложено, неизвестно.

Как я могу перебрать все значения хэша локали и сохранить схему нетронутой?

И если это возможно, но не обязательно, я был бы очень рад, если бы мы сохранили порядок ключей в конечном результате. Было бы здорово найти отсутствующие ключи позже при просмотре вручную.

Я очень новичок в рубине. Я использую Руби 2.7.2

Заключение/решение

Все ответы правильные, и я люблю их всех. Однако я хотел бы иметь возможность контролировать как ключи, так и значения. Не просто трансформировать по значениям. Поэтому я принял ответ, который соответствует моим потребностям. Я смог выполнить свое намерение с выбранным ответом.


person Slasher    schedule 23.01.2021    source источник
comment
Подсказка: ответ — десятое слово четвертого абзаца.   -  person Jörg W Mittag    schedule 24.01.2021
comment
возможно, этот ответ поможет: stackoverflow.com/questions/65756598/   -  person r4cc00n    schedule 24.01.2021
comment
Спасибо за отзыв об ответах. Правильный этикет стека   -  person Tom Harvey    schedule 24.01.2021


Ответы (3)


Итак, вы хотите рекурсивно анализировать до тех пор, пока не останется уровней для анализа.

Это очень распространено в программном обеспечении и называется «рекурсией». Поищите, чтобы узнать больше об этом — это будет появляться снова и снова в вашем путешествии. Добро пожаловать в рубин кстати!

Что касается вашей реальной текущей проблемы. Прочтите https://mrxpalmeiras.wordpress.com/2017/03/30/how-to-parse-a-nested-yaml-config-file-in-python-and-ruby/

Но также обратите внимание на гем i18n. См. этот ответ https://stackoverflow.com/a/51216931/1777331 и документы для драгоценного камня https://github.com/ruby-i18n/i18n Это может решить вашу проблему обработки интернационализации без необходимости чтобы узнать подробности обработки файлов yaml.

person Tom Harvey    schedule 23.01.2021

Вы можете использовать deep_transform_values! в своем хеш-объекте для рекурсивного изменения значений. (Или его неразрушающая версия deep_transform_values, которая возвращает новый хэш вместо изменения исходного хэша.)

hash.deep_transform_values! { |value| translate_func(value) }

Примечание: deep_transform_values! — это метод Rails. См. исходный код здесь для вдохновения, если вы не используете Rails.

person hmnhf    schedule 23.01.2021

Если вам нужно простое решение без рельсов, вы можете просто создать рекурсивный метод:

def recurse(hash)
  hash.transform_values do |v|
    case v
    when String
      v.reverse # Just for the sake of the example
    when Hash
      recurse(v)
    else
      v
    end
  end
end

Выход:

{"hello"=>"ollaH", "messages"=>{"alerts"=>{"yay"=>"!aD", false=>"nieN"}, "deep"=>{"nested"=>{"another"=>{"level"=>{"hi"=>"!iH"}}}}}, "test"=>"!tseT"}

Однако это может быть случай изобретения велосипеда — вы можете использовать гем i18n для переводов и i18n-tasks для предварительного заполнения файлов YAML переводами из Google Translate API.

person max    schedule 23.01.2021
comment
Помимо выбранного ответа, это единственная жизнеспособная альтернатива для меня, и я обязательно буду использовать ее в других случаях использования. Спасибо. Выбранный ответ содержит драгоценный камень i18n, который сглаживает клавиши. Таким образом, я могу сравнить с целевой локалью и перевести только отсутствующие ключи. Это позволяет мне свести к минимуму вызовы API, которые уже перевели ключи. - person Slasher; 24.01.2021