【问题标题】:Rails sync ranges inside of arrayRails 同步数组内的范围
【发布时间】:2021-10-15 09:40:59
【问题描述】:

目前我在 Ruby on Rails 中同步时间段时遇到问题。

我需要比较 2 个从无到 n 哈希的数组,然后在“结果”数组中有同步时间段。

举个例子:

time_periods_a = [
  { start_date: '01/10/2021', end_date: '31/10/2021',
    additional_attribute: 5 },
  { start_date: '01/11/2021', end_date: '30/11/2021',
    additional_attribute: 10 }
]

time_periods_b = [
  { start_date: '01/10/2021', end_date: '31/12/2021',
    additional_attribute: 20 }

结果应该是:

[
  { start_date: '01/10/2021', end_date: '31/10/2021',
    additional_attribute_a: 5, additional_attribute_b: 20 },
  { start_date: '01/11/2021', end_date: '30/11/2021',
    additional_attribute_a: 10, additional_attribute_b: 20 },
  { start_date: '01/12/2021', end_date: '31/12/2021',
    additional_attribute_a: 0, additional_attribute_b: 20 }
]

也许你对此有一个简单的解决方案,我已经绝望了 2 天。

编辑:

由于我没有很好地解释这个问题,这里是一个编辑:

这两个数组可能包含重叠的哈希值。通常,这些必须分成几个时期。

也许是另一个例子,有点短但更复杂。

time_periods_a: [1 - 2, 3 - 6, 7 - 9] time_periods_b: [ 2-5, 6-9]

这需要导致:[1-1, 2-2, 3-5, 6-6, 7-9]

这个数字只是月份索引,因为 start_date 每次都是月初,end_date 是月底。

不需要两个数组包含相同的句点,但这是可能的。 这就是我添加附加属性的原因。 如果一个时间段包含在 1 个数组中但没有包含在另一个数组中,那么我需要添加这个时间段,只需要一个 additional_attribute: 0

编辑我之前使用的 #2 代码

  def format_date_ranges(example_model)
    first_array = example_model.first_array
    second_array = example_model.second_array
    return first_array.map { |hash| hash.merge('additional_attribute_b' => hash['additional_attribute'], 'additional_attribute_b' => 0) } if second_array.blank?
    return first_array.map { |hash| hash.merge('additional_attribute_a' => hash['additional_attribute'], 'additional_attribute_b' => 0) } if second_array.blank?

    iterate_count = [first_array.size, second_array.size].max
    result_array = []
    iterate_count.times do |index|
      relevant_first_hash = first_hash[index]
      releveant_second_hash = second_hash[index]
      if relevant_first_hash.nil?
        result_array << result_hash(0,
                                    releveant_second_hash['additional_attribute'],
                                    releveant_second_hash['start_date'],
                                    releveant_second_hash['end_date'],
                                    '')
        next
      end

      if relevant_second_hash.nil?
        result_array << result_hash(relevant_first_hash['additional_attribute'],
                                    0,
                                    relevant_first_hash['start_date'],
                                    relevant_first_hash['end_date'],
                                    relevant_first_hash['interval'])
        next
      end

      first_start_date = relevant_first_hash['start_date'].to_date
      first_end_date = relevant_first_hash['end_date'].to_date
      second_start_date = relevant_second_hash['start_date'].to_date
      second_end_date = relevant_second_hash['end_date'].to_date

      if first_start_date == second_start_date
        start_date = second_start_date
      else
        start_date = [first_start_date, second_start_date].min
        end_date = [first_start_date, second_start_date].max - 1.day
        result_array << result_hash(relevant_first_hash['additional_attribute'],
                                    relevant_second_hash['additional_attribute'],
                                    start_date,
                                    end_date,
                                    relevant_first_hash['interval'])
        start_date = [first_start_date, second_start_date].max
      end
      if first_end_date == second_end_date
        end_date = second_end_date
      else
        end_date = [first_end_date, second_end_date].min
        result_array << result_hash(relevant_first_hash['additional_attribute'],
                                    relevant_second_hash['additional_attribute'],
                                    start_date,
                                    end_date,
                                    relevant_first_hash['interval'])
        start_date = end_date + 1.day
        end_date = [first_end_date, second_end_date].max
      end
      result_array << result_hash(relevant_first_hash['additional_attribute'],
                                  relevant_second_hash['additional_attribute'],
                                  start_date,
                                  end_date,
                                  relevant_first_hash['interval'])
    end
    result_array
  end

  def result_hash(additional_attribute_a, additional_attribute_b, start_date, end_date, interval)
    { additional_attribute_a: additional_attribute_a, additional_attribute_b: additional_attribute_b, start_date: start_date, end_date: end_date, interval: interval }
  end

【问题讨论】:

  • “同步时间段”是什么意思?因为你可以通过简单的time_periods_a + time_periods_b 得到你想要的结果(假设你在time_periods_b 的定义中有错字,start_date 应该是01/12/2021)。但我怀疑明显的拼写错误实际上是您的问题所在,并且您有一个更复杂的要求,您没有解释。
  • @JohnP 也许我没有像预期的那样解释这个问题。 start_date 中没有错字。这两个数组可能包含重叠的哈希。通常,这些必须分成几个时期。也许是另一个例子,有点短但更复杂。 time_periods_a:[1 - 2, 3 - 6, 7 - 9]time_periods_b:[ 2-5, 6-9]这需要导致:[1-1, 2-2, 3-5, 6-6, 7-9]这个数字只是月份索引,因为start_date每次都在月初,end_date在月底。也许现在更清楚了。
  • 那么,正如我所问的,“同步时间段”是什么意思?我真的不知道你想在这里解决什么问题。 (特别是在其中一个日期范围内肯定存在问题,因为它指的是结束日期31/11/2021,它不存在。)
  • @JohnP 编辑了评论。
  • 不需要两个数组包含相同的句点,但这是可能的。这就是我添加附加属性的原因。如果一个时间段包含在 1 个数组中但没有包含在另一个数组中,那么我需要添加这个时间段,只需要一个 additional_attribute: 0

标签: ruby-on-rails ruby-on-rails-6


【解决方案1】:

我认为除了重新考虑您的数据结构之外,没有任何办法可以解决问题的复杂性,您可能会考虑这样做。我已尝试解释以下大部分步骤,但您可能必须运行代码并添加一些 puts 语句才能获得透彻的理解。

我已将问题概括为具有任意数量的哈希数组的问题,这实际上简化了代码。

给定数据和概括

a = [
  { start_date: '01/10/2021', end_date: '31/10/2021',
    additional_attribute: 5 },
  { start_date: '01/11/2021', end_date: '30/11/2021',
    additional_attribute: 10 }
]

b = [
  { start_date: '01/10/2021', end_date: '31/12/2021',
    additional_attribute: 20 }
]

array = [a, b]
  #=> [[{:start_date=>"01/10/2021", :end_date=>"31/10/2021",
  #      :additional_attribute=>5},
  #     {:start_date=>"01/11/2021", :end_date=>"30/11/2021",
  #      :additional_attribute=>10}],
  #    [{:start_date=>"01/10/2021", :end_date=>"31/12/2021",
  #      :additional_attribute=>20}]]   

创建两个辅助方法

def date_str_to_date(date_str)
  DateTime.strptime(date_str, '%d/%m/%Y')
end
def date_to_date_str(date_time)
    date_time.strftime('%d/%m/%Y')
end

例如:

date_str_to_date('01/10/2021')
  #=> #<DateTime: 2021-10-01T00:00:00+00:00 ((2459489j,0s,0n),+0s,2299161j)>
date_to_date_str(date_str_to_date('31/12/2021'))
  #=> "31/12/2021"

DateTime::strptime

​将array转换为哈希数组

arr =
  array.each_with_index.with_object([]) do |(ar,i),a2|
    ar.each do |h|
      a2 << h.merge(start_date: date_str_to_date(h[:start_date]),
                    end_date: date_str_to_date(h[:end_date]), idx: i)
    end
  end
  #=> [{:start_date=>#<DateTime: 2021-10-01T00:00:00+00:00 ((2459489j,0s,0n),+0s,2299161j)>,
  #     :end_date=>#<DateTime: 2021-10-31T00:00:00+00:00 ((2459519j,0s,0n),+0s,2299161j)>,
  #     :additional_attribute=>5, :idx=>0},
  #    {:start_date=>#<DateTime: 2021-11-01T00:00:00+00:00 ((2459520j,0s,0n),+0s,2299161j)>,
  #     :end_date=>#<DateTime: 2021-11-30T00:00:00+00:00 ((2459549j,0s,0n),+0s,2299161j)>,
  #     :additional_attribute=>10, :idx=>0},
  #    {:start_date=>#<DateTime: 2021-10-01T00:00:00+00:00 ((2459489j,0s,0n),+0s,2299161j)>,
  #     :end_date=>#<DateTime: 2021-12-31T00:00:00+00:00 ((2459580j,0s,0n),+0s,2299161j)>,
  #     :additional_attribute=>20, :idx=>1}]

请注意,ab 中的每个哈希都映射到 arr 中的哈希,来自 a 的哈希映射为 :idx =&gt; 0,来自 b 的哈希映射为 :idx =&gt; 1

计算最早开始日期和最晚结束日期

start, finish = arr.flat_map { |h| [h[:start_date], h[:end_date]] }.minmax
  #=> [#<DateTime: 2021-10-01T00:00:00+00:00 ((2459489j,0s,0n),+0s,2299161j)>,
  #    #<DateTime: 2021-12-31T00:00:00+00:00 ((2459580j,0s,0n),+0s,2299161j)>]

所以

start
  #=> #<DateTime: 2021-10-01T00:00:00+00:00 ((2459489j,0s,0n),+0s,2299161j)>
finish
  #=> #<DateTime: 2021-12-31T00:00:00+00:00 ((2459580j,0s,0n),+0s,2299161j)>

请参阅 Enumerable#flat_mapArray#minmax

对于startfinish 之间的每个日期,构造一组arr 元素的索引,其范围包括给定日期

require 'set'
coverage_by_date = (start..finish).map do |date|
  [date,
   arr.each_index.select do |i|
    (arr[i][:start_date]..arr[i][:end_date]).cover?(date)
   end.to_set
  ]
end
  #=> [[#<DateTime: 2021-10-01T00:00:00+00:00 ((2459489j,0s,0n),+0s,2299161j)>, #<Set: {0, 2}>],
  #    [#<DateTime: 2021-10-02T00:00:00+00:00 ((2459490j,0s,0n),+0s,2299161j)>, #<Set: {0, 2}>],
  #    ...
  #    [#<DateTime: 2021-10-31T00:00:00+00:00 ((2459519j,0s,0n),+0s,2299161j)>, #<Set: {0, 2}>],
  #    [#<DateTime: 2021-11-01T00:00:00+00:00 ((2459520j,0s,0n),+0s,2299161j)>, #<Set: {1, 2}>],
  #    ...
  #    [#<DateTime: 2021-11-30T00:00:00+00:00 ((2459549j,0s,0n),+0s,2299161j)>, #<Set: {1, 2}>],
  #    [#<DateTime: 2021-12-01T00:00:00+00:00 ((2459550j,0s,0n),+0s,2299161j)>, #<Set: {2}>],
  #    ...
  #    [#<DateTime: 2021-12-31T00:00:00+00:00 ((2459580j,0s,0n),+0s,2299161j)>, #<Set: {2}>]]

最后,删除具有空覆盖集的日期,并将生成的日期数组分割成具有相等覆盖集的范围,然后映射到所需的哈希

coverage_by_date.reject { |_,set| set.empty? }
                .slice_when { |(_,set1),(_,set2)| set1 != set2 }
                .map do |ar|
                   attributes = ar.first
                                  .last
                                  .each_with_object(Hash.new(0)) do |i,h|
                                     g = arr[i] 
                                     h[g[:idx]] = g[:additional_attribute]
                                   end.values_at(*0..array.size-1)
                   { start_date: date_to_date_str(ar.first.first),
                     end_date: date_to_date_str(ar.last.first), 
                     attributes: attributes }                       
                 end
  #=> [{:start_date=>"01/10/2021", :end_date=>"31/10/2021", :attributes=>[5, 20]},
  #    {:start_date=>"01/11/2021", :end_date=>"30/11/2021", :attributes=>[10, 20]},
  #    {:start_date=>"01/12/2021", :end_date=>"31/12/2021", :attributes=>[0, 20]}]

当(例如)

ar.first.last
  #=> #<Set: {0, 2}>

我们发现

attributes
  #=> [5, 20]

注意:

coverage_by_date.reject { |_,set| set.empty? }
                .slice_when { |(_,set1),(_,set2)| set1 != set2 }
                .map do |ar|
                   { start_date: date_to_date_str(ar.first.first),
                     end_date: date_to_date_str(ar.last.first),
                     set: ar.first.last }
                 end
  #=> [{:start_date=>"01/10/2021", :end_date=>"31/10/2021", :set=>#<Set: {0, 2}>},
  #    {:start_date=>"01/11/2021", :end_date=>"30/11/2021", :set=>#<Set: {1, 2}>},
  #    {:start_date=>"01/12/2021", :end_date=>"31/12/2021", :set=>#<Set: {2}>}]

参见Enumerable#slice_whenHash::new 的形式,它接受一个参数(默认值)并且没有块,以及Hash#values_at

【讨论】:

  • 这正是我想要的!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-22
相关资源
最近更新 更多