Home Reference Source

src/List.js

import { 
  isNotNull, 
  isNotUndefined, 
  isNull, 
  isUndefined, 
  isFunction, 
  isNotFunction, 
  isArray, 
  isNotArray 
  } from './helper/check';
import error from './errors/errors';

/**
 * List class, this class allow you to use a LinkedList DS based on an array
 * @example
 * let list = new List();
 * @access public
 * @author Jose Roberto Quevedo
 * @version 1.1.0
 * @since 0.1.0
 */
export default class List {
  /**
   * @constructor this is constructor of the class, here an empty array is initialize
   * @since 0.1.0
   * @access public
   */
  constructor() {
    /**
     * @member {Array} array this is a way for easy implementing list and for making the lib more quick
     * @access private
     * @since 0.1.0
     */
    this.array = [];
  }

  /**
   * @function builder
   * This method is an interface for the class to create a list from the operation aplided to the other;
   * @access private
   * @since 0.1.0
   * @param {Array} array
   */
  builder(array) {
    let list = new List();
    list.array = array;
    return list;
  } 

  /**
   * @function isEmpty
   * This method say if a list is empty or not.
   * @access public
   * @since 0.1.0
   * @return { List } true if the list is empty or false otherwaise
   * @example
   * let boolean = list.isEmpty();
   */
  isEmpty() {
    return this.array.length <= 0;
  }

  /**
   * @function size
   * this method say what is the size of the list
   * @access public
   * @since 0.1.0
   * @return {number} 0 if the list is empty or an positive integer representing the list size
   * @example
   * let boolean = list.isEmpty();
   */
  size() {
    return this.array.length;
  }

  /**
   * @function addFirst
   * This method allow you to add an element in the first position of the list 
   * @param {any} data the element than you whan to add to the list
   * @access public
   * @since 0.1.0
   * @return { List } a new instance of the class with the elements of the other list and the new element added
   * @throws {Error001} when shit go bad
   * @throws {Error100} when data is null
   * @throws {Error101} when data is undefined
   * @example 
   * let l = list.addFirst(1);
   */
  addFirst(data) {
    if(isNotUndefined(data) && isNotNull(data)) {
      return this.builder([data, ...this.array]);
    } else if (isNull(data)) {
      throw error["100"];
    } else if (isUndefined(data)) {
      throw error["101"];
    } else {
      throw error["001"];
    }
  }

  /**
   * @function addLast
   * This method allow you to add an element in the last position of the list 
   * @param {any} data the element than you whan to add to the list
   * @access public
   * @since 0.1.0
   * @return { List } a new instance of the class with the elements of the other list and the new element added
   * @throws {Error001} when shit go bad
   * @throws {Error100} when data is null
   * @throws {Error101} when data is undefined
   * @example 
   * let l = list.addLast(1);
   */
  addLast(data) {
    if(isNotUndefined(data) && isNotNull(data)) {
      return this.builder([...this.array, data]);
    } else if (isNull(data)) {
      throw error["100"];
    } else if (isUndefined(data)) {
      throw error["101"];
    } else {
      throw error["001"];
    }
  }

  /**
   * @function add
   * This method allow you to add an element in the position i of the list 
   * @param {any} data the element than you whan to add to the list
   * @param {number} i the index value in which to add the element [0, list.size())
   * @access public
   * @since 0.1.0
   * @return { List } a new instance of the class with the elements of the other list and the new element added
   * @throws {Error001} when shit go bad
   * @throws {Error100} when data is null
   * @throws {Error101} when data is undefined
   * @throws {Error110} when the index is null
   * @throws {Error111} when the index is undefined
   * @throws {Error112} when the index is Not a Number
   * @example 
   * let l = list.add(1, 0);
   */
  add(data, i) {
    if(!isNaN(i) && isNotUndefined([data, i]) && isNotNull([data, i])) {
      if (i === 0) {
        return this.addFirst(data);
      } else if (i === this.size() - 1) {
        return this.addLast(data);
      } else if (i < 0) {
        return this.add(data, this.size() + i);
      } else if (i >= this.size()) {
        throw error["113"];
      } else {
        let vec = [];
        for (let j = 0; j < this.array.length; j++) {
          if(i === j) {
            vec.push(data);
          }
          vec.push(this.array[j]);
        }
        return this.builder(vec);
      }
    } else if (isNull(data)) {
      throw error["100"];
    } else if (isUndefined(data)) {
      throw error["101"];
    } else if (isNull(i)) {
      throw error["110"];
    } else if (isUndefined(i)) {
      throw error["111"];
    } else  if (isNaN(i)) {
      throw error["112"];
    } else {
      throw error["001"];
    }
  }

  /**
   * @function addAll
   * This method allow you to add an collection of elements to the list 
   * @param {any[]} collection an Array of elements to add 
   * @access public
   * @since 0.1.0
   * @return { List } a new instance of the class with the elements of the other list and the new elements added
   * @throws {Error001} when shit go bad
   * @throws {Error102} when collection is null
   * @throws {Error103} when collection is undefined
   * @throws {Error104} when collection is not an array
   * @example 
   * let l = list.addAll([1,'a',3]);
   */
  addAll(collection) {
    if(isNotUndefined(collection) && isNotNull(collection) && isArray(collection)) {
      return this.builder([...this.array, ...collection]);
    } else if (isNotArray(collection)) {
      throw error["104"];
    } else if (isNull(collection)) {
      throw error["102"];
    } else if (isUndefined(collection)) {
      throw error["103"];
    } else {
      throw error["001"];
    }
  }

  /**
   * @function deleteFirst
   * This method allow you to delete the first element of the list  
   * @access public
   * @since 0.1.0
   * @return { List } null is the list is empty or a new instance of the class with the elements of the other list except from the first
   * @example 
   * let l = list.deleteFirst();
   */
  deleteFirst() {
    if(this.isEmpty()) {
      return null;
    } else {
      return this.builder(this.array.slice(1, this.array.length));
    }
  }

  /**
   * @function deleteLast
   * This method allow you to last the first element of the list  
   * @access public
   * @since 0.1.0
   * @return { List } null is the list is empty or a new instance of the class with the elements of the other list except from the first
   * @example 
   * let l = list.deleteLast();
   */
  deleteLast() {
    if(this.isEmpty()) {
      return null;
    } else {
      return this.builder(this.array.slice(0, this.array.length - 1));
    }
  }

  /**
   * @function delete
   * This method allow you to delete the element i from the list
   * @param {number} i the index of the element to eliminate [0, list.size())
   * @access public
   * @since 0.1.0
   * @return { List } null if the list is empty a new instance of the class with the elements of the other list except the element i
   * @throws {Error001} when shit go bad
   * @throws {Error110} when i is null
   * @throws {Error111} when i is undefined
   * @throws {Error112} when i is Not a Number
   * @throws {Error113} when i is bigger than the list size
   * @example 
   * let l = list.delete(0);
   */
  delete(i) {
    if(this.isEmpty()) {
      return null;
    } else {
      if(isNotUndefined(i) && isNotNull(i) && !isNaN(i)) {
        if(i === 0) {
          return this.deleteFirst();
        } else if (i === this.size() - 1) {
          return this.deleteLast();
        } else if(i < 0) {
          this.delete(this.size() + i);
        } else if (i >= this.size()) {
          throw error["113"];
        } else {
          let vec = [];
          for (let j = 0; j < this.array.length; j++) {
            if(i !== j) {
              vec.push(this.array[j]);
            }
          }
          return this.builder(vec); 
        }
      } else if (isNaN(i)) {
        throw error["112"];
      } else  if (isNull(i)) {
        throw error["110"];
      } else if (isUndefined(i)) {
        throw error["111"];
      } else {
        throw error["001"];
      }
    }
  }

  /**
   * @function clear
   * WARNING: This method delete all the element of the original list.
   * This should be used carefully
   * @access public
   * @since 0.1.0
   * @example
   * list.clear();
   */
  clear() {
    if(!this.isEmpty()) {
      console.warn("DS.js Warning: You're clearing the list all the data will be erased. You have 5s to cancel the operation");
      setTimeout(() => {
        console.log("DS.js Warning: The data is being erased...");
        this.array = [];
      }, 5000);
    } else {
      console.log("Your list doesn't have any data to delete");
    }
  }

  /**
   * @function forEach
   * This method allow us to iterate over the list
   * @param {function (value: any, i: number, vec: array)} callback this is a callback function that will be execute for every element in the list, it should not mutated the element of the list
   * @since 0.1.0
   * @access public
   * @throws {Error001} when shit go bad
   * @throws {Error200} when the list is empty
   * @throws {Error300} when callback is not a function 
   * @example
   * list.forEach((x,i,vec) => {
   *  console.log(`Value: ${x}, Index: ${i}`);
   * });
   */
  forEach(callback) {
    if(isFunction(callback) && !this.isEmpty()) {
      let vec = [...this.array];
      let list = this.builder(vec);
      for (let i = 0; i < vec.length; i++) {
        callback(vec[i], i, list);  
      } 
    } else if (this.isEmpty()) {
      throw error["200"];
    } else if (isNotFunction(callback)) {
      throw error["300"];
    } else {
      throw error["001"]
    }
  }

  /**
   * @function map
   * This method allow us to map over the list
   * @param {function (value: any, i: number, vec: array)} callback this is a callback function that will be execute for every element in the list, and it should mutated the element of the list
   * @since 0.1.0
   * @access public
   * @throws {Error001} when shit go bad
   * @throws {Error200} when the list is empty
   * @throws {Error300} when callback is not a function
   * @return { List } An instance of the class with the mutated elements 
   * @example 
   * let l = list.map((x,i,vec) => x * x);
   */
  map(callback) {
    if(!this.isEmpty() && isFunction(callback)) {
      let vec = [...this.array];
      let aux = [];
      for (let i = 0; i < vec.length; i++) {
        aux.push(callback(vec[i], i, vec));
        if(i === vec.length - 1) {
          return this.builder(aux);
        } 
      }
    } else if (this.isEmpty()) {
      throw error["200"];
    } else if (isNotFunction(callback)) {
      throw error["300"];
    } else {
      throw error["001"];
    }
  }

  /**
   * @function toString
   * This method convert the current list to and string, just like this: 1,2,3,4
   * @access public
   * @since 0.1.0
   * @return null if the list is empty or a string representig the list
   * @example
   * let string = list.string();
   */
  toString() {
    if(this.isEmpty()) {
      return null;
    } else {
      return [...this.array].join();
    }
  }

  /**
   * @function toJSON
   * This method parse the list to a JSON
   * @access public
   * @since 0.1.0
   * @return {object} null if the list is empty or an object representing the list
   * @example
   * let json = list.toJSON();
   */
  toJSON() {
    if(this.isEmpty()) {
      return null;
    } else {
      let json = {};
      for (let i = 0; i < this.array.length; i++) {
        json[i] = this.array[i];
      }
      return json;
    }
  }

  /**
   * @function toArray
   * this method convert the list into an array
   * @access public
   * @since 0.2.0
   * @return {Array<any>} return an array with the elements contained in the list or null if the list is empty
   * @example
   * let vec = list.toArray();
   */
  toArray() {
    if (this.isEmpty()) {
      return null;
    } else {
      return [...this.array];
    }
  }

  /**
   * @function indexOf
   * This method returns the current index of an element
   * @access public
   * @since 0.2.0
   * @param {any} key the element that we are searching in the list
   * @param {function(key: any, value: any)} comparer a function that return true when the two values are equals
   * @throws {Error001} When shits go bad
   * @throws {Error401} When the key is null
   * @throws {Error402} When the key is undefined
   * @throws {Error501} When the comparer is null
   * @throws {Error503} When the comparer is not a function
   * @return {number} the index of the element or -1 if the element isn't in the list
   * @example
   * let i = list.indexOf(3);
   * let j = list.indexOf(3, (x, y) => x === y["x"]);
   */
  indexOf(key, comparer) {
    if (isNotUndefined(key) && isNotNull(key) && isUndefined(comparer)) {
      let idx = -1;
      for (let i = 0; i < this.array.length; i++) {
        if(this.array[i] === key) {
          idx = i;
          break;
        }
      }
      return idx;
    } else if (isNotUndefined([key, comparer]) && isNotUndefined([key, comparer]) && isFunction(comparer)) {
      let idx = -1;
      for (let i = 0; i < this.array.length; i++) {
        if (comparer(key, this.array[i])) {
          idx = i;
          break;
        }
      }
      return idx;
    } else if(isNull(key)) {
      throw error["401"];
    } else if (isUndefined(key)) {
      throw error["402"];
    } else if (isNull(comparer)) {
      throw error["501"];
    } else  if (isNotFunction(comparer)) {
      throw error["503"];
    } else {
      throw error["001"];
    }
  }

  /**
   * @function sort
   * This function sort the list if according to a comparer function 
   * if the comparer parameter is undefined (not provided) the list will be order ascending
   * @access public
   * @since 0.2.0
   * @param { function(key: any, value: any) } comparer a function that allow the library to decided what element should be put first
   * @throws {Error501} when the comparer is null
   * @throws {Error503} when the comparer isn't a function
   * @throws {Error001} when shits go bad
   * @return {List} an new instance of the class with the list sorted;
   * @example
   * let l = list.sort((x, y) => y > x);
   */
  sort(comparer) {
    if (this.isEmpty()) {
      throw error["500"];
    } else {
      if (isNotUndefined(comparer) && isNotNull(comparer) && isFunction(comparer)) {
        let vec = [...this.array];
        return this.builder(vec.sort(comparer));
      } else if (isNull(comparer)) {
        throw error["501"];
      } else if (isUndefined(comparer)) {
        comparer = (x, y) => x > y;
        let vec = [...this.array];
        return this.builder(vec.sort(comparer));
      } else if (isNotFunction(comparer)) {
        throw error["503"];
      } else {
        throw error["001"];
      }
    }
  }

}