基于哈希中key的值对哈希数组进行排序?

时间:2015-02-13 19:00:42

标签: ruby vagrant

我正在尝试与Vagrant合作,在旋转Docker容器时执行一些自动化。 Vagrantfiles本质上是Ruby,因此我应该能够应用Ruby逻辑来协助解决这个问题。

我正在读取一个conf.d目录,该目录中填充了包含配置数据的YAML文件,然后将配置项的哈希值推送到数组中。完成后,我使用.each通过数组,并根据哈希中的一些键的值将配置应用于数组中的每个条目。其中一个键是“链接”。 link的值将与另一个键“name”的值相关联。

我基本上需要确保link => 'name'的散列位于使用name => 'value'的散列之前的数组中。

输入和预期输出的示例:

输入

containers = [{"name"=>"foo", "ports"=>["80:80", "443:443"], "links"=>["bar", "baz"]}, {"name"=>"bar", "ports"=>["8888:8888"]}, {"name"=>"baz","ports"=>"80:80"}]

预期输出

containers = [{"name"=>"bar", "ports"=>["8888:8888"]}, {"name"=>"baz", "ports"=>"80:80"}, {"name"=>"foo", "ports"=>["80:80", "443:443"], "links"=>["bar", "baz"]}]

最终结果是任何带有“link”的条目出现在数组中与哈希的名称键匹配的条目之后。 (基本上是基于链接密钥的依赖顺序。)

请注意,链接容器可能会链接到另一个链接容器。

我有点困惑,因为我对我需要做的事情有所了解,但缺乏技术性的印章来实际弄清楚“怎么样?” :)

提前感谢您的任何帮助。

3 个答案:

答案 0 :(得分:1)

对我而言,最简单的事情就是:

linkless_configs = []
linked_configs = []
if config_hash.has_key?("links")
  linked_configs.push(config_hash)
else
  linkless_configs.push(config_hash)
end

然后你可以通过linkless_configs + linked_configs进行迭代,并保证每个链接的配置都在相应的无链接配置之后。

或者,如果你必须排序,你可以

containers.sort_by { |config| config.has_key?("links") ? 1 : 0 }

答案 1 :(得分:1)

[编辑: @DavidGrayson指出了我的答案存在缺陷。我会看看能不能找到解决办法,但如果我不能解决,我担心可能会出现这种情况,我会删除答案。 [编辑#2 :哦,我的!在我最初的编辑后,有人对我的回答进行了评价。我不确定我现在可以删除它,但说实话,我已经决定不这样做,主要是因为我的解释对任何提出的解决OP问题的方法都有影响。在余额中有10分,现在更加引人注目。 2#潮]

我相信我理解这个问题。 sort需要一个总订单,这是对每对元素a <= ba <= b的部分顺序。 ref后者不是问题,但部分订单要求是。偏序必须满足以下公理:

  • 反身性(x ≤ x),
  • 反对称(如果x ≤ yy ≤ x然后x = y)和
  • 及物性(如果x ≤ yy ≤ z,则为x ≤ z)。

我的订购只满足反身性公理。大卫给出反例:

containers = [h0, h1, h2]

,其中

h0 = {'name'=>'foo', 'links'=>['bar']},
h1 = {'name'=>'a'},
h2 = {'name'=>'bar'},

containers.sort
  #=> [{"name"=>"foo", "links"=>["bar"]},
  #    {"name"=>"a"}, {"name"=>"bar"}]

我的方法Hash#<=>确定:

h0 = h1
h0 > h2
h1 = h2

如果sort找到h0 = h1 = h2,则会通过传递性来推断h0 = h2(而不是检查h0 <=> h2),这可能会导致错误的结果。

David还指出o.follows?(self)应该引发异常,因为我已将其定义为private。由于我还没有遇到异常,我得出的结论是该声明尚未执行,但我没有追查其原因,但这是一个小问题(尽管无疑是一个有用的线索)。

我很感谢大卫找出问题所在。当然,需要公开不正确的答案,但我觉得我也学到了一些有用的东西。

<强>潮]

如果我正确理解了这个问题,并且数据提供了有效的排序,我认为你可以按照以下方式进行。

class Hash
  def <=>(o)
    case
    when   follows?(o)    then  1
    when o.follows?(self) then -1
    else                        0
    end
  end

  private

  def follows?(o)
    key?("links") && self["links"].include?(o["name"])
  end
end

containers = [{"name"=>"foo", "ports"=>["80:80", "443:443"],
               "links"=>["bar", "baz"]},
              {"name"=>"bar", "ports"=>["8888:8888"]},
              {"name"=>"baz","ports"=>"80:80"}]

containers.sort
  #=> [{"name"=>"baz", "ports"=>"80:80"},
  #    {"name"=>"bar", "ports"=>["8888:8888"]},
  #    {"name"=>"foo", "ports"=>["80:80", "443:443"],
  #     "links"=>["bar", "baz"]}] 

<强>附录

虽然我假设数据提供了有效的排序,但@ Ajedi32询问当存在循环引用时会发生什么。我们来看看:

containers = [{"name"=>"foo", "links"=>["bar"]},
              {"name"=>"bar", "links"=>["baz"]},
              {"name"=>"baz", "links"=>["foo"]}]
containers.sort
  #=> [{ "name"=>"baz", "links"=>["foo"] },
  #    { "name"=>"bar", "links"=>["baz"] },
  #    { "name"=>"foo", "links"=>["bar"] }]

containers = [{"name"=>"foo", "links"=>["bar"]},
              {"name"=>"bar", "links"=>["foo"]}]
containers.sort
  #=> [{ "name"=>"bar", "links"=>["foo"] },
  #    { "name"=>"foo", "links"=>["bar"] }]

这表明如果不确定没有循环引用,则应在排序之前检查它。

答案 2 :(得分:1)

这应该适合你:

def order_containers(containers)
  unordered = containers.dup
  ordered = []
  names_from_ordered = {}
  name_is_ordered = names_from_ordered.method(:[])
  until unordered.empty?
    container = unordered.find do |c|
      c.fetch('links', []).all? &name_is_ordered
    end
    raise 'container ordering impossible' if !container
    ordered << container
    unordered.delete(container)
    names_from_ordered[container.fetch('name')] = true
  end
  ordered
end

containers = [
  { 'name'=>'foo', 'links'=>['bar'] },
  { 'name'=>'a', 'links'=>['goo'] },
  { 'name'=>'bar' },
  { 'name'=>'goo', 'links'=>['foo'] },
]

containers = order_containers(containers)

require 'pp'
pp containers
# => [{"name"=>"bar"},
#     {"name"=>"foo", "links"=>["bar"]},
#     {"name"=>"goo", "links"=>["foo"]},
#     {"name"=>"a", "links"=>["goo"]}]

基本思想是我们使用循环,循环的每次迭代都会从输入列表中找到一个适合添加到输出列表的容器。如果容器所依赖的所有容器都已添加到输出列表中,则容器适合添加到输出列表中。然后从输入列表中删除容器并将其添加到输出列表中。

此循环可以以两种主要方式终止:

  1. 当输入列表为空时,表示成功,或
  2. 当我们找不到我们能够启动的容器时,这将是由循环依赖引起的错误。