Ruby-无法用另一个替换最后一个范围的元素

时间:2019-10-05 16:44:20

标签: ruby

所以我想合并重叠的范围,它应该如下所示: 输入:范围= [(1..2),(3..6),(5..8)] 输出:预期= [(1..2),(3..8)] 但是,当代码在时间间隔上迭代并转到else语句时,我只收到一条消息:“ function_merge.rb:9:in block in merge': undefined method end ='for 2..19:Range(NoMethodError)”

我试图将merged.last.end和interval.end保存为变量,在两行代码中重写了if语句(if interval.end> merged.last.end merged.last.end = interval.end end),但是所有无效的:-(

def merge(intervals)
  merged = []
  intervals.sort_by! { |interval| interval.begin }

  intervals.each do |interval|
    if merged.empty? || merged.last.end < interval.begin
      merged << interval
    else
      merged.last.end = interval.end if interval.end > merged.last.end
    end
  end
  return merged
end

我不明白为什么会收到此错误消息,因为“ end”是一种范围方法?我只想用interval.end数字“更新” merged.last.end。 如果您有任何解决方法的提示,那就很好了:-)

2 个答案:

答案 0 :(得分:0)

正如塞巴斯蒂安指出的那样,范围是不可变的。不必尝试更改范围,而必须创建一个新的范围。

def merge(intervals)
  merged = []
  intervals.sort_by! { |interval| interval.begin }

  intervals.each do |interval|
    if merged.empty? || merged.last.end < interval.begin
      merged << interval
    else
      merged[-1] = Range.new(merged.last.begin, interval.end, interval.exclude_end?)
    end
  end
  return merged
end

答案 1 :(得分:0)

已经解释了范围是不可变的。该问题意味着范围所涵盖的元素都是可比较的(例如,不是['a'..'z', 1..10])。我假设范围数组不包含有限范围和无限范围的组合。

解决方案

代码

def distill(arr)
  a = arr.reject { |r| r.exclude_end? ? (r.end <= r.begin) : r.end < r.begin }.
          sort_by(&:begin)
  return [] if a.empty?
  combined = []
  curr = a.shift
  loop do
    break (combined << curr) if a.empty?
    nxt = a.shift
    if nxt.begin > curr.end
       combined << curr
       curr = nxt
    else
       last = [curr, nxt].max_by { |r| [r.end, r.exclude_end? ? 0 : 1] }
       curr = last.exclude_end? ? (curr.begin...last.end) :
         curr.begin..last.end
    end
  end
end

示例

distill [5..8, 7...9, 9..11, 1...4, 38..37] 
  #=> [1...4, 5..11]

distill [1.5...2.2, 2.2..3.0, 3.0...4.5, 4.7..5.3, 5.2..4.6] 
  #=> [1.5...4.5, 4.7..5.3]  

distill ['a'..'d', 'c'..'f', 'b'..'g']
  # 'a'..'g'

说明

请参见Range#exclude_end?

第一个示例的步骤如下。

arr = [5..8, 7...9, 9..11, 1...4, 38..37]

a = arr.reject { |r| r.exclude_end? ? (r.end <= r.begin) : r.end < r.begin }.
        sort_by(&:begin)
  #=> [1...4, 5..8, 7...9, 9..11] 
a.empty?
  #=> false, so do not return
combined = []
curr = a.shift
  #=> 1...4 
a #=> [5..8, 7...9, 9..11] 

通过用puts语句添加代码并显示结果,可以最好地解释循环中的计算。

loop do
  puts "a.empty? #=> true, so break #{combined + [curr]}" if a.empty?
  break (combined << curr) if a.empty?
  puts "a.empty? #=> false"
  nxt = a.shift
  puts "nxt=#{nxt}, a=#{a}"
  puts "nxt.begin=#{nxt.begin} > #{curr.end} = curr.end = #{nxt.begin > curr.end}"
  if nxt.begin > curr.end
    combined << curr
    puts "combined << #{curr} = #{combined}"
    curr = nxt
    puts "curr = nxt = #{curr}"
  else
    last = [curr, nxt].max_by { |r| [r.end, r.exclude_end? ? 0 : 1] }
    puts "last=#{last}, last.exclude_end?=#{last.exclude_end?}"
    curr = last.exclude_end? ? (curr.begin...last.end) :
      curr.begin..last.end
    puts "new value of curr=#{curr}"
  end
  puts
end

a.empty? #=> false
nxt=5..8, a=[7...9, 9..11]
nxt.begin=5 > 4 = curr.end = true
combined << 1...4 = [1...4]
curr = nxt = 5..8

a.empty? #=> false
nxt=7...9, a=[9..11]
nxt.begin=7 > 8 = curr.end = false
last=7...9, last.exclude_end?=true
new value of curr=5...9

a.empty? #=> false
nxt=9..11, a=[]
nxt.begin=9 > 9 = curr.end = false
last=9..11, last.exclude_end?=false
new value of curr=5..11

a.empty? #=> true, so break [1...4, 5..11]

有时可以很方便地返回一个空的(但有效的)范围,例如38..37;人们不应该认为空范围必然表示某些问题不正确。

替代解决方案

如果范围是有限的(如示例中所示),并且范围的组合大小没有太大,则可以编写以下内容。

代码

def distill(arr)
  arr.flat_map(&:to_a).
      uniq.
      sort.
      chunk_while { |x,y| y == x.next }.
      map { |a| a.first..a.last }
end

示例

distill [5..8, 7...9, 9..11, 1...4, 38..37] 
  #=> [1..3, 5..11]

distill ['a'..'d', 'c'..'f', 'b'..'g']
  # 'a'..'g'

说明

第一个示例的步骤如下。

arr = [5..8, 7...9, 9..11, 1...4, 38..37] 

a = arr.flat_map(&:to_a)
  #=> => [5, 6, 7, 8, 7, 8, 9, 10, 11, 1, 2, 3]  
b = a.uniq
  #=> [5, 6, 7, 8, 9, 10, 11, 1, 2, 3] 
c = b.sort
  #=> [1, 2, 3, 5, 6, 7, 8, 9, 10, 11] 
d = c.chunk_while { |x,y| y == x.next }
  #=> #<Enumerator: #<Enumerator::Generator:0x00005c2683af8dd0>:each> 
e = d.map { |a| a.first..a.last }
  #=> [1..3, 5..11] 

可以将枚举数d转换为数组,以查看它将生成的元素并传递给chunk_while的块:

d.to_a
  #=> [[1, 2, 3], [5, 6, 7, 8, 9, 10, 11]] 

请参见Enumerable#chunk_while。也可以使用Enumerable#slice_when