Как определить пики и впадины в массиве с плавающей точкой?

#arrays #ruby

#массивы #ruby

Вопрос:

У меня есть массив,

 array = [
  0.43, 
  0.64, # => peak
  0.2, 
 -0.05, 
 -0.15, # => trough
  0.2, # => peak
 -0.1, 
 -0.5, # => trough
 -0.3
]
  

который имеет два пика и два впадины в данных. Эти пики и впадины не обязательно являются минимальными и максимальными значениями массива. Как мне определить их с помощью программы?

Идеальным результатом было бы:

 peak_indexes = [1, 5]
trough_indexes = [4, 7]
  

Комментарии:

1. Что делает -0.5 отрицательным пиком?

2. @ElChapo поскольку данные перед ним ближе к 0, чем -0,5, это

3. Итак, в этом случае каждый конечный элемент был бы пиком / впадиной?. Пик, если элемент перед ним ближе к 0, впадина, если элемент перед ним дальше от 0.

4. Это разумное замечание. Я бы сказал, исключите последний элемент, тогда, как я полагаю, невозможно определить, является ли это истинным пиком или нет

5. Что, если пик / впадина охватывает несколько элементов, например [0.1, 0.6, 0.6, 0.2] ?

Ответ №1:

each_cons(3) извлекает смежные три члена, необходимые для проверки среднего члена, исключая триплеты с первым или последним элементом array в середине.

with_index(1) учитывается тот факт, что был пропущен триплет с первым элементом array в середине, таким образом, нумерация индексов начинается с 1.

Вы не определили, что вы подразумеваете под пиками и впадинами. Если вы хотите использовать локальные значения max и min, то сработает следующее.

 array.each_cons(3).with_index(1).select{|a, i| a.max == a[1]}.map(amp;:last)
# => [1, 5]

array.each_cons(3).with_index(1).select{|a, i| a.min == a[1]}.map(amp;:last)
# => [4, 7]
  

Или, если вы имеете в виду то, что Стефан объясняет в комментарии к моему ответу, то сработает следующее:

 array
.each_cons(3)
.with_index(1)
.select{|(a1, a2, a3), i| a1 < a2 amp;amp; a2 > a3}
.map(amp;:last)
# => [1, 5]

array
.each_cons(3)
.with_index(1)
.select{|(a1, a2, a3), i| a1 > a2 amp;amp; a2 < a3}
.map(amp;:last)
# => [4, 7]
  

Комментарии:

1. a.max == a[1] / a.min == a[1] выдает ложноположительные результаты, если имеется несколько идентичных записей, например [0.1, 0.2, 0.2, 0.3] . Вместо этого вам следует проверить, действительно ли средний элемент больше (не больше или равен) его соседей, например, через |(a, b, c), i| a < b amp;amp; b > c }

2. Хотелось бы, чтобы в Ruby были цепочки сравнений, как в Python. a < b > c выглядело бы так круто.

3. @Stefan Я думаю, что это усложнило бы синтаксис. Это выражение следовало бы понимать как единую конструкцию, а не как последовательное применение двух методов. Т.е. a < b> c не должно было бы означать (a < b) > c . Некоторые математические выражения не подходят для программирования. Есть еще один пример $a, b in A$ , который, возможно, можно было бы спутать с множественным присвоением, если бы он был импортирован в Ruby.

4. (a < b) > c в любом случае не имеет особого смысла, потому что a < b возвращает либо true из false , ни один из которых не отвечает на > . К сожалению, 1 < 2 == true будет оцениваться по-другому, так что это может привести к нарушению существующего кода. Помимо этого, я думаю, что это очень подходит для программирования.

Ответ №2:

Сначала давайте определим, что означает, что что-то является пиком или впадиной.

  • Пик — это значение, которое больше, чем значение слева и справа от него.
  • Впадина — это значение, которое меньше значения слева и справа от него.

Это позволит нам определить две полезные функции:

 def is_peak?(left_value, value, right_value)
  return value > left_value amp;amp; value > right_value
end

def is_trough?(left_value, value, right_value)
  return value < left_value amp;amp; value < right_value
end
  

Теперь мы можем просмотреть каждый элемент в массиве и использовать эти функции, чтобы спросить, является ли элемент пиком или впадиной.

 array = [0.43, 0.64, 0.2, -0.05, -0.15, 0.2, -0.1, -0.5]
positive_peak_indexes = []
negative_peak_indexes = []
# Loop through the array
array.each_with_index do |elem, i|
  # Make sure we don't get an out of bounds exception
  next if (i-1 < 0) || (i   1 >= array.length)

  # Now we add to our appropriate arrays if it's a peak/trough
  positive_peak_indexes << i if is_peak?(array[i-1], elem, array[i 1])
  negative_peak_indexes << i if is_trough?(array[i-1], elem, array[i 1])
end
puts "positive_peak_indexes = #{positive_peak_indexes.to_s}"
puts "negative_peak_indexes = #{negative_peak_indexes.to_s}"
  

Ответ №3:

У меня не было времени протестировать на большем количестве случаев, так что, возможно, есть какая-то ошибка.

В любом случае, основная идея заключается в том, чтобы заархивировать каждый элемент с его индексом (Перечислимый#each_with_index), затем использовать метод Enumerable#chunk_while для нарезки массива при внесении изменений. Наконец, извлеките из блоков крайние значения.

Лучше использовать фрагмент кода, ary представляющий собой массив данных.

Сначала фрагменты:

 chunks = ary.map.with_index{ |x, i| [x,i] }.chunk_while { |x,y| y.first < x.first }
  

Затем соберите крайние значения:

 peaks = chunks.collect { |e| e.first if e.size > 1 }.compact #=> [[0.64, 1], [0.2, 5]]
trough = chunks.collect { |e| e.last if e.size > 1 }.compact #=> [[-0.15, 4], [-0.5, 7]]