我有一个包含多个时间范围的数组:
[Tue, 24 May 2011 08:00:00 CEST +02:00..Tue, 24 May 2011 13:00:00 CEST +02:00,
Tue, 24 May 2011 16:30:00 CEST +02:00..Tue, 24 May 2011 18:00:00 CEST +02:00,
Tue, 24 May 2011 08:00:00 CEST +02:00..Tue, 24 May 2011 09:00:00 CEST +02:00,
Tue, 24 May 2011 15:30:00 CEST +02:00..Tue, 24 May 2011 18:00:00 CEST +02:00]
我希望将重叠时间范围组合在一起得到相同的数组,因此这种情况的输出将为:
[Tue, 24 May 2011 08:00:00 CEST +02:00..Tue, 24 May 2011 13:00:00 CEST +02:00,
Tue, 24 May 2011 15:30:00 CEST +02:00..Tue, 24 May 2011 18:00:00 CEST +02:00]
因此,它会在时间范围重叠时创建新的时间范围,依此类推。如果他们不重叠,将保持分离。另一个例子:
输入:
[Tue, 24 May 2011 08:00:00 CEST +02:00..Tue, 24 May 2011 13:00:00 CEST +02:00,
Tue, 24 May 2011 16:00:00 CEST +02:00..Tue, 24 May 2011 18:00:00 CEST +02:00]
输出(将是相同的,因为它们不重叠):
[Tue, 24 May 2011 08:00:00 CEST +02:00..Tue, 24 May 2011 13:00:00 CEST +02:00,
Tue, 24 May 2011 16:00:00 CEST +02:00..Tue, 24 May 2011 18:00:00 CEST +02:00]
我在想一些递归方法,但我需要一些指导...
答案 0 :(得分:30)
如果两个范围重叠,则给出一个返回 truthy 的函数:
def ranges_overlap?(a, b)
a.include?(b.begin) || b.include?(a.begin)
end
(此功能由sepp2k and steenslag提供)
和合并两个重叠范围的函数:
def merge_ranges(a, b)
[a.begin, b.begin].min..[a.end, b.end].max
end
然后在给定范围数组的情况下,此函数返回一个合并了任何重叠范围的新数组:
def merge_overlapping_ranges(overlapping_ranges)
overlapping_ranges.sort_by(&:begin).inject([]) do |ranges, range|
if !ranges.empty? && ranges_overlap?(ranges.last, range)
ranges[0...-1] + [merge_ranges(ranges.last, range)]
else
ranges + [range]
end
end
end
答案 1 :(得分:5)
稍微搜索一下,我找到了一个可以解决问题的代码:
def self.merge_ranges(ranges)
ranges = ranges.sort_by {|r| r.first }
*outages = ranges.shift
ranges.each do |r|
lastr = outages[-1]
if lastr.last >= r.first - 1
outages[-1] = lastr.first..[r.last, lastr.last].max
else
outages.push(r)
end
end
outages
end
样本(也使用时间范围!):
ranges = [1..5, 20..20, 4..11, 40..45, 39..50]
merge_ranges(ranges)
=> [1..11, 20..20, 39..50]
答案 2 :(得分:3)
您可以使用multi_range宝石来做到这一点。
示例1:
ranges = [
Time.parse('Tue, 24 May 2011 08:00:00 CEST +02:00..Tue')..Time.parse('24 May 2011 13:00:00 CEST +02:00'),
Time.parse('Tue, 24 May 2011 16:30:00 CEST +02:00..Tue')..Time.parse('24 May 2011 18:00:00 CEST +02:00'),
Time.parse('Tue, 24 May 2011 08:00:00 CEST +02:00..Tue')..Time.parse('24 May 2011 09:00:00 CEST +02:00'),
Time.parse('Tue, 24 May 2011 15:30:00 CEST +02:00..Tue')..Time.parse('24 May 2011 18:00:00 CEST +02:00'),
]
MultiRange.new(ranges).merge_overlaps.ranges
# => [2011-05-24 08:00:00 +0800..2011-05-24 13:00:00 +0800, 2011-05-24 15:30:00 +0800..2011-05-24 18:00:00 +0800]
示例2:
ranges = [
Time.parse('Tue, 24 May 2011 08:00:00 CEST +02:00')..Time.parse('Tue, 24 May 2011 13:00:00 CEST +02:00'),
Time.parse('Tue, 24 May 2011 16:00:00 CEST +02:00')..Time.parse('Tue, 24 May 2011 18:00:00 CEST +02:00'),
]
MultiRange.new(ranges).merge_overlaps.ranges
# => [2011-05-24 08:00:00 +0800..2011-05-24 13:00:00 +0800, 2011-05-24 16:00:00 +0800..2011-05-24 18:00:00 +0800]
答案 3 :(得分:2)
facet gem有Range.combine
方法可能有用:http://rdoc.info/github/rubyworks/facets/master/Range#combine-instance_method
答案 4 :(得分:1)
某种可能有用的算法:
Sort range array by start time (r1, r2, r3, r4, .. rn)
for each range pair [r1, r2], [r2, r3] .. [rn-1, rn]:
if r1_end > r2_start: # they overlap
add [r1_start, r2_end] to new range array
else: # they do not overlap
add [r1] and [r2] to new range array (no changes)
startover with the new range array until no more changes
答案 5 :(得分:1)
@ wayne-conrad提供的解决方案非常好。我为一个问题实现了它,我偶然发现了。然后我实现了一个迭代版本并对两者进行了基准测试。看来,迭代版本更快。注意:我使用public class ExampleActivity extends Activity{
private AudioManager mAudioManager;
private BroadcastReceiver mBroadcastReceiver;
@Override
public void onCreate(@Nullable Bundle savedInstanceState, @Nullable PersistableBundle persistentState)
{
super.onCreate(savedInstanceState, persistentState);
setContentView(R.layout.example_activity);
initActivity();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED);
registerReceiver(mBroadcastReceiver, intentFilter);
}
@Override
public void onDestroy()
{
super.onDestroy();
unregisterReceiver(mBroadcastReceiver);
}
private void initActivity()
{
initBroadcasrReceiver();
initAudioManager();
}
private void initBroadcasrReceiver()
{
mBroadcastReceiver = new BroadcastReceiver()
{
@Override
public void onReceive(Context context, Intent intent)
{
if (intent.getAction().equals(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED))
{
int scoAudioState = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, -1);
if(scoAudioState == AudioManager.SCO_AUDIO_STATE_CONNECTED)
{
// This method should be called only after SCO is connected!
mAudioManager.setBluetoothScoOn(true);
}
}
}
};
}
private void initAudioManager()
{
mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
resetSco();
mAudioManager.startBluetoothSco();
}
private void resetSco()
{
mAudioManager.setMode(AudioManager.MODE_NORMAL);
mAudioManager.stopBluetoothSco();
mAudioManager.setBluetoothScoOn(false);
mAudioManager.setSpeakerphoneOn(false);
mAudioManager.setWiredHeadsetOn(false);
}}
作为ActiveSupport
和时间助手,但实现纯Ruby版本是微不足道的。
Range#overlaps?
答案 6 :(得分:0)
你不想只想找到数组中最小的第一个值和最后一个值吗?
ranges = [Tue, 24 May 2011 08:00:00 CEST +02:00..Tue, 24 May 2011 13:00:00 CEST +02:00,
Tue, 24 May 2011 16:30:00 CEST +02:00..Tue, 24 May 2011 18:00:00 CEST +02:00,
Tue, 24 May 2011 08:00:00 CEST +02:00..Tue, 24 May 2011 09:00:00 CEST +02:00,
Tue, 24 May 2011 15:30:00 CEST +02:00..Tue, 24 May 2011 18:00:00 CEST +02:00]
union = [ranges.collect(&:first).sort.first, ranges.collect(&:last).sort.last]
答案 7 :(得分:0)
标记的答案很有效,除了少数用例。其中一个用例是
[Tue, 21 June 13:30:00 GMT +0:00..Tue, 21 June 15:30:00 GMT +00:00,
Tue, 21 June 14:30:00 GMT +0:00..Tue, 21 June 15:30:00 GMT +00:00]
ranges_overlap
中的条件无法处理此用例。所以我写了这个
def ranges_overlap?(a, b)
a.include?(b.begin) || b.include?(a.begin) || a.include?(b.end) || b.include?(a.end)|| (a.begin < b.begin && a.end >= b.end) || (a.begin >= b.begin && a.end < b.end)
end
到目前为止,这是为我处理所有边缘情况。
答案 8 :(得分:0)
gem range_operators通过向Ruby Range
类添加缺少的功能做得非常好。它比添加整个facets gem小。
我的情况是解决方案是rangify
方法,它被添加到Array
类中,并且可以完全按照您的要求进行操作。
答案 9 :(得分:0)
我对Wayne Conrad的答案进行了小幅更新,以处理与开放式数组有关的边缘情况(使用...运算符而不是..运算符创建)。
我将名称更改为merge_continuous_ranges
,因为尽管0...1
和1..2
之类的范围不重叠,但它们的合并范围是连续的,因此将它们合并是有意义的:
def merge_continuous_ranges(ranges)
ranges.sort_by(&:begin).inject([]) do |result, range|
if !result.empty? && ranges_continuous?(result.last, range)
result[0...-1] + [merge_ranges(result.last, range)]
else
result + [range]
end
end
end
def ranges_continuous?(a, b)
a.include?(b.begin) || b.include?(a.begin) || a.end == b.begin || b.end == a.begin
end
def merge_ranges(a, b)
range_begin = [a.begin, b.begin].min
range_end = [a.end, b.end].max
exclude_end = case a.end <=> b.end
when -1
b.exclude_end?
when 0
a.exclude_end? && b.exclude_end?
when 1
a.exclude_end?
end
exclude_end ? range_begin...range_end : range_begin..range_end
end