1. 程式人生 > >Eloquent JavaScript #05# higher-order functions

Eloquent JavaScript #05# higher-order functions

name sum reat () lam per .net class sse

索引:

  • Notes
    1. 高階函數
    2. forEach
    3. filter
    4. map
    5. reduce
    6. some
    7. findIndex
    8. 重寫課本示例代碼
  • Excercises
    1. Flattening
    2. Your own loop
    3. Everything
    4. Dominant writing direction

Notes

1、高階函數的概念:Functions that operate on other functions, either by taking them as arguments or by returning them, are called higher-order functions

.

2、自己yy的高階函數優勢:

  • 更少的代碼。有效減少鍵盤磨損
  • 傳統編程是復用對象、方法,高階函數則是復用一種更加抽象的模式
  • 對於理解、熟練運用高階函數的人而言,采用高階函數比傳統代碼有更好的可讀性

高階函數是函數式編程的一種特性,當然js並不是函數編程語言,對於函數式編程可以參考—— 什麽是函數式編程思維? - 用心閣的回答

3、代碼收集:

① 高階函數示例1

function greaterThan(n) {
  return m => m > n;
}
let greaterThan10 = greaterThan(10);
console.log(greaterThan10(
11)); // → true

--- -- ------------ - - - -- - -- - - - - - --- - - - ---- -- --

② 高階函數示例2

function noisy(f) {
  return (...args) => {
    console.log("calling with", args);
    let result = f(...args);
    console.log("called with", args, ", returned", result);
    
return result; }; } noisy(Math.min)(3, 2, 1); // → calling with [3, 2, 1] // → called with [3, 2, 1] , returned 1

--- -- ------------ - - - -- - -- - - - - - --- - - - ---- -- --

③ 高階函數示例3

function unless(test, then) {
  if (!test) then();
}

repeat(3, n => {
  unless(n % 2 == 1, () => {
    console.log(n, "is even");
  });
});
// → 0 is even
// → 2 is even

--- -- ------------ - - - -- - -- - - - - - --- - - - ---- -- --

④ 高階函數forEach,功能類似於for/of循環。

["A", "B"].forEach(l => console.log(l));
// → A
// → B

--- -- ------------ - - - -- - -- - - - - - --- - - - ---- -- --

⑤ 用於過濾數據的高階函數

function filter(array, test) {
  let passed = [];
  for (let element of array) {
    if (test(element)) {
      passed.push(element);
    }
  }
  return passed;
}

console.log(filter(SCRIPTS, script => script.living));
// → [{name: "Adlam", …}, …]

標準數組方法:

console.log(SCRIPTS.filter(s => s.direction == "ttb"));
// → [{name: "Mongolian", …}, …]

--- -- ------------ - - - -- - -- - - - - - --- - - - ---- -- --

⑥ filter會生成過濾掉一些數據的新數組,map則從原有的數組構造一個長度相同、但數據經過轉化的新數組。

function map(array, transform) {
  let mapped = [];
  for (let element of array) {
    mapped.push(transform(element));
  }
  return mapped;
}

let rtlScripts = SCRIPTS.filter(s => s.direction == "rtl");
console.log(map(rtlScripts, s => s.name));
// → ["Adlam", "Arabic", "Imperial Aramaic", …]

map同樣是一個標準的數組方法。

--- -- ------------ - - - -- - -- - - - - - --- - - - ---- -- --

⑦ 高階函數reduce(或者說fold):通過對數組中的元素逐個進行某種累加運算產出一個結果。例如說求和sum(array),翻譯成高階函數就是reduce(array, (a, b) => a + b, 0)

function reduce(array, combine, start) {
  let current = start;
  for (let element of array) {
    current = combine(current, element);
  }
  return current;
}

console.log(reduce([1, 2, 3, 4], (a, b) => a + b, 0));
// → 10

標準數組方法:

console.log([1, 2, 3, 4].reduce((a, b) => a + b));
// → 10

reduce用於比較最後得出一個結果:

function characterCount(script) {
  return script.ranges.reduce((count, [from, to]) => {
    return count + (to - from);
  }, 0);
}

console.log(SCRIPTS.reduce((a, b) => {
  return characterCount(a) < characterCount(b) ? b : a;
}));
// → {name: "Han", …}

更簡單的例子:

[1, 23, 21, 12, 3, 5].reduce((a, b) => a > b ? a : b);
// → 23
[1, 23, 21, 12, 3, 5].reduce((a, b) => {
    console.log(`compare ${a} with ${b}`);
    return a > b ? a : b;
});
// → compare 1 with 23
// → compare 23 with 21
// → compare 23 with 12
// → compare 23 with 3
// → compare 23 with 5
// → 23

--- -- ------------ - - - -- - -- - - - - - --- - - - ---- -- --

⑧ 高階函數some

[2, 3, 2, 1, 2, 5, 7].some(a => a > 7);
// → false

[2, 3, 2, 1, 2, 5, 7].some(a => a >= 7);
// → true

[2, 3, 2, 1, 2, 5, 7].some(a => {
    console.log(`start testing ${a}`);
    return a == 1;
});
// → start testing 2
// → start testing 3
// → start testing 2
// → start testing 1
// → true

測試數組中的元素,只要其中任何一個滿足就返回true

--- -- ------------ - - - -- - -- - - - - - --- - - - ---- -- --

⑨ 高階函數findIndex

[2, 3, 2, 1, 2, 5, 7].findIndex(a => a > 7);
// → -1

[2, 3, 2, 1, 2, 5, 7].findIndex(a => a >= 7);
// →  6

[2, 3, 2, 1, 2, 5, 7].findIndex(a => {
    console.log(`start testing ${a}`);
    return a == 1;
});
// → start testing 2
// → start testing 3
// → start testing 2
// → start testing 1
// → 3

4、重寫課本示例代碼(統計文本中字符的腳本歸屬分布)

數據:http://eloquentjavascript.net/code/scripts.js

數據格式:

  {
    name: "Adlam",
    ranges: [[125184, 125259], [125264, 125274], [125278, 125280]],
    direction: "rtl",
    year: 1987,
    living: true,
    link: "https://en.wikipedia.org/wiki/Fula_alphabets#Adlam_alphabet"
  }

主代碼:

/**
 * 字符是由各種字符腳本處理的
 * 通過每個字符的編碼可以找到該字符的腳本歸屬
 * 例如編碼為1~100的字符由a腳本處理
 * 編碼為300~400、500~700的由b腳本處理
 */

/**
 * 課本上的函數一般直接用“代表返回值的名詞”做函數名
 * 而java中這種情況一般用“get+代表返回值的名詞”做函數名
 * 如果覺得某個函數意義模糊,可以自行在函數前面腦補動詞
 */

const textToCodes = (text) => {
    let result = [];
    for (let char of text) {
          result.push(char.codePointAt(0));
    }
    return result;
};

const codeToScriptName = (code) => {
    let result = null;
    for (let script of SCRIPTS) {
        if (script.ranges.some(([from, to]) => {
            return code >= from && code < to;
        })) {
            result = script.name;
            break;
        }
    }
    return result;
};

const elementCounts = (arr) => {
    let result = [];
    for (let element of arr) {
        let index = result.findIndex(a => a.label == element);
        if (index == -1) {
            result.push({label: element, count: 1});
        } else {
            result[index].count++;    
        }
    }
    return result;
};

const scriptsRate = (text) => {
    let scriptNames = textToCodes(text).map(code => codeToScriptName(code));
    let total = scriptNames.length;
    let scriptCounts = elementCounts(scriptNames);
    return scriptCounts.map(({label, count}) => {
        return {
            label: label == null ? "none" : label, 
            rate: (count / total).toFixed(2)
        };
    });
};

const main = () => {
    let text = ‘英國的狗說"woof", 俄羅斯的狗說"тяв"‘;
    console.log(scriptsRate(text).map(item => {
        return `${item.rate * 100}% ${item.label}`;
    }).join(", "));
};

main();
// → 46% Han, 25% none, 17% Latin, 13% Cyrillic

Exercises

① Flattening

let arrays = [[1, 2, 3], [4, 5], [6]];

arrays.reduce((sum, x) => {
    console.log(`concat ${sum} and ${x}`);
    return sum.concat(x);
});
// → concat 1,2,3 and 4,5
// → concat 1,2,3,4,5 and 6
// → [1, 2, 3, 4, 5, 6]

--- -- ------------ - - - -- - -- - - - - - --- - - - ---- -- --

② Your own loop

const loop = (initVal, test, update, body) => {
    let count = 0;
    for (let i = initVal; test(i); i = update(i)) {
        body(i);
    }
};

loop(3, n => n > 0, n => n - 1, console.log);
// → 3
// → 2
// → 1

--- -- ------------ - - - -- - -- - - - - - --- - - - ---- -- --

③ Everything

版本1:

function every(array, test) {
    for (let x of array) {
        if (!test(x)) return false;
    }
    return true;
}

console.log(every([1, 3, 5], n => n < 10));
// → true
console.log(every([2, 4, 16], n => n < 10));
// → false
console.log(every([], n => n < 10));
// → true

版本2:

function every(array, test) {
    return !array.some(x => !test(x));
}

console.log(every([1, 3, 5], n => n < 10));
// → true
console.log(every([2, 4, 16], n => n < 10));
// → false
console.log(every([], n => n < 10));
// → true

--- -- ------------ - - - -- - -- - - - - - --- - - - ---- -- --

④ Dominant writing direction

PS. 要用到示例代碼中的數據。

const textToCodes = (text) => {
    let result = [];
    for (let char of text) {
          result.push(char.codePointAt(0));
    }
    return result;
};

const codeToDirection = (code) => {
    let result = null;
    for (let script of SCRIPTS) {
        if (script.ranges.some(([from, to]) => {
            return code >= from && code < to;
        })) {
            result = script.direction;
            break;
        }
    }
    return result;
};

const elementCounts = (arr) => {
    let result = [];
    for (let element of arr) {
        let index = result.findIndex(a => a.label == element);
        if (index == -1) {
            result.push({label: element, count: 1});
        } else {
            result[index].count++;    
        }
    }
    return result;
};

function dominantDirection(text) {
    let derections = textToCodes(text).map(code => codeToDirection(code));
    let derectionCounts = elementCounts(derections).filter(x => x.label != null);
    return derectionCounts.reduce((a, b) => {
        return a.count > b.count ? a : b;
    }).label;
}

console.log(dominantDirection("Hello!"));
// → ltr
console.log(dominantDirection("Hey, ???? ?????"));
// → rtl

Eloquent JavaScript #05# higher-order functions