ruby 如何递归地从散列中生成连接字符串?

vd2z7a6w  于 2022-11-04  发布在  Ruby
关注(0)|答案(4)|浏览(140)

我有下面这个复杂的哈希结构(在许多哈希结构中),看起来如下:

hash = {"en-us"=>
  {:learn_more=>"Learn more",
   :non_apple_desktop=>"To redeem, open this link.",
   :value_prop_safety=>"",
   :storage_size=>
    {:apple_tv_1_month_tms=>
      {:cta=>"Offer",
       :details=>"Get a 1-month subscription!.",
       :disclaimer=>"This offer expires on December 10, 2021.",
       :header=>"Watch The Morning Show ",
       :normal_price=>"$2.99"}
      }
    }
  }

我想做的是创建一个函数,它将基于哈希结构生成以下字符串输出:

en-us.storage_size.apple_tv_1_month_tms.cta: Offer
en-us.storage_size.apple_tv_1_month_tms.details: Get a 1-month subscription!.
en-us.storage_size.apple_tv_1_month_tms.disclaimer: This offer expires on December 10, 2021.
en-us.storage_size.apple_tv_1_month_tms.header: Watch The Morning Show
en-us.storage_size.apple_tv_1_month_tms.normal_price: $2.99
en-us.learn_more: Learn more
en-us.non_apple_desktop: To redeem, open this link.
en-us.value_prop_safety:

我使用了另一个stackoverflow问题中的递归函数,它在一定程度上完成了这一点:

def show(hash, current_path = '')
  string = ''
  hash.each do |k,v|
    if v.respond_to?(:each)
      current_path += "#{k}."
      show v, current_path
    else
      string += "#{current_path}#{k}: #{v}" + "\n"
    end
  end
  string
end

如果我把puts语句放在方法体中,我可以看到想要的结果,但它是逐行的。我需要的是获得完整的输出,因为我将把它写入一个csv。我似乎不能让它在当前的化身中工作。
如果我把一个puts show(hash)放到我的irb中,那么我将不会得到任何输出。

show(hash) ----->

en-us.storage_size.apple_tv_1_month_tms.cta: Offer
en-us.storage_size.apple_tv_1_month_tms.details: Get a 1-month subscription!.
en-us.storage_size.apple_tv_1_month_tms.disclaimer: This offer expires on December 10, 2021.
en-us.storage_size.apple_tv_1_month_tms.header: Watch The Morning Show
en-us.storage_size.apple_tv_1_month_tms.normal_price: $2.99
en-us.learn_more: Learn more
en-us.non_apple_desktop: To redeem, open this link.
en-us.value_prop_safety:

这应该是一个简单的递归任务,但我不能指出我到底错在哪里。帮助将是非常感激的。谢谢。

n7taea2i

n7taea2i1#

在我看来,使用i18n gem要方便得多
它有一个I18n::Backend::Flatten#flatten_translations方法,它接收一个翻译的散列(其中键是一个locale,值是另一个散列),并返回一个所有翻译都扁平化的散列,这正是您所需要的
只需将生成的哈希转换为字符串,就可以完成了

require "i18n/backend/flatten"

include I18n::Backend::Flatten

locale_hash = {"en-us"=>
  {:learn_more=>"Learn more",
   :non_apple_desktop=>"To redeem, open this link.",
   :value_prop_safety=>"",
   :storage_size=>
    {:apple_tv_1_month_tms=>
      {:cta=>"Offer",
       :details=>"Get a 1-month subscription!.",
       :disclaimer=>"This offer expires on December 10, 2021.",
       :header=>"Watch The Morning Show ",
       :normal_price=>"$2.99"}
      }
    }
  }

puts flatten_translations(nil, locale_hash, nil, nil).
       map { |k, v| "#{k}: #{v}" }.
       join("\n")

# will print

# en-us.learn_more: Learn more

# en-us.non_apple_desktop: To redeem, open this link.

# en-us.value_prop_safety:

# en-us.storage_size.apple_tv_1_month_tms.cta: Offer

# en-us.storage_size.apple_tv_1_month_tms.details: Get a 1-month subscription!.

# en-us.storage_size.apple_tv_1_month_tms.disclaimer: This offer expires on December 10, 2021.

# en-us.storage_size.apple_tv_1_month_tms.header: Watch The Morning Show

# en-us.storage_size.apple_tv_1_month_tms.normal_price: $2.99

当然,最好不要包含在main对象中,而是包含在某个服务对象中

require "i18n/backend/flatten"

class StringifyLocaleHash
  include I18n::Backend::Flatten

  attr_reader :locale_hash

  def self.call(locale_hash)
    new(locale_hash).call
  end

  def initialize(locale_hash)
    @locale_hash = locale_hash
  end

  def call
    flatten_translations(nil, locale_hash, nil, nil).
      map { |k, v| "#{k}: #{v}" }.
      join("\n")
  end
end

# To get string call such way

StringifyLocaleHash.(locale_hash)
elcex8rz

elcex8rz2#

回答你的问题:

show v, current_path

应该是

string += show v, current_path

否则将丢失递归调用所做所有工作。
请注意,a += b会将a替换为新字符串a + b。它不会更改a。因此,保留show的返回值非常重要。
如果你想依赖于字符串是可变的,这里有一个可变字符串版本;然而,注意它可能并不总是有效,因为字符串不变性是Ruby中增加的一个选项。如果frozen_string_literals是on,可变连接操作符<<将失败。在可变字符串版本中,您不能在每次迭代中初始化string,因为您将丢弃调用者已经完成的工作;因此它将作为另一个参数传递,并仅在初始调用时由默认值初始化。

def show(hash, current_path = '', string = '')
  hash.each do |k,v|
    if v.respond_to?(:each)
      current_path += "#{k}."
      show v, current_path, string
    else
      string << "#{current_path}#{k}: #{v}" + "\n"
    end
  end
  string
end
vnzz0bqm

vnzz0bqm3#

方法的注意事项

一、几个问题:

  1. Ruby不是JavaScript,哈希没有属性,所以链接点是不起作用的,除非你在事后做大量的工作来分割和连接数据的路径。
  2. en-us不是有效的LANG值。您可以将其作为键使用,但如果区域设置很重要,您可能希望使用技术上更准确的值(如en_USen_US.UTF-8)作为键。
    第二,如果您已经 * 知道 * JSON的结构,那么您只需将每个JSON键设置为一个列值并填充它,这可能要容易得多。
    第三,虽然Ruby可以进行递归,但它的大多数内置方法都是为 iteration 设计的。迭代对于很多事情来说更快更容易,所以除非你有其他原因,否则只要迭代你需要的信息就行了。

从具有预定义格式的结构化数据构建CSV

最后,如果您只想将数据填充到CSV或创建字符串,那么您可能只想提取数据,然后将其与您已经知道的信息混合在一起。例如:

require "hashie"

# assume your hash is already defined

offer_values =
  hash.extend(Hashie::Extensions::DeepFind)
    .deep_find(:apple_tv_1_month_tms).values

# =>

# ["Offer",

# "Get a 1-month subscription!.",

# "This offer expires on December 10, 2021.",

# "Watch The Morning Show ",

# "$2.99"]

要获取顶级键的值,可以这样做:

linking = hash["en-us"].keys[0..-2].map { hash.dig "en-us", _1 }

# => ["Learn more", "To redeem, open this link.", ""]

如果做更多的工作,你可以使用ActiveSupport或者使用Ruby 3.1的模式匹配和查找模式来做类似的事情,但这也适用于较早的Ruby版本。唯一的缺点是它依赖于Hashie gem来进行Hashie::DeepFind,但这是Ruby没有一个简单的内置方法来查找嵌套结构的情况之一,而不是模式匹配,我认为这是值得的。
您仍然需要做一些工作来将提取的数据转换为您想要的格式,但这会将您想要的所有值转换为一对变量,offer_valueslinking。然后您可以对它们做任何您想要的操作。

请参阅

除了Hashie::DeepFind和Ruby 3模式匹配之外,还有许多查询语言可能对从JSON中提取数据很有用,其中一些语言带有Ruby gem,或者可以很容易地集成到Ruby应用程序中:

vsdwdz23

vsdwdz234#

这是一个相对简单的递归,因为每个值都是一个字符串或哈希值。
第一个

相关问题