根据哈希中键的值对哈希数组进行排序



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

我正在阅读一个conf.d目录,该目录中充满了包含配置数据的YAML文件,然后将配置项的哈希推送到数组中。完成后,我将使用.each遍历数组,并根据散列中一些键的值将配置应用于数组中的每个条目。其中一个键是"链接"。链接的值将与另一个键"名称"的值相关联。

我本质上需要确保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"]}]

最终的结果是,任何带有"链接"的条目都会出现在数组中哈希名称关键字与其匹配的条目之后。(基本上是基于链接关键字的依赖排序。)

请注意,可能会出现链接容器链接到另一个链接容器的情况。

这让我有点困惑,因为我有自己需要做什么的想法,但缺乏真正弄清楚"如何?"的技术能力:)

提前感谢您的帮助。

这应该适用于您:

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. 当我们找不到可以启动的容器时,这将是由循环依赖引起的错误

在我看来,最简单的事情应该是:

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 }

[编辑:@DavidGrayson指出了我的答案中的一个缺陷。我会看看是否能找到解决方案,但如果不能,我担心可能会出现这种情况,我会删除答案。[编辑#2:哦,天哪!在我初次编辑后,有人对我的答案投了赞成票。我不确定我现在能不能删除它,但说实话,我已经决定不这么做了,主要是因为我的解释对OP问题的任何拟议解决方案都有影响。在平衡了10分的情况下,现在更具说服力了。2#tidE]

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

  • 自反性(x ≤ x
  • 反对称性(如果x ≤ yy ≤ x,则x = y)和
  • 传递性(如果是x ≤ yy ≤ z,则是x ≤ z

我的排序只满足自反性公理。David举了一个反例:

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。由于我还没有遇到异常,我得出结论,该语句尚未执行,但我还没有找到原因,但这只是一个小问题(尽管毫无疑问是一条有用的线索)。

我感谢大卫发现了这个问题。当然,错误的答案需要暴露出来,但我觉得我也学到了一些有用的东西。

tidE]

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

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"] }]

这表明,如果不确定是否存在循环引用,那么在排序之前应该检查一下。

最新更新