import type { Fn , Fn2 , ICompare , IContains , ICopy , IEquiv } from "@thi.ng/api" ; import { DEFAULT_EPS } from "@thi.ng/api/api" ; import { isString } from "@thi.ng/checks/is-string" ; import { and , or } from "@thi.ng/dlogic" ; import { illegalArgs } from "@thi.ng/errors/illegal-arguments" ; export enum Classifier { DISJOINT_LEFT , DISJOINT_RIGHT , EQUIV , SUBSET , SUPERSET , OVERLAP_LEFT , OVERLAP_RIGHT } export class Interval implements ICompare < Interval > , IContains < number > , ICopy < Interval > , IEquiv { l : number ; r : number ; lopen : boolean ; ropen : boolean ; constructor ( l , r , lopen = false , ropen = false ) { ( l > r || ( l === r && lopen !== ropen ) ) && illegalArgs ( `invalid interval: ${ $toString ( l , r , lopen , ropen ) }` ) ; this . l = l ; this . r = r ; this . lopen = lopen ; this . ropen = ropen ; } get size ( ) : number { return isEmpty ( this ) ? 0 : this . r - this . l ; } copy ( ) { return new Interval ( this . l , this . r , this . lopen , this . ropen ) ; } param i compare ( i ) { return compare ( this , i ) ; } equiv ( i ) { return ( this === i || ( i instanceof Interval && this . l === i . l && this . r === i . r && this . lopen === i . lopen && this . ropen === i . ropen ) ) ; } param x contains ( x ) { return contains ( this , x ) ; } toString ( ) { return $toString ( this . l , this . r , this . lopen , this . ropen ) ; } toJSON ( ) { return this . toString ( ) ; } } const BRACES = "()[]" ; const RE_INF = /^([-+])?(inf(inity)?|\u221e)$/i ; export function interval ( spec ) ; export function interval ( l , r , lopen ? , ropen ? ) ; export function interval ( l , r ? , lopen ? , ropen ? ) { return isString ( l ) ? parse ( l ) : new Interval ( l , r ! , lopen , ropen ) ; } param src export const parse = ( src ) => { let l , r , c1 , c2 ; const n = src . length - 1 ; const comma = src . indexOf ( "," ) > 0 ; const dot = src . indexOf ( ".." ) > 0 ; if ( n < ( dot ? 3 : 2 ) ) illegalArgs ( src ) ; c1 = src . charAt ( 0 ) ; c2 = src . charAt ( n ) ; if ( BRACES . indexOf ( c1 ) < 0 || BRACES . indexOf ( c2 ) < 0 || ! ( comma || dot ) ) { illegalArgs ( src ) ; } [ l , r ] = src . substring ( 1 , n ) . split ( dot ? ".." : "," ) . map ( ( x , i ) => { x = x . trim ( ) ; const inf = RE_INF . exec ( x ) ; const n = ( x === "" && i === 0 ) || ( inf && inf [ 1 ] === "-" ) ? - Infinity : ( x === "" && i > 0 ) || ( inf && inf [ 1 ] !== "-" ) ? Infinity : parseFloat ( x ) ; isNaN ( n ) && illegalArgs ( `expected number: '${ x }'` ) ; return n ; } ) ; r === undefined && ( r = Infinity ) ; return new Interval ( l , r , c1 === "(" || c1 === "]" , c2 === ")" || c2 === "[" ) ; } ; /**
 * Returns new infinite interval `[-∞..∞]`
 */ export const infinity = ( ) => new Interval ( - Infinity , Infinity ) ; param min param open export const withMin = ( min , open = false ) => new Interval ( min , Infinity , open , false ) ; param max param open export const withMax = ( max , open = false ) => new Interval ( - Infinity , max , false , open ) ; param min param max export const open = ( min , max ) => new Interval ( min , max , true , true ) ; param min param max export const openClosed = ( min , max ) => new Interval ( min , max , true , false ) ; param min param max export const closedOpen = ( min , max ) => new Interval ( min , max , false , true ) ; param min param max export const closed = ( min , max ) => new Interval ( min , max , false , false ) ; param i param step export const values = ( i , step ) => samples ( i , Math . floor ( i . size / step + 1 ) ) ; example param i param n export function * samples ( i , n ) { const delta = n > 1 ? ( i . r - i . l ) / ( n - 1 ) : 0 ; for ( let x = 0 ; x < n ; x ++ ) { const y = i . l + delta * x ; if ( contains ( i , y ) ) yield y ; } } param i export const isEmpty = ( i ) => i . l >= i . r ; param i param x export const isBefore = ( i , x ) => x instanceof Interval ? i . ropen || x . lopen ? i . r <= x . l : i . r < x . l : i . ropen ? i . r <= x : i . r < x ; param i param x export const isAfter = ( i , x ) => x instanceof Interval ? i . lopen || x . ropen ? i . l >= x . r : i . l > x . r : i . ropen ? i . l >= x : i . l > x ; param a param b export const compare = ( a , b ) => { if ( a === b ) return 0 ; let c ; return a . l < b . l ? - 1 : a . l > b . l ? 1 : a . r < b . r ? - 1 : a . r > b . r ? 1 : ( c = ~ ~ a . lopen - ~ ~ b . lopen ) === 0 ? ~ ~ b . ropen - ~ ~ a . ropen : c ; } ; param i param x export const contains = ( i , x ) => ( i . lopen ? x > i . l : x >= i . l ) && ( i . ropen ? x < i . r : x <= i . r ) ; export const centroid = ( i ) => ( i . l + i . r ) / 2 ; param i param x export const include = ( i , x ) => isAfter ( i , x ) ? new Interval ( x , i . r , false , i . ropen ) : isBefore ( i , x ) ? new Interval ( i . l , x , i . lopen , false ) : i . copy ( ) ; param a param b export const distance = ( a , b ) => overlaps ( a , b ) ? 0 : a . l < b . l ? b . l - a . r : a . l - b . r ; /**
 * Applies given `fn` to both sides of interval `i` and returns a new
 *  Interval of transformed end points.
 *
 *  param i param fn export const transform = ( i , fn ) => new Interval ( fn ( i . l ) , fn ( i . r ) , i . lopen , i . ropen ) ; param a param b export const classify = ( a , b ) => a . equiv ( b ) ? Classifier . EQUIV : isBefore ( a , b ) ? Classifier . DISJOINT_LEFT : isAfter ( a , b ) ? Classifier . DISJOINT_RIGHT : contains ( a , b . l ) ? contains ( a , b . r ) ? Classifier . SUPERSET : Classifier . OVERLAP_RIGHT : contains ( a , b . r ) ? Classifier . OVERLAP_LEFT : Classifier . SUBSET ; param a param b export const overlaps = ( a , b ) => classify ( a , b ) >= Classifier . EQUIV ; param a param b export const union = ( a , b ) => { if ( isEmpty ( a ) ) return b ; if ( isEmpty ( b ) ) return a ; const [ l , lo ] = $min ( a . l , b . l , a . lopen , b . lopen , and ) ; const [ r , ro ] = $max ( a . r , b . r , a . ropen , b . ropen , and ) ; return new Interval ( l , r , lo , ro ) ; } ; param a param b export const intersection = ( a , b ) => { if ( overlaps ( a , b ) ) { const [ l , lo ] = $max ( a . l , b . l , a . lopen , b . lopen , or ) ; const [ r , ro ] = $min ( a . r , b . r , a . ropen , b . ropen , or ) ; return new Interval ( l , r , lo , ro ) ; } } ; export const prefix = ( a , b ) => { if ( overlaps ( a , b ) ) { const [ l , lo ] = $min ( a . l , b . l , a . lopen , b . lopen , or ) ; const [ r , ro ] = $min ( a . r , b . l , a . ropen , b . lopen , or ) ; return new Interval ( l , r , lo , ro ) ; } } ; export const suffix = ( a , b ) => { if ( overlaps ( a , b ) ) { const [ l , lo ] = $max ( a . l , b . r , a . lopen , b . ropen , or ) ; const [ r , ro ] = $max ( a . r , b . r , a . ropen , b . ropen , or ) ; return new Interval ( l , r , lo , ro ) ; } } ; param i param x param eps export const min = ( i , x , eps = DEFAULT_EPS ) => i . ropen ? ( x >= i . r ? i . r - eps : x ) : x > i . r ? i . r : x ; param i param x param eps export const max = ( i , x , eps = DEFAULT_EPS ) => i . lopen ? ( x <= i . l ? i . l + eps : x ) : x < i . l ? i . l : x ; /**
 * Clamps `x` to interval `i`, using  Interval . min and
 *  Interval . max .
 *
 *  param i param x param eps export const clamp = ( i , x , eps = DEFAULT_EPS ) => min ( i , max ( i , x , eps ) , eps ) ; internal const $min = ( a , b , ao , bo , op ) => $minmax ( a < b , a , b , ao , bo , op ) ; internal const $max = ( a , b , ao , bo , op ) => $minmax ( a > b , a , b , ao , bo , op ) ; internal const $minmax = ( test , a , b , ao , bo , op ) : [ number , boolean ] => ( test ? [ a , ao ] : a === b ? [ a , op ( ao , bo ) ] : [ b , bo ] ) ; internal const $toString = ( l , r , lopen , ropen ) => `${ lopen ? "(" : "[" }${ l } .. ${ r }${ ropen ? ")" : "]" }` ;