基于相邻元素比较的Ruby / Rails组阵

时间:2015-03-28 23:46:04

标签: ruby-on-rails ruby arrays

我有一个完整的对象阵列,我想通过将它们与周围元素进行比较来分组。

astart属性排序。

 a = [{ name: "joe",   start: "9am",  end: "10am" },
      { name: "joe",   start: "10am", end: "11am" },
      { name: "harry", start: "11am", end: "12pm" },
      { name: "harry", start: "12pm", end: "1pm"  },
      { name: "harry", start: "2pm",  end: "3pm"  },
      { name: "joe",   start: "3pm",  end: "4pm"  },
      { name: "joe",   start: "4pm",  end: "5pm"  }]

我想按name属性对相邻对象进行分组,但仅当startend次相同时才生成:

    a = [[{ name: "joe",   start: "9am",  end: "10am" }, { name: "joe",   start: "10am", end: "11am" }],
         [{ name: "harry", start: "11am", end: "12pm" }, { name: "harry", start: "12pm", end: "1pm"  }],
         [{ name: "harry", start: "2pm",  end: "3pm"  }],
         [{ name: "joe",   start: "3pm",  end: "4pm"  }, { name: "joe",   start: "4pm",  end: "5pm"  }]]

连续时间段没有最大值。


我可以按name对其进行分组,如果在此处相邻,则可以将其分组:Ruby / Rails Groups only Adjacent Array Elements

a.chunk { |hash| hash[:name] }.map(&:last)

但它似乎并不像我可以通过chunk获取元素索引来进行start end时间比较。


看起来答案就在这里:Grouping an array by comparing 2 adjacent elements

但是我在编写自己的功能时失败了。 (我很难理解slice_before的作用。)

def self.group_by(data)
  tmp = data.first
  data.slice_before do |item|
    tmp, prev = item, tmp
    item.application == prev.application &&
      item.start == prev.end
  end.to_a
  return data
end

任何帮助将不胜感激!

3 个答案:

答案 0 :(得分:2)

这是使用Enumerable#sort_byEnumerable#slice_when的单向方式。但它需要Ruby 2.2+。

require 'time' # for sorting times

a = [{ name: "joe",   start: "9am",  end: "10am" },
     { name: "joe",   start: "10am", end: "11am" },
     { name: "harry", start: "11am", end: "12pm" },
     { name: "harry", start: "12pm", end: "1pm"  },
     { name: "harry", start: "2pm",  end: "3pm"  },
     { name: "joe",   start: "3pm",  end: "4pm"  },
     { name: "joe",   start: "4pm",  end: "5pm"  }]

a.sort_by { |h| [ h[:name], Time.parse(h[:start]) ] }                     # 1
 .slice_when { |x, y| x[:end] != y[:start] || x[:name] != y[:name] }.to_a # 2

产生

=> [[{:name=>"harry", :start=>"11am", :end=>"12pm"}, {:name=>"harry", :start=>"12pm", :end=>"1pm"}],
    [{:name=>"harry", :start=>"2pm", :end=>"3pm"}],
    [{:name=>"joe", :start=>"9am", :end=>"10am"}, {:name=>"joe", :start=>"10am", :end=>"11am"}],
    [{:name=>"joe", :start=>"3pm", :end=>"4pm"}, {:name=>"joe", :start=>"4pm", :end=>"5pm"}]]

以下是对中间结果的逐步解释:

1)按名称对哈希值进行排序,然后按名称对时间进行排序。请注意使用Time.parse暂时将时间字符串转换为Time对象以进行正确排序:

 => [{:name=>"harry", :start=>"11am", :end=>"12pm"},
     {:name=>"harry", :start=>"12pm", :end=>"1pm"},
     {:name=>"harry", :start=>"2pm", :end=>"3pm"},
     {:name=>"joe", :start=>"9am", :end=>"10am"},
     {:name=>"joe", :start=>"10am", :end=>"11am"},
     {:name=>"joe", :start=>"3pm", :end=>"4pm"},
     {:name=>"joe", :start=>"4pm", :end=>"5pm"}]

2)现在,当前者的结束时间不等于后者的开始时间或名称不匹配使用Daniel Polfer的解决方案时,将此中间数组切片。这将返回Enumerator个对象,因此最终to_a方法调用:

=> [[{:name=>"harry", :start=>"11am", :end=>"12pm"}, {:name=>"harry", :start=>"12pm", :end=>"1pm"}],
    [{:name=>"harry", :start=>"2pm", :end=>"3pm"}],
    [{:name=>"joe", :start=>"9am", :end=>"10am"}, {:name=>"joe", :start=>"10am", :end=>"11am"}],
    [{:name=>"joe", :start=>"3pm", :end=>"4pm"}, {:name=>"joe", :start=>"4pm", :end=>"5pm"}]]

如果您的哈希值已经预先分类,那么Daniel Polfer的解决方案应该可以正常工作。但是,如果你有任何数据,其名称和/或开始时间是这样的:

b = [{:name=>"joe", :start=>"3pm", :end=>"4pm"},
     {:name=>"bill", :start=>"2pm", :end=>"3pm"},
     {:name=>"joe", :start=>"5pm", :end=>"6pm"},
     {:name=>"joe", :start=>"4pm", :end=>"5pm"}] 

只需使用slice_when返回

=> [[{:name=>"joe", :start=>"3pm", :end=>"4pm"}],
    [{:name=>"bill", :start=>"2pm", :end=>"3pm"}],
    [{:name=>"joe", :start=>"5pm", :end=>"6pm"}],
    [{:name=>"joe", :start=>"4pm", :end=>"5pm"}]]

而不是

=> [[{:name=>"bill", :start=>"2pm", :end=>"3pm"}],
    [{:name=>"joe", :start=>"3pm", :end=>"4pm"},
     {:name=>"joe", :start=>"4pm", :end=>"5pm"},
     {:name=>"joe", :start=>"5pm", :end=>"6pm"}]]

答案 1 :(得分:2)

详细,但按照您呈现的顺序给出结果,并使用旧版本的Ruby ...

a.inject([]) do |result,hash| 
  if (!result.empty? && 
      (result.last.last[:name] == hash[:name]) && 
      (result.last.last[:end] == hash[:start]))
    result.last << hash
  else
    result << [hash]
  end
  result
end

一次构建一个哈希值,选择将哈希值添加到最后一个数组的末尾或者&#34;步骤&#34;到一个新的最终数组条目。


Ruby 2.2 +

a.slice_when{|h1,h2| (h1[:name]!=h2[:name]) || (h1[:end]!=h2[:start])}.to_a

答案 2 :(得分:0)

使用slice_before:

y=a[0]
a.slice_before { |hash| x=y 
                        y = hash
                        x[:name] != y[:name] || x[:end] != y[:start]
                        }.to_a