Поиск локальных минут в массиве

Есть ли простой способ определить локальные минимумы и максимумы массива значений. Например

Element Value   Note
1         1 
2         3 
3         5 
4         6 
5         7       max
5         5 
6         4       min
7         6 
8         9 
9         10      max
10        8 
11        7 
12        5      min
13        10    

поэтому массив, который определяется как:

let arr = [|1;3;5;6;7;5;4;6;9;10;8;7;5;10|]

определил бы

mins  = [|4;5|]

и

maxs  = [|7;10|]

Это может быть список или последовательность, а также массив. Два вопроса

  1. Есть ли какие-либо средства в F #, которые поддаются этой задаче?
  2. Существует ли общий алгоритм для определения минимума или максимума или того и другого?
  3. Если писать с нуля, следует ли подходить к этому функционально или императивно?

Спасибо


person akaphenom    schedule 06.10.2010    source источник
comment
Вы хотите разделить массив на строго возрастающие прогоны?   -  person SLaks    schedule 06.10.2010
comment
Как именно вы определяете локальный максимум и минимум?   -  person casablanca    schedule 06.10.2010
comment
@SLaks Я думаю, он хочет найти все числа i, для которых 'a[i-1] › a[i] ‹ a[i+1]', ​​и назвать это минутами. Чего я не понимаю, так это в чем проблема. Просто перебирайте числа и сравнивайте.   -  person Nikita Rybak    schedule 06.10.2010
comment
Тупой вопрос, наверное, почему mins = [|1;3|]?   -  person Onorio Catenacci    schedule 06.10.2010
comment
нанесите точки, соедините их линией и осмотрите долины, а самые низкие точки в каждой долине - это мин. Я работаю со сглаженными котировками акций и исследую рыночные тайминги с помощью отдельных сборщиков акций. Эти минуты представляют собой оптимальные точки входа для длинных пиков.   -  person akaphenom    schedule 07.10.2010
comment
@akaphenom - ах, спасибо за объяснение - подумал, что я что-то упускаю.   -  person Onorio Catenacci    schedule 07.10.2010


Ответы (6)


Похоже, это работа для... Seq.windowed! ‹супергеройская музыка>

let arr = [|1;3;5;6;7;5;4;6;9;10;8;7;5;10|] 

let _,mins,maxs = 
    arr |> Seq.windowed 3 |> Seq.fold (fun (i,mins,maxs) [|a;b;c|] -> 
    if a>b&&b<c then   (i+1, i::mins,    maxs)
    elif a<b&&b>c then (i+1,    mins, i::maxs)
    else               (i+1,    mins,    maxs)) (1,[],[])

arr |> Seq.iteri (fun i x -> printfn "%2d: %2d" i x)
printfn "mins %A" mins
printfn "maxs %A" maxs
(*
 0:  1
 1:  3
 2:  5
 3:  6
 4:  7
 5:  5
 6:  4
 7:  6
 8:  9
 9: 10
10:  8
11:  7
12:  5
13: 10
mins [12; 6]
maxs [9; 4]
*)
person Brian    schedule 06.10.2010
comment
Мне нравится этот подход, мне не нужно беспокоиться о навигации по индексам массива и т. д., оконная функция интересна - очевидно, я не думал использовать ее в этой задаче, однако сегодня я использовал ее для расчета скользящих средних... - person akaphenom; 06.10.2010
comment
Мне так понравился этот ответ, что я сделал неэффективную (но более удобочитаемую) версию! - person Massif; 06.10.2010
comment
Я отслеживаю старые вопросы без ответов, проблема с этим решением заключается в том, что оно разваливается на повторяющиеся значения... - person akaphenom; 19.11.2010
comment
Я не понимаю, что вы говорите, можете ли вы показать тестовый пример, на котором он не работает? (возможно, если вы имеете в виду соседние одинаковые значения, просто измените, например, < на <= в ifs?) - person Brian; 19.11.2010

Основываясь на ответе Брайана, я немного предпочитаю эту версию:

let arr = [|1;3;5;6;7;5;4;6;9;10;8;7;5;10|] 

let find_min_max input = 
    let windows = Seq.windowed 3 input 
    let mins = Seq.filter (fun [|a;b;c|] -> a>b && b<c) windows
               |> Seq.map (fun [|a;b;c|] -> b)
    let maxs = Seq.filter (fun [|a;b;c|] -> a<b && b>c) windows
               |> Seq.map (fun [|a;b;c|] -> b)

    mins, maxs


let mins, maxs = find_min_max arr 

printfn "mins %A" mins
printfn "maxs %A" maxs
person Massif    schedule 06.10.2010

Основываясь на ответе Массифа, который основан на ответе Брайана, я бы, вероятно, объединил фильтр и карту в один:

let find_min_max input =  
    let windows = Seq.windowed 3 input  
    let mins = Seq.choose (fun [|a;b;c|] -> if a>b && b<c then Some(b) else None) windows 
    let maxs = Seq.choose (fun [|a;b;c|] -> if a<b && b>c then Some(b) else None) windows 

    mins, maxs 

:)

person Alexander Rautenberg    schedule 06.10.2010

Я думаю, что было бы тривиально написать

for x from 0 to size-2:
if (a[x] > a[x+1] && a[x+1] < a[x+2]) // also, there should be bound checking
    //a[x+1] is a min!
    minima.cram(x+1)
if (a[x] < a[x+1] && a[x+1] > a[x+2]) // also, there should be bound checking
    //a[x+1] is a max!
    maxima.cram(x+1)

Или я упростил?

person JoshD    schedule 06.10.2010
comment
Y - это просто, однако, поскольку я реализую на F #, я пытаюсь найти более функциональный подход. Что касается императивного подхода, то это... - person akaphenom; 06.10.2010

Если вы ищете функциональный пример, вы, вероятно, могли бы сделать (в Ruby)

def derivative_signs(list)
  (0..list.length-2).map { |i| (list[i+1] - list[i]) <=> 0 }
  ## <=> is the "rocketship" operator which is basically: be -1 if number is negative
  ##                                                      be 0 if number is zero
  ##                                                      be 1 if number is positive
  ## Alternatively, one could use x / x.abs, with an exception if x == 0
end

def derivative(list)
  (0..list.length-2).map { |i| list[i+1] - list[i] }
end

Исходя из исчисления, минимумы/максимумы - это когда первая производная меняет знак. Таким образом, можно просмотреть derivative_signs(arr) и найти все изменения знака.

first_derivative_signs = derivative_signs(arr)
# => [1, 1, 1, 1, -1, -1, 1, 1, 1, -1, -1, -1, 1]

В качестве альтернативы вы также можете сделать

second_derivative = derivative(derivative_signs(arr))

В списке, который вы предоставили, вы получите:

[0, 0, 0, -2, 0, 2, 0, 0, -2, 0, 0, 2]

Ясно видно, что значения со второй производной -2 являются максимальными, а значения со второй производной 2 — минимальными. Индекс второй производной равен индексу исходного списка + 1. Таким образом, second_derivative[4], то есть -2, соответствует arr[5] (7), что является максимумом.

Почему мы делаем «нормальную» производную во второй раз вместо производной_знака?

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

Например, рассмотрим

[1, 3, 6, 6, 7, 5, 4, 6, 9, 10, 8, 7, 5, 10]
# first_derivative_signs =>  [1, 1, 0, 1, -1, -1, 1, 1, 1, -1, -1, -1, 1]
# second_derivative_signs => [0, -1, 1, -1, 0, 1, 0, 0, -1, 0, 0, 1]
# second_derivative       => [0, -1, 1, -2, 0, 2, 0, 0, -2, 0, 0, 2]

Обратите внимание, что second_derivative_signs выдает нам несколько "ложных" минимумов/максимумов, в то время как second_derivative, когда мы проверяем только -2 и 2, является хорошим.

person Justin L.    schedule 06.10.2010

Я бы использовал Seq.pairwise для получения каждого числа и его предшественника. Затем вы можете вычислить разницу между каждым числом и его предшественником, чтобы получить разницу между всеми значениями.

На третьем этапе вы просматриваете различия и ищете изменения знаков. Когда знак меняется, вы знаете, что значение до изменения знака было экстремумом (либо минимальным, либо максимальным).

Я совсем не знаю F#, но первый шаг должен выглядеть так:

let diffs = Seq.pairwise arr |> Seq.map (-)
person Konrad Rudolph    schedule 06.10.2010