如何在Ruby中使用哈希键进行这些字符串替换?

如何在Ruby中使用哈希键进行这些字符串替换?,ruby,Ruby,我有一堆JSON文件,用Python和Ruby处理,看起来像这样: { "KEY1": "foo", "KEY2": "bar", "URL": "https://{KEY2}.com/{KEY1}", "IMPORTANT_THING": "repos/{KEY1}", "NOTE": "This thing is {KEY1}{KEY2}ed", "PYTHON_ONLY_THING": "{}/test/{}.py" } 请注意,键的显示

我有一堆JSON文件,用Python和Ruby处理,看起来像这样:

{
    "KEY1": "foo",
    "KEY2": "bar",

    "URL": "https://{KEY2}.com/{KEY1}",
    "IMPORTANT_THING": "repos/{KEY1}",
    "NOTE": "This thing is {KEY1}{KEY2}ed",
    "PYTHON_ONLY_THING": "{}/test/{}.py"
}
请注意,键的显示顺序不一致,我不希望更改JSON

以下是我的测试代码,显示了我迄今为止所做的尝试:

my_config = {"KEY1"=>"foo",
             "KEY2"=>"bar",
             "URL"=>"https://{KEY2}.com/{KEY1}",
             "IMPORTANT_THING"=>"repos/{KEY1}",
             "NOTE"=>"This thing is {KEY1}{KEY2}ed",
             "PYTHON_ONLY_THING"=>"{}/test/{}.py"}

my_config.each_key do |key|
    # Braindead, hard-coded solution that works:
    # my_config[key].gsub!("{KEY1}", my_config["KEY1"])
    # my_config[key].gsub!("{KEY2}", my_config["KEY2"])

    # More flexible (if it would work):
    # my_config[key].gsub!(/{.*}/, my_config['\0'.slice(1,-2)])
    my_config[key].gsub!(/{.*}/) {|s| my_config[s.slice(1,-2)]}
end

puts my_config
我现在使用的是braindead解决方案,它可以产生预期的输出:

{"KEY1"=>"foo", "KEY2"=>"bar", "URL"=>"https://bar.com/foo", "IMPORTANT_THING"=>"repos/foo", "NOTE"=>"This thing is foobared", "PYTHON_ONLY_THING"=>"{}/test/{}.py"}
但我想让它更灵活,更易于维护。第一个“更好”的解决方案抛出了一个错误,这显然是因为slice对“\0”本身进行操作,而不是对匹配项进行操作,而且我不确定它是否会多次匹配。当前未注释的解决方案不起作用,因为第二部分似乎一次只处理一个字母,而不是像我预期的那样处理每个匹配,所以它只是删除了大括号中的内容。更糟糕的是,它删除了PYTHON_ONLY_东西中外部大括号之间的所有内容,这是不好的

我想如果这能奏效的话,我需要同时修改我的正则表达式和Ruby代码,但我不确定在哪里可以找到更多的帮助。或者gsub不是这个工作的合适工具。有什么想法吗

我在Linux x86_64上使用Ruby 2.3.7。

与初始哈希一起使用以替换:

my_config.map do |k, v|
  [
    k,
    v.gsub(/(?<={)[^}]+(?=})/, my_config).gsub(/{(?!})|(?<!{)}/, '')
  ]
end.to_h
#⇒ {"KEY1"=>"foo",
#   "KEY2"=>"bar",
#   "URL"=>"https://bar.com/foo",
#   "IMPORTANT_THING"=>"repos/foo",
#   "NOTE"=>"This thing is foobared",
#   "PYTHON_ONLY_THING"=>"{}/test/{}.py"}
与替换的初始哈希一起使用:

my_config.map do |k, v|
  [
    k,
    v.gsub(/(?<={)[^}]+(?=})/, my_config).gsub(/{(?!})|(?<!{)}/, '')
  ]
end.to_h
#⇒ {"KEY1"=>"foo",
#   "KEY2"=>"bar",
#   "URL"=>"https://bar.com/foo",
#   "IMPORTANT_THING"=>"repos/foo",
#   "NOTE"=>"This thing is foobared",
#   "PYTHON_ONLY_THING"=>"{}/test/{}.py"}

下面是一个可能的解决方案:

my_config = {"KEY1"=>"foo",
             "KEY2"=>"bar",
             "URL"=>"https://{KEY2}.com/{KEY1}",
             "IMPORTANT_THING"=>"repos/{KEY1}",
             "NOTE"=>"This thing is {KEY1}{KEY2}ed",
             "PYTHON_ONLY_THING"=>"{}/test/{}.py"}

my_config.each_key do |key|
  placeholders = my_config[key].scan(/{([^}]+)}/).flatten
  placeholders.each do |placeholder|
    my_config[key].gsub!("{#{placeholder}}", my_config[placeholder]) if my_config.keys.include?(placeholder)
  end
end

puts my_config
  • 通过使用
    scan
    ,这将替换所有匹配项,而不仅仅是第一个匹配项
  • 在正则表达式中使用
    [[^}]+
    ,而不是
    *
    ,意味着您不会在匹配的这一部分“吞咽”太多。例如,如果输入包含
    “{FOO}bar{BAZ}”
    ,那么您希望该模式只捕获
    FOO
    BAZ
    ,而不是
    FOO}bar{BAZ
  • 将扫描结果分组,然后调用
    展平
    ,这是一种简单的方法,可以拒绝捕获组之外的内容,即在本例中是
    {
    }
    字符。(这只会使代码比使用
    切片(1,-2)
    之类的索引稍微不那么神秘!)
  • my_config.keys.include?(占位符)
    检查这是否是一个已知值,因此您不会将其替换为
    nil

    • 这里有一个可能的解决方案:

      my_config = {"KEY1"=>"foo",
                   "KEY2"=>"bar",
                   "URL"=>"https://{KEY2}.com/{KEY1}",
                   "IMPORTANT_THING"=>"repos/{KEY1}",
                   "NOTE"=>"This thing is {KEY1}{KEY2}ed",
                   "PYTHON_ONLY_THING"=>"{}/test/{}.py"}
      
      my_config.each_key do |key|
        placeholders = my_config[key].scan(/{([^}]+)}/).flatten
        placeholders.each do |placeholder|
          my_config[key].gsub!("{#{placeholder}}", my_config[placeholder]) if my_config.keys.include?(placeholder)
        end
      end
      
      puts my_config
      
      • 通过使用
        scan
        ,这将替换所有匹配项,而不仅仅是第一个匹配项
      • 在正则表达式中使用
        [[^}]+
        ,而不是
        *
        ,意味着您不会在匹配的这一部分“吞咽”太多。例如,如果输入包含
        “{FOO}bar{BAZ}”
        ,那么您希望该模式只捕获
        FOO
        BAZ
        ,而不是
        FOO}bar{BAZ
      • 将扫描结果分组,然后调用
        展平
        ,这是一种简单的方法,可以拒绝捕获组之外的内容,即在本例中是
        {
        }
        字符。(这只会使代码比使用
        切片(1,-2)
        之类的索引稍微不那么神秘!)
      • my_config.keys.include?(占位符)
        检查这是否是一个已知值,因此您不会将其替换为
        nil


      切片的第二个参数是长度。如果是负整数,它不总是返回一个
      nil
      吗?这是无效的JSON@Sinstein啊,好消息!我看错了文档。显然我是Ruby的初学者。@engineersmnky修复了。我当场编造的,它没有被使用,所以不太重要。:)
      slice
      的第二个参数是length。如果是负整数,它不总是返回一个
      nil
      吗?这是无效的JSON@Sinstein啊,好消息!我看错了文档。显然我是Ruby的初学者。:@engineersmnky修复了。我当场编造的,没有用过,所以不太重要。:)接受了hash as replacements:)是的,没错……但是这个方法有一个稍微不那么混乱的正则表达式:)它也慢了很多,因为这需要对字符串进行N*M次传递,而不仅仅是N次。谢谢你!我非常感谢你的透彻解释和额外的错误检查。我很快就理解了它,它按原样处理了我的测试代码。accep是的,没错……但是这个方法有一个稍微不那么混乱的正则表达式:)它也慢了很多,因为这需要对字符串进行N*M次传递,而不仅仅是N次。谢谢你!我非常感谢你的透彻解释和额外的错误检查。我很快就理解了它,并且它按原样处理了我的测试代码。我最喜欢第二个答案!(但正如OP提到的ruby 2.3,他们可能无法充分利用这一点,正如您所说)非常好!我更喜欢将哈希转换为gsub的方法。不过,我有一个问题。为了与我的代码一起工作,我制作了map block
      my_config=my_config.map…(snip)…end.to_h
      。这是您所期望的吗?它看起来有点混乱。通过map进行复制比使用每个_键然后使用gsub!进行就地修改更可取还是更常见(就像我以前做的那样)?谢谢!事实上,
      to_h
      是有意的,对不起。实际上,相当于
      Hash#transform_values
      的方法已经存在很长时间了:
      h.merge(h){| |,value,| |……}
      。我最喜欢第二个答案!(但正如OP提到的ruby 2.3,他们可能无法充分利用这一点,正如你所说的那样)非常好!我更喜欢转换散列以传递给gsub的方法。不过,我有一个问题。为了使用我的代码,我制作了映射块
      my_config=my_config.map…(snip)…end.to_h
      。这是您所期望的吗?它看起来有点混乱。通过map进行复制比使用每个_键然后使用gsub!进行就地修改更可取还是更常见(就像我以前做的那样)?谢谢!事实上,
      to_h
      是有意的,对不起。实际上,相当于
      Hash#transform_values
      的方法已经存在很长时间了:
      h.merge(h){| |,value,| |……}