Home Reference Source

src/libs/polyfill.js

/*
 * Copyright (c) 2018 Isaac Phoenix ([email protected]).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import math from 'mathjs'
import {instanceOf} from './util';

const Complex = math.complex().constructor
const Matrix = math.matrix().constructor

/**
 * @ignore
 * check if value is complex number
 * @param {Object} value
 * @return {boolean}
 */
export function isComplex(value) {
  return value instanceof Complex
}

/**
 * @ignore
 * check if value is number or complex number
 * @param {Object} value
 * @return {boolean}
 */
export function isNumeric(value) {
  return (typeof value === 'number' || value instanceof Complex)
}

/**
 * @ignore
 * return intersection of s1 & s2
 * @param {Set} s1
 * @param {Set} s2
 * @return {Set}
 */
export function intersection(s1, s2) {
  return new Set([...s1].filter(x => s2.has(x)))
}

/**
 * @ignore
 * return union set of s1 & s2
 * @param {Set} s1
 * @param {Set} s2
 * @return {Set<any>}
 */
export function unionSet(s1, s2) {
  const s = [...s2].filter(x => !s1.has(x))
  const result = new Set(s1)
  s.forEach(x => result.add(x))
  return result
}

/**
 * @ignore
 * return symmetric difference of s1 & s2
 * @param {Set} s1
 * @param {Set} s2
 * @return {Set<*>}
 */
export function symmetricDifference(s1, s2) {
  const inset = intersection(s1, s2)
  const a = [...s1].filter(x => !inset.has(x))
  const b = [...s2].filter(x => !inset.has(x))
  return new Set([...a, ...b])
}

/**
 * @ignore
 * check if s1 is equal to s2
 * @param {Set} s1
 * @param {Set} s2
 * @return {boolean}
 */
export function setEqual(s1, s2) {
  return symmetricDifference(s1, s2).size === 0
}

/**
 * @ignore
 * check if `superset` is the super set of `s`
 * @param {Set} superset
 * @param {Set} s
 * @return {boolean}
 */
export function setIsSuperSet(superset, s) {
  const result = [...s].filter(x => !superset.has(x))
  return result.length === 0
}

/**
 * @ignore
 * @param {Set<*>} s1
 * @param {Set<*>} s2
 * @return {Set<*>}
 */
export function setDifference(s1, s2) {
  return new Set([...s1].filter(x => !s2.has(x)))
}

/**
 * @ignore
 * create a Set contains numbers in range from 0 to n
 * @param {number} n
 * @return {Set<number>}
 */
export function setFromRange(n) {
  const result = new Set()
  for (let i = 0; i < n; i++) {
    result.add(i)
  }
  return result
}

/**
 * @ignore
 * create an array filled by number in range, active like python does
 * @param {number} start
 * @param {number} end
 * @param {number} step
 * @return {number[]}
 */
export function arrayFromRange(start, end, step) {
  if (typeof end === 'undefined') {
    end = start
    start = 0
  }
  if (typeof step === 'undefined') {
    step = 1
  }
  const n = end - start
  const result = new Array(n)
  for (let i = 0; i < n; i += step) {
    result[i] = i + start
  }
  return result
}

/**
 * @ignore
 * return a random sample from `array` which length is `count`
 * @param {any[]} array
 * @param {number} count
 * @return {any[]}
 */
export function randomSample(array, count) {
  const result = []
  const {length} = array
  if (length >= count) {
    const copy = array.slice(0)
    while (result.length < count) {
      const idx = Math.floor(Math.random() * copy.length)
      result.push(copy[idx])
      copy.splice(idx, 1)
    }
  }
  return result
}

/**
 * @ignore
 * test if two array(a1, a2) are equal, support instance of classes in this library
 * @param {Array} a1
 * @param {Array} a2
 * @param {function} itemCompareFunc
 * @return {boolean}
 */
export function arrayEqual(a1, a2, itemCompareFunc) {
  if (a1 === a2) {
    return true
  }

  if (Array.isArray(a1) && Array.isArray(a2)) {
    const l1 = a1.length
    const l2 = a2.length
    if (l1 === l2) {
      for (let i = 0; i < l1; ++i) {
        const c = a1[i]
        const d = a2[i]
        let func = itemCompareFunc
        if (!func && c.__proto__.equal) {
          func = (x, y) => Reflect.apply(c.__proto__.equal, x, [y])
        }
        if (Array.isArray(c) && Array.isArray(d)) {
          func = arrayEqual
        }
        if (!func) {
          func = (x, y) => x === y
        }
        const f = func(c, d)
        if (!f) {
          return false
        }
      }
      return true
    }
  }
  return false
}

/**
 * @ignore
 * @function
 * reverse version of `forEach`
 * @param {function} callbackFunc
 */
Array.prototype.rforEach = function (callbackFunc) {
  if (typeof callbackFunc === 'function') {
    const count = this.length
    for (let i = count - 1; i >= 0; --i) {
      callbackFunc(this[i])
    }
  }
}

/**
 * @ignore
 * @function
 * reverse version of `map`
 * @param {function} callbackFunc
 * @return {any[]}
 */
Array.prototype.rmap = function (callbackFunc) {
  const result = []
  if (typeof callbackFunc === 'function') {
    const count = this.length
    for (let i = count - 1; i >= 0; --i) {
      result.push(callbackFunc(this[i]))
    }
  }
  return result
}

/**
 * @ignore
 * @function
 * return total exist count of `item` in array
 * @param {any} item
 */
Array.prototype.count = function (item) {
  let count = 0
  for (let i = 0; i < this.length; ++i) {
    if (this[i] === item) {
      ++count
    }
  }
  return count
}

/**
 * @ignore
 * remove all existance of `target` from array
 * @param {any} target
 */
Array.prototype.remove = function (target) {
  let idx = -1
  for (let i = 0; i < this.length; ++i) {
    if (arrayEqual(this[i], target)) {
      idx = i
      break
    }
  }
  if (idx !== -1) {
    this.splice(idx, 1)
  }
}

/**
 * @ignore
 * return all regular expression match count of `substring` in string
 * @param {string} substring
 * @return {number}
 */
String.prototype.count = function (substring) {
  const exp = new RegExp(substring, 'g')
  const result = this.match(exp)
  if (result) return result.length
  return 0
}

/**
 * return `length` of v, act like python
 * @param {any} v
 * @return {number}
 */
export function len(v) {
  if (typeof v === 'undefined' || v === null) {
    return 0
  }
  if (Array.isArray(v)) {
    return v.length
  }
  if (v instanceof Set) {
    return v.size
  }
  if (v instanceof Matrix) {
    return v.size()[0]
  }
  if (instanceOf(v, String)) {
    return v.length
  }
  if (typeof v.length !== 'undefined') {
    if (typeof v.length === 'function') {
      return v.length()
    } else {
      return v.length
    }
  }
  if (typeof v === 'object') {
    return Object.keys(v).length
  }
  return 0
}

/**
 * @ignore
 * parse string contains 1/0 into bit array
 * @param {string} str
 * @return {boolean[]}
 */
export function stringToBitArray(str) {
  if (Array.isArray(str)) {
    return str
  }
  const result = []
  if (instanceOf(str, String)) {
    for (let i = 0; i < str.length; ++i) {
      result.push(str.charAt(i) !== '0')
    }
  }
  return result
}

/**
 * @ignore
 * return dot product of two complex vector(a1, a2)
 * @param {Complex[]} a1
 * @param {Complex[]} a2
 * @return {Complex}
 */
export function complexVectorDot(a1, a2) {
  let real = 0
  let image = 0
  a1.forEach((c1, [i]) => {
    const c2 = a2.subset(math.index(i))
    const r1 = math.re(c1)
    const i1 = math.im(c1)
    const r2 = math.re(c2)
    const i2 = math.im(c2)
    real += r1 * r2 - (-i1 * i2)
    image += r1 * i2 - r2 * i1
  })
  return math.complex(real, image)
}

/**
 * @ignore
 * return n-length Array filled by item
 * @param {Function|any} item
 * @param {number} count
 * @return {Array}
 */
export function narray(item, count) {
  const result = []
  if (typeof item === 'function') {
    for (let i = 0; i < count; ++i) {
      result.push(item())
    }
  } else {
    for (let i = 0; i < count; ++i) {
      result.push(item)
    }
  }
  return result
}

/**
 * @ignore
 * product loop on two Arrays p1 & p2
 * @param {Array} p1
 * @param {Array} p2
 * @param {function} func
 */
export function productLoop(p1, p2, func) {
  for (let i = 0; i < p1.length; ++i) {
    for (let j = 0; j < p2.length; ++j) {
      const stop = func(p1[i], p2[j])
      if (stop) {
        return
      }
    }
  }
}

/**
 * @ignore
 * product loop on three Arrays p1 & p2 & p3
 * @param {Array} p1
 * @param {Array} p2
 * @param {Array} p3
 * @param {function} func
 */
export function productLoop3(p1, p2, p3, func) {
  for (let i = 0; i < p1.length; ++i) {
    for (let j = 0; j < p2.length; ++j) {
      for (let k = 0; k < p3.length; ++k) {
        const stop = func(p1[i], p2[j], p3[k])
        if (stop) {
          return
        }
      }
    }
  }
}

/**
 * @ignore
 * return (base ^ exp) % mod, it's fast and support big number
 * @param {number} base
 * @param {number}  exp
 * @param  {number} mod
 * @return {number}
 */
export function expmod(base, exp, mod) {
  if (exp === 0) return 1
  if (exp % 2 === 0) {
    return Math.pow(expmod(base, (exp / 2), mod), 2) % mod
  } else {
    return (base * expmod(base, (exp - 1), mod)) % mod
  }
}