用于查找局部最大值的Ruby程序未通过一次测试

时间:2017-05-12 21:08:14

标签: arrays ruby math

我有一个程序,用于在ruby中找到通过除一次测试之外的所有峰值(局部最大值)。就我个人而言,我认为我的程序还可以,但是我可能没有考虑到问题中未指定的假定邻域大小,但是其他人也曾尝试过它。

这是我到目前为止所拥有的。

def pick_peaks(arr)
  pos = []
  peaks =[]
  peak_set = {pos: [], peaks: []}

  for i in 1..arr.length-2
    if arr[i-1] < arr[i] && arr[i] >= arr[i+1] 
      unless edge_plateau?(arr, i)
      peak_set[:pos] << i
      peak_set[:peaks] << arr[i]
    end
  end
end
  peak_set_alt = peak_set.collect{|k,v| [k.to_s, v]}.to_h
  peak_set_alt
end

def edge_plateau?(array, position)
  edge_plateau_left = true
  edge_plateau_right = true
  i = 1
  until i == position
    edge_plateau_left = false if array[0] != array[i]
    i += 1
  end

  i = array.length-2
  until i == position
  edge_plateau_right = false if array[i] != array.last
  i -= 1
end
  edge_plateau_left or edge_plateau_right
end

这是它需要通过的测试,但我不知道原始数组,所以这是一个挑战。

Expected: {"pos"=>[2, 7, 14, 20], "peaks"=>[5, 6, 5, 5]}, instead got: {"pos"=>[2, 7, 11, 14, 20], "peaks"=>[5, 6, 3, 5, 5]}

我在中间得到一个额外的峰值,但如果它是局部最大值那应该没问题,对吗?

更新

感谢我的建议,我找到了测试数组

[1, 2, 5, 4, 3, 2, 3, 6, 4, 1, 2, 3, 3, 4, 5, 3, 2, 1, 2, 3, 5, 5, 4, 3]

2 个答案:

答案 0 :(得分:1)

这是一种更类似Ruby的方法来查找局部最大值。

<强>代码

def locale_maxima(arr)
  last_idx = arr.size - 1
  peaks, pos =
    ([[-Float::INFINITY, nil]] +
     arr.each_with_index.reject { |v,i| i < last_idx && v == arr[i+1] } +
     [[-Float::INFINITY, nil]]
    ).each_cons(3).
      select { |(n1,_), (n2,_), (n3,_)| n1 < n2 && n2 > n3 }.
      map { |_,max_pair,_| max_pair }.
      transpose
  { pos: pos, peaks: peaks }
end

示例

arr = [1, 2, 5, 4, 3, 2, 3, 6, 4, 1, 2, 3, 3, 4, 5, 3, 2, 1, 2, 3, 5, 5, 4, 3]
locale_maxima arr 
  #=> { :pos  =>[2, 7, 14, 21],
  #     :peaks=>[5, 6,  5,  5] } 

<强>解释

步骤如下。

  last_idx = arr.size - 1
    #=> 23

如果有连续的相等值,可能代表拐点(并发症),则删除除最后一个之外的所有值。要报告局部最大值的索引,我们需要在删除重复项之前保存索引。

  b = arr.each_with_index.reject { |v,i| i < last_idx && v == arr[i+1] }      
    #=> [[1, 0], [2, 1], [5, 2], [4, 3], [3, 4], [2, 5], [3, 6], [6, 7],
    #    [4, 8], [1, 9], [2, 10], [3, 12], [4, 13], [5, 14], [3, 15],
    #    [2, 16], [1, 17], [2, 18], [3, 19], [5, 21], [4, 22], [3, 23]]

请注意[3,11][5, 20]已被删除。

在开头和结尾处对不能成为局部最大值的对(nil是任意的)。

  c = [[-Float::INFINITY, nil]] + b + [[-Float::INFINITY, nil]] 
    #=> [[-Infinity, nil], [1, 0], [2, 1], [5, 2], [4, 3], [3, 4], [2, 5], [3, 6], 
    #    [6, 7], [4, 8], [1, 9], [2, 10], [3, 12], [4, 13], [5, 14], [3, 15], [2, 16],
    #    [1, 17], [2, 18], [3, 19], [5, 21], [4, 22], [3, 23], [-Infinity, nil]]

使用Enumerable#each_cons生成一个枚举器,用于识别局部最大值。

  d = c.each_cons(3)
    #=> #<Enumerator:
    #   [[-Infinity, nil], [1, 0], [2, 1], [5, 2], [4, 3], [3, 4], [2, 5], 
    #    [3, 6], [6, 7], [4, 8], [1, 9], [2, 10], [3, 12], [4, 13], [5, 14],
    #    [3, 15], [2, 16], [1, 17], [2, 18], [3, 19], [5, 21], [4, 22],
    #    [3, 23], [-Infinity, nil]]:each_cons(3)> 
  e = d.select { |(n1,_), (n2,_), (n3,_)| n1 < n2 && n2 > n3 }
    #=> [[[2, 1], [5, 2], [4, 3]],
    #    [[3, 6], [6, 7], [4, 8]],
    #    [[4, 13], [5, 14], [3, 15]],
    #    [[3, 19], [5, 21], [4, 22]]] 
  f = e.map { |_,max_pair,_| max_pair }
    #=> [[5, 2], [6, 7], [5, 14], [5, 21]]
  peaks, pos = f.transpose
    #=> [[5, 6, 5, 5], [2, 7, 14, 21]] 
  { pos: pos, peaks: peaks }
    #=> {:pos=>[2, 7, 14, 21], :peaks=>[5, 6, 5, 5]}

答案 1 :(得分:0)

您的代码中有多个错误。

尝试使用以下数据的代码

pick_peaks([0,1,10,1,2,2,3,1,10,1,0])

你会得到

  

{&#34; pos&#34; =&gt; [2,4,6,8],&#34;峰值&#34; =&gt; [10,2,3,10]}

显然2这是一个错误。所以这里的错误来源是arr[i] >= arr[i+1]

除非在任务中明确说明了shomehow,否则你似乎处理错误的边缘。考虑

pick_peaks([0,0,10,1,2,2,3,1,10,0,0])

你会得到

  

{&#34; pos&#34; =&gt; [4,6],&#34;峰值&#34; =&gt; [2,3]}

在左侧和右侧缺少10

没有确切的任务,很难肯定地说,但乍一看似乎你的算法在代码和Big-O方面都要复杂化。你为什么不穿过阵列,跟踪你是爬山,平原还是跌倒?

<强>更新

这是一段代码,用于说明我的上一个建议。我不擅长Ruby,所以我的示例代码将使用JavaScript,因此您可以在浏览器控制台中运行它:

function pick_peaks(arr) {
   var prevUp = true; //let the start be a peak
   var peaks = [];
   var lastUpInd = 0;

   for(var i = 0; i < arr.length-1; i++) {
      if (arr[i] < arr[i+1]) {
         prevUp = true;
         lastUpInd = i + 1;
      }
      else if (arr[i] > arr[i+1]) {
         if(prevUp) {
               for(var j = lastUpInd; j <= i; j++) {
                  peaks.push([j, arr[j]]);
             }
         }
         prevUp = false;
      }       
   }

   // additionally handle the end to let it be a peak   
   if(prevUp)
   {
        for(var j = lastUpInd; j <= i; j++) {
              peaks.push([j, arr[j]]);
        }
   }
   return peaks;
}

如果您不希望结束可能是峰值,只需使用prevUp初始化false,并在评论后删除内部if的最后for