如何从其他间隔构建一个间隔数组

时间:2017-04-22 02:29:13

标签: ruby algorithm data-structures

给出以下数组间隔:

today = Time.current.beginning_of_day
tomorrow = Time.current.tomorrow.beginning_of_day

availabilities = {
  monday: [
    { start_time: today + 6.hours,
      end_of_time: today + 12.hours },
    { start_time: today + 8.hours,
      end_of_time: today + 18.hours }
  ],
  tuesday: [
    { start_time: tomorrow + 10.hours,
      end_time: tomorrow + 16.hours },
    { start_time: tomorrow + 18.hours,
      end_time: tomorrow + 23.hours }
  ]
}

如何以这样的方式构建availabilities数组,例如在mondaytuesday哈希的情况下:

# monday
{ start_time: 'Today at 06:00',
  end_time: 'Today at 18:00' }
# tuesday
[ { start_time: 'Tomorrow at 10:00',
    end_time: 'Tomorrow at 16:00' },
  { start_time: 'Tomorrow at 18:00',
    end_time: 'Tomorrow at 23:00' } ]

我想要实现的是获取给定日期的可用时间间隔,而不管哪个实体将提供该可用性。

在此先感谢,我们非常感谢您使用哪种算法的任何帮助或指导。

4 个答案:

答案 0 :(得分:2)

  1. 按开始时间对间隔进行排序
  2. 从第一个间隔开始,检查它是否与下一个间隔重叠。
  3. 如果是,则合并它们并重复该过程
  4. 如果不是,请继续下一个间隔。
  5. 证明它有效:

    如果区间A,B和C被排序,并且A和C重叠,则也意味着B肯定与A重叠。

    这是在Ruby中实现这一点的一种方法。

    def overlap?(r1, r2)
      !(r1.end <= r2.begin || r1.begin >= r2.end)
    end
    
    def merge_intervals(r1, r2)
      [r1.begin, r2.begin].min..[r1.end, r2.end].max
    end
    
    def flatten_intervals(intervals)
      first, *rest = intervals.sort_by(&:begin)
      rest.each_with_object([first]) { |r,stack| stack <<
        (overlap?(stack.last, r) ? merge_intervals(stack.pop, r) : r) }
    end
    
    intervals = [0..2, 5..8, 4..9, 11..13, 15..17, 19..21, 17..19, 16..20]
    flatten_intervals(intervals)
      #=> [0..2, 4..9, 11..13, 15..21]
    

答案 1 :(得分:1)

这是将时间间隔数组转换为非重叠时间间隔数组的简便方法。我假设粒度是一小时(但是将其更改为分钟或秒是很简单的)。为方便起见,我还将时间间隔表示为范围而不是问题中指定的哈希值(尽管将范围转换为哈希值很容易)。

假设

time_intervals = [0..2, 5..8, 4..9, 11..13, 15..17, 19..21, 17..19, 16..20]

我们可以按如下方式查看这些间隔:

00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23
xx xx xx       xx xx xx xx       xx xx xx    xx xx xx    xx xx xx
            xx xx xx xx xx xx                      xx xx xx 
                                                xx xx xx xx xx

我们希望如此结合这些:

00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23
xx xx xx    xx xx xx xx xx xx    xx xx xx    xx xx xx xx xx xx xx xx 

一种简单的方法如下。

h = 24.times.with_object({}) { |i,h| h[i] = :uncovered } 
time_intervals.each { |range|
  (range.begin..range.end).each { |i| h[i] = :covered } }
h.delete_if { |_,v| v == :uncovered }.
  keys.
  slice_when { |k1, k2| k2 - k1 > 1 }.
  map { |a| a.first..a.last }
  #=> [0..2, 4..9, 11..13, 15..21]

步骤如下。

h = 24.times.with_object({}) { |i,h| h[i] = :uncovered } 
  #=> {0=>:uncovered, 1=>:uncovered, 2=>:uncovered,..., 23=>:uncovered} 
time_intervals.each { |range|
  (range.begin..range.end).each { |i| h[i] = :covered } }
h #=> { all k=>:covered except k=>:uncovered for k = 3, 10, 14, 22 and 23 } 
g = h.delete_if { |_,v| v == :uncovered }
  #=> { all k=>:covered, k = 1,2, 4,5,6,7,8,9, 11,12,13, 15,16,17,18,19,20,21v]
k = g.keys
  #=> [0, 1, 2, 4, 5, 6, 7, 8, 9, 11, 12, 13, 15, 16, 17, 18, 19, 20, 21] 
e = k.slice_when { |k1, k2| k2 - k1 > 1 }
  #=> #<Enumerator: #<Enumerator::Generator:0x007fee0a05c7b0>:each> 

我们可以看到这个枚举器生成的元素如下:

e.entries
  #=> [[0, 1, 2], [4, 5, 6, 7, 8, 9], [11, 12, 13], [15, 16, 17, 18, 19, 20, 21]]

Enumerable#entries。也可以使用Enumerable#to_a

最后一步是将数组转换为范围。

e.map { |a| a.first..a.last }
  #=> [0..2, 4..9, 11..13, 15..21]

答案 2 :(得分:1)

合并重叠间隔的算法:

1. Sort the intervals on start time
2. Assign left and right of first interval (0)
3. Iterate over the intervals from 1 to size-1
    if (current interval start lies in prev. interval)
        update right to max(prev. right, current. right) 
    else
        [left, right] is non-overlapping interval => push it to answer array
        reassign left and right to current interval
4. push last [left, right] to answer array

解决方案:

# hash of overlapping intervals
availabilities = {
  monday: [
    { start_time: 6,
      end_time: 12 },
    { start_time: 8,
      end_time: 18 }
  ],
  tuesday: [
    { start_time: 10,
      end_time: 16 },
    { start_time: 18,
      end_time: 23 }
  ]
}

# function for converting hash to intervals, process, and then convert back to hash
def solve(list)
    return_hash = {}
    list.each do |key, arr|
        intervals = []
        arr.each { |hash| intervals << [hash[:start_time], hash[:end_time]] }
        non_overlapping_intervals = merge_interval(intervals)
        temp = []
        non_overlapping_intervals.each { |interval| temp << {start_time: interval[0], end_time: interval[1]} }
        return_hash[key] = temp
    end
    return_hash
end

# algorithm to merge intervals and return non-overlapping intervals
def merge_interval(v)
    intervals = []
    v.sort()
    size = v.size()
    l, r = v[0][0], v[0][1]
    (1...size).each do |i|
        if v[i][0] <= r
            r = [r,v[i][1]].max;
        else
            intervals << [l, r]
            l, r = v[i][0], v[i][1]
        end         
    end
    intervals << [l, r]
    return intervals
end

# solve call for availabilities hash
p solve(availabilities)

输出:

{
 :monday=>[{:start_time=>6, :end_time=>18}],
 :tuesday=>[{:start_time=>10, :end_time=>16},
            {:start_time=>18, :end_time=>23}]
}

答案 3 :(得分:0)

格式略有不同(Range而不是具有不一致键的哈希),这个问题变得更容易:

public class TheFragment extends Fragment {

    TextView tv;
    public TheFragment() {

    }
    String value = getArguments().getString("key");

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    /*    if(value!=null){
            tv.setText(value);
        } */

    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.fragment_this, container, false);
        tv = (TextView) getView().findViewById(R.id.thePrice);
        tv.setText(value);
        return view;
    }
}

然后,您可以使用range-operator gem对范围求和。