// following https://dl.acm.org/doi/pdf/10.1145/7531.24036?casa_token=eU7GWug-Y98AAAAA%3A4GZQqc2mZBx14I_lLJyQrp6JLhxvxyn9pDaVCAisU2KozgOCW5HbSVYviPBxgN0RWf8GCUshDaW5 // 'Intersection of Convex Objects in Two and Three Dimensions' by Chazelle, and Dobkin enum Behavior { Increasing, Decreasing, Extremum, } // A real valued function f defined on // the integers 0, 1, . . . , n-1 is said to be unimodal if there exists an integer m such that // f is strictly increasing (respectively, decreasing) on [ 0, m] and // decreasing (respectively, increasing) on [m + 1, n-1] // No three sequential elements have the same value export class UnimodalSequence { f: (m: number) => number // int -> double // the sequence values get Sequence() { return this.f } set Sequence(value: (m: number) => number) { this.f = value } length: number // the length of the sequence: the sequence starts from 0 get Length(): number { return this.length } set Length(value: number) { this.length = value } constructor(sequenceDelegate: (m: number) => number, length: number) { this.f = sequenceDelegate this.length = length } FindMinimum(): number { // find out first that the minimum is inside of the domain let a = 0 let b: number = this.length - 1 let m: number = a + Math.floor((b - a) / 2) const valAtM: number = this.f(m) if (valAtM >= this.f(0) && valAtM >= this.f(this.length - 1)) return this.f(0) < this.f(this.length - 1) ? 0 : this.length - 1 while (b - a > 1) { m = a + Math.floor((b - a) / 2) switch (this.BehaviourAtIndex(m)) { case Behavior.Decreasing: a = m break case Behavior.Increasing: b = m break case Behavior.Extremum: return m } } return a === b ? a : this.f(a) <= this.f(b) ? a : b } private BehaviourAtIndex(m: number): Behavior { const seqAtM: number = this.f(m) if (m === 0) { const seqAt1: number = this.f(1) if (seqAt1 === seqAtM) { return Behavior.Extremum } return seqAt1 > seqAtM ? Behavior.Increasing : Behavior.Decreasing } if (m === this.length - 1) { const seqAt1: number = this.f(this.length - 2) if (seqAt1 === seqAtM) { return Behavior.Extremum } return seqAt1 > seqAtM ? Behavior.Decreasing : Behavior.Increasing } const delLeft: number = seqAtM - this.f(m - 1) const delRight: number = this.f(m + 1) - seqAtM if (delLeft * delRight <= 0) { return Behavior.Extremum } return delLeft > 0 ? Behavior.Increasing : Behavior.Decreasing } FindMaximum(): number { // find out first that the maximum is inside of the domain let a = 0 let b: number = this.length - 1 let m: number = a + Math.floor((b - a) / 2) const valAtM: number = this.f(m) if (valAtM <= this.f(0) && valAtM <= this.f(this.length - 1)) { return this.f(0) > this.f(this.length - 1) ? 0 : this.length - 1 } while (b - a > 1) { m = a + Math.floor((b - a) / 2) switch (this.BehaviourAtIndex(m)) { case Behavior.Decreasing: b = m break case Behavior.Increasing: a = m break case Behavior.Extremum: return m } } return a === b ? a : this.f(a) >= this.f(b) ? a : b } }