# helpers

[GAS](..) ⟩ [app]() ⟩ helpers

{% tabs %}
{% tab title="🔴 主題" %}
* global functions
  * [typename](../../../js/val/type/name/typename "mention")
* Array
  * [arr.isempty](../../../js/val/builtin/arr/ext/arr.isempty "mention")
  * [arr.ismatrix](../../../js/val/builtin/arr/ext/arr.ismatrix "mention") - 可用來檢查 2D data 資料格式是否正確
  * [arr.indexdictionaryforvalues](../../../js/val/builtin/arr/ext/arr.indexdictionaryforvalues "mention") - 找欄位索引 (column index)
  * [arr.containssubarray](../../../js/val/builtin/arr/ext/arr.containssubarray "mention") - 檢查某列是否包含特定欄位名稱
  * [arr.removeduplicates](../../../js/val/builtin/arr/ext/arr.removeduplicates "mention")
  * [arr.uniquemerge](../../../js/val/builtin/arr/ext/arr.uniquemerge "mention")
* String
  * [str.removewhitespaces](../../../js/val/prim/str/method/str.removewhitespaces "mention")
  * [str.replacewhitespaces](../../../js/val/prim/str/method/str.replacewhitespaces "mention")
{% endtab %}

{% tab title="💾 程式" %}
:floppy\_disk:[google apps script](https://script.google.com/u/1/home/projects/1HhmZrUiINZvdRMkJFozavWcYsTRb7bPE0Fs76YcnVDDWgc8lPVMXp63z/edit)

```javascript
const { keys } = Object;
const {log} = Logger;

/**
 * ⭐ global functions:
 * --------------------
 * - typeName()
 * 
 * ⭐ Array
 * --------------------
 * - sum()
 * - average()
 * - padEndSpaces(n)
 * - rank()
 * - indexDictionaryForValues()
 * - containsSubarray()
 * - removeDuplicates()
 * - uniqueMerge()
 * - isMatrix
 * 
 * ⭐ String
 * --------------------
 * - removeWhitespaces()
 */

// ┌────────────┐
// │ Global Functions   │
// └────────────┘

// 🔸 value's type
function typeName(value) {
    //  01234567╭──╮↱ -1
    // "[object XXXX]"
    //          ^^^^  <----  slice(8, -1)
    return Object.prototype.toString.call(value).slice(8, -1);
}

// ┌──────┐
// │ Array    │
// └──────┘

// 🔸 arr.sum()
Array.prototype.sum = function(){
    return this.reduce((result, value) => result + value, 0);
};

// 🔸 arr.average()
Array.prototype.average = function(){
    return this.sum() / this.length;
};

// 🔸 arr.padEndSpaces(n)
/**
 * pad spaces ("") to the end of array
 * 
 * @example
 * 
 *   const a = ['班級','座號','姓名'];
 *   a.padEndSpaces(3),        // [ '班級', '座號', '姓名', '', '', '' ]
 * 
 */
Array.prototype.padEndSpaces = function(n) {
    return this.concat(Array(n).fill(''));
};

// rank objects
/**
 * rank array of objects
 * 
 * @example
 * 
 *   students.rank('rankOfSchool', (a, b) => +b.total - +a.total);
 * 
 */
Array.prototype.rank = function(rankPropertyName, compare){

  const sorted = this.sort(compare);
  let current;    // current object keeping the current rank

  // 🧨 雷區：
  // - indices (keys) of array.entries() are "integer-based"
  // - indices (keys) of Object.entries(array) are "string-based"❗❗❗
  for(const [i, obj] of sorted.entries()){  

    // 如果不同分，就產生新的班排名次，並記錄目前的總分與班排名次
    if (!current || compare(current, obj) !== 0) {
      obj[rankPropertyName] = i + 1;
      current = obj;
    }
    // 如果同分，就用相同的班排。
    else {
      obj[rankPropertyName] = current[rankPropertyName];
    }

  }

  return this;    // for chaining
};

/* 
 * 🔸 array.indexDictionaryForValues(values)
 * return the index for each value (a dictionary object)
 *
 * @example
 *
 *   let row = ["座號", "姓名", "國語文", "英語文"];
 *   row.indexDictionaryForValues(["座號", "姓名"]) // { '座號': 0, '姓名': 1 }
 */
Array.prototype.indexDictionaryForValues = function(values){
    
    let dict = {};
    
    for(const value of values){
        const i = this.indexOf(value);
        if(i < 0) throw new Error(`⛔ array.indexDictionaryForValues(): array doesn't have value "${value}"`);
        dict[value] = i;
    }
    
    return dict;
};

// 🔸 arr.containsSubarray()
Array.prototype.containsSubarray = function(subarray, orderIsRelevant=false) {
    
    // ⭐ closure with internal parameter `i`
    const valueIsInArraySequentially = (i => v => i = this.indexOf(v, i) + 1)(0);
    
    const valueIsInArray = (v => this.includes(v));
    const valuePassedTest = orderIsRelevant ? valueIsInArraySequentially : valueIsInArray;
    return subarray.every(valuePassedTest);
};

// 🔸 array.removeDuplicates() 
Array.prototype.removeDuplicates = function(){
    return [... new Set(this)]
};

// 🔸 array.uniqueMerge() 
Array.prototype.uniqueMerge = function(){
  return this.flat().removeDuplicates();
};

// 🔸 arr.isMatrix
Object.defineProperty(Array.prototype, 'isMatrix', {
  get(){
    
    if (!this[0]) {
      console.log(`ℹ️ array.isMatrix: array doesn't have first row. `);
      return false;
    }
    
    function valueIsArray(value, i){
      
      let isArray = Array.isArray(value);
      
      if (!isArray) {
        console.log(`ℹ️ array.isMatrix: array[${i}] is not an array.`);
        return false;
      }
      
      return true;
    }
    
    // check if every value is an array.
    if (!this.every((value, i) => valueIsArray(value, i))) return false;
    
    // check if first row has elements
    if (!this[0].length > 0) {
      console.log(`ℹ️ array.isMatrix: first row has no values.`);
      return false;
    }
    
    const arraySameLength = (array, i) => {
      
      const len = array.length;
      const len0 = this[0].length;
      
      if (len !== len0){
        console.log(`ℹ️ array.isMatrix: array[${i}].length (${len}) ≠ array[0].length (${len0}).`);
        return false
      }
      
      return true;
    }
    
    // check if every array has the same length;
    if (!this.every((arr, i) => arraySameLength(arr, i))) return false;
    
    return true;
  }
});

// ┌──────┐
// │ String   │
// └──────┘

// 🔸 str.removeWhitespaces()
String.prototype.removeWhitespaces = function() {
    return this.replace(/\s+/g, '');
};
```
{% endtab %}

{% tab title="👥 相關" %}
* object [getter-setter](../../../js/val/obj/prop/getter-setter "mention")
{% endtab %}
{% endtabs %}
