1. 程式人生 > 程式設計 >如何用JavaScript實現一個數組惰性求值庫

如何用JavaScript實現一個數組惰性求值庫

概述

程式設計語言理論中,惰性求值(英語:Lazy Evaluation),又譯為惰性計算、懶惰求值,也稱為傳需求呼叫(call-by-need),是一個計算機程式設計中的一個概念,它的目的是要最小化計算機要做的工作。它有兩個相關而又有區別的含意,可以表示為“延遲求值”和“最小化求值”,除可以得到效能的提升外,惰性計算的最重要的好處是它可以構造一個無限的資料型別。

看到函式式語言裡面的惰性求值,想自己用javascript寫一個最簡實現,加深對惰性求值瞭解。用了兩種方法,都不到 80 行實現了基本的陣列的惰性求值。

怎麼實現

惰性求值每次求值的時候並不是返回數值,而是返回一個包含計算引數的求值函式,每次到了要使用值得時候,才會進行計算。

如何用JavaScript實現一個數組惰性求值庫

當有多個惰性操作的時候,構成一個求值函式鏈,每次求值的時候,每個求值函式都向上一個求值函式求值,返回一個值。最後當計算函式終止的時候,返回一個終止值。

如何用JavaScript實現一個數組惰性求值庫

具體實現

判斷求值函式終止

每次求值函式都會返回各種資料,所以得使用一個獨一無二的值來作為判斷流是否完成的標誌。剛好 Symbol() 可以建立一個新的 symbol ,它的值與其它任何值皆不相等。

const over = Symbol();

const isOver = function (_over) {
  return _over === over;
}

生成函式 range

range 函式接受一個起始和終止引數,返回一個求值函式,執行求值函式返回一個值,終止的時候返回終止值。

const range = function (from,to) {
  let i = from;
  return function () {
    if (i < to) {
      i++
      console.log('range\t',i);
      return i
    }
    return over;
  }
}

轉換函式 map

接受一個求值函式和處理函式,獲取求值函式 flow 中的資料,對資料進行處理,返回一個流。

const map = function (flow,transform) {
  return function () {
    const data = flow();
    console.log('map\t',data);
    return isOver(data) ? data : transform(data);
  }
}

過濾函式 filter

接受一個求值函式,對求值函式 flow 中資料進行過濾,找到符合的資料並且返回。

const filter = function (flow,condition) {
  return function () {
    while(true) {
      const data = flow();
      if (isOver(data)) {
        return data;
      }
      if(condition(data)) {
        console.log('filter\t',data);
        return data;
      }
    }
  }
}

中斷函式 stop

接受一個求值函式,當達到某個條件時中斷,可以用閉包函式加上 stop 函式接著實現一個 take 函式。

const stop = function (flow,condition) {
  let _stop = false;
  return function () {
    if (_stop) return over;
    const data = flow();
    if (isOver(data)) {
      return data;
    }
    _stop = condition(data);
    return data;
  }
}

const take = function(flow,num) {
  let i = 0;
  return stop(flow,(data) => {
    return ++i >= num;
  });
}

收集函式 join

因為返回的都是一個函式,最後得使用一個 join 函式來收集所有的值並且返回一個數組。

const join = function (flow) {
  const array = [];
  while(true) {
    const data = flow();
    if (isOver(data)) {
      break;
    }
    array.push(data);
  }
  return array;
}

測試:

const nums = join(take(filter(map(range(0,20),n => n * 10),n => n % 3 === 程式設計客棧0),2));
console.log(nums);

輸出:

range  1

map    1

range  2

map    2

range  3

map    3

filter     程式設計客棧;30

range  4

map    4

range  5

map    5

range  6

map    6

filter     60

更優雅的實現

上面使用 函式 + 閉包 實現了惰性求值,但是還是不夠優雅,絕大部分程式碼都放到迭代和判斷求值是否完成上面去了。其實 es6 中還有更好方法來實現惰性求值,就是使用 generator,generator 已經幫我們解決了迭代和判斷流是否完成,我們就可以專注於邏輯,寫出更簡潔易懂結構清晰的程式碼。

const range = function* (from,to) {
  for(let i = from; i < to; i++) {
    console.log('range\t',i);
    yield i;
  }
}

const map = function* (flow,transform) {
  for(const data of flow) {
    console.log('map\t',data)程式設計客棧;
    yield(transform(data));
  }
}

const filter = function* (flow,condition) {
  for(const data of flow) {
    console.log('filter\t',data);
    if (condition(data)) {
      yield data;
    }
  }
}

const stop = function*(flow,condition) {
  for(const data of flow) {
    yield data;
    if (condition(data)) www.cppcns.com{
      break;
    }
  }
}

const take = function (flow,number) {
  let count = 0;
  const _filter = function (data) {
    count ++
    return count >= number;
  }
  return stop(flow,_filter);
}

還得加上鍊式呼叫才算是完成了。

class _Lazy{
  constructor() {
    this.iterator = null;
  }

  range(...NKhxVhargs) {
    this.iterator = range(...args);
    return this;
  }

  map(...args) {
    this.iterator = map(this.iterator,...args);
    return this;
  }

  filter(...args) {
    this.iterator = filter(this.iterator,...args);
    return this;
  }

  take(...args) {
    this.iterator = take(this.iterator,...args);
    return this;
  }

  [Symbol.iterator]() {
    return this.iterator;
  }

}

function lazy () {
  return new _Lazy();
}

最後再測試一下:

const nums = lazy().range(0,100).map(n => n * 10).filter(n => n % 3 === 0).take(2);

for(let n of nums) {
  console.log('num:\t',n,'\n');
}

輸出:

range  0

map    0

filter     0

num:   0

range  1

map    1

filter     10

range  2

map    2

filter     20

range  3

map    3

filter     30

num:   30

好了,大功告成。

總結

這樣我們就完成了一個最簡的陣列惰性求值的庫,這裡只是簡單實現了惰性求值,要放到工程中還需要新增很多細節。因為程式碼不過 80 行,可以很清楚的瞭解惰性求值原理,還能加深對生成器的理解。

以上就是如何用javaScript實現一個數組惰性求值庫的詳細內容,更多關於JavaScript實現陣列惰性求值庫的資料請關注我們其它相關文章!