国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

基于 Generator 和 Iterator 的惰性列表

superw / 2874人閱讀

摘要:在某些不定長度的列表操作上,惰性列表會讓代碼和結(jié)構(gòu)更靈活。方法本身是立即執(zhí)行的,如果滿足條件,這里的方法會執(zhí)行兩次。結(jié)語和是帶給我們的非常強大的語言層面的能力,它本身的求值可以看作是惰性的。

初識 Lazy List

如果有了解過 Haskell 的朋友,對下面的這些表達一定不陌生

repeat 1 -- => [1, 1, 1, 1, 1,...]
cycle "abc" -- => "abcabcabc..."
[1, 3..] -- => [1, 3, 5, 7, ...]

上面的幾個表達式產(chǎn)生的都是無限列表。對于習(xí)慣了主流編程語言的朋友可能感到困惑,在有限的內(nèi)存里面如何能表達無限的概念。主要的原因就是 Haskell 是一門默認(rèn)采用惰性求值策略的語言,沒有用到的部分,在內(nèi)存里面只是一個表達式,并不會真正的去做計算。

如果只看上面的幾個表達式,很多朋友可能會說,也沒感覺到有什么神奇的地方,似乎并沒有什么作用。我們再看看下面的代碼。

Haskell 中的 fibonacci 數(shù)列:

fibonacci = 1 : 1 : zipWith (+) fibonacci (tail fibonacci)

這里 fibonacci 本身是一個惰性結(jié)構(gòu),所以在計算的時候,會先算出列表前面的兩個1,得到 1 : 1... 這樣的結(jié)構(gòu),然后怎么表達 fibonaccifib(n) = fib(n - 1) + fib(n - 2) 特性呢?我們可以注意到,n - 1n - 2 剛好在數(shù)列中相差一位,所以 n 可以看作是該數(shù)列錯位的相加的結(jié)果。

我們再來看一則篩法求素數(shù)。不熟悉篩法的可以先點開 wiki 去看一下該算法的思路。下面這段代碼是 Haskell 的一個簡單實現(xiàn)。

primes = 2 : filter isPrime [3, 5..]
  where
    isPrime x = all (p -> x `mod` p > 0) (takeWhile (p -> p * p <= x) primes)
So, Why Lazy?

在某些不定長度的列表操作上,惰性列表會讓代碼和結(jié)構(gòu)更靈活。用上面的 primes 列表舉個例子好了,在傳統(tǒng)的 C 語言或者 Java 的實現(xiàn)里面,我們一般要先聲明一個最大長度或者一個最大的取值范圍,比如 10000 以內(nèi)的素數(shù)。如果后面的計算要用到超過這個范圍,我們就不得不重新調(diào)用生成函數(shù),重新生成一份更長的列表。這里面的問題是:一、要主動去調(diào)用這個工廠函數(shù),二、如果要復(fù)用已經(jīng)計算出來的數(shù)據(jù),手動去維護一個cache列表,勢必增加代碼的復(fù)雜度。另外一個可能的情況是,我們預(yù)先生成了一份很長的列表,后面的計算中只用到了列表頭部的一丟丟數(shù)據(jù),這也是極大的浪費。

惰性列表的使用增加了我們編程的表達能力,讓我們可以更關(guān)注數(shù)據(jù)結(jié)構(gòu)本身的特性,而不是浪費時間在如何去管理堆棧上面。因為,惰性求值特性保證我們在需要一個值的時候才會去計算,所以可以自動地最小化我們的計算量,節(jié)約資源。

比如我們可以通過 lazy byteString 去讀、寫文件,它本身不會把整個文件加載到我們的內(nèi)存里面,而是按需的讀取。有的時候我們讀一個大文件,可能只篩選出需要的前幾十條數(shù)據(jù),卻確不得不把幾百 M 甚至上 G 的大文件整個的放到內(nèi)存里面。

這里也找到一篇14年的文章 How to Speed Up Lo-Dash ×100? Introducing Lazy Evaluation,感興趣的可以點開看看。

在 JavaScript 中實現(xiàn) Lazy List

在 JavaScript 有沒有惰性結(jié)構(gòu)呢?先看下面這個例子。

let fetchSomething = fetch("/some/thing");
if (condition) {
  fetchSomething = fetch("/some/thing/condition");
}
fetchSomething.then(() => {
  // TODO
});

fetch 方法本身是立即執(zhí)行的,如果滿足條件,這里的 fetch 方法會執(zhí)行兩次。這并不是我們期待的行為,這里需要讓這個 fetch 的動作在我們需要的時候才去執(zhí)行,而不是聲明的時候就開始執(zhí)行的話,通常的做法是把它改成下面的樣子。

let fetchSomething = () => fetch("/some/thing");
if (condition) {
  fetchSomething = () = fetch("/some/thing/condition");
}
fetchSomething.then(() => {
  // TODO
});

由此啟發(fā),我們大致可以實現(xiàn)如下的結(jié)構(gòu)。

class List {
  head: T | () => T
  tail: List | () => List

  constructor(head: T, tail: () => List) {
    this.head = () => head;
    this.tail = tail;
  }
}

List 本質(zhì)上是一個單鏈表,構(gòu)造函數(shù)里面?zhèn)魅氲?tail 是一個工廠函數(shù),用來構(gòu)建新的 List 節(jié)點。只有在我們訪問到一個節(jié)點的時候,才對它的 head 求值,訪問它的下一個節(jié)點的時候?qū)?tail 求值,不然 head 和 tail 都只是待求值的表達式。

這種方式看起來似乎已經(jīng)解決了我的問題,但是這種結(jié)構(gòu)在和普通的 Array 做互相轉(zhuǎn)換的時候,存在大量不必要的額外開銷。

那 JavaScript 中有沒有更天然的結(jié)構(gòu),可以讓我們免于去構(gòu)造這樣一個復(fù)雜的對象,簡化代碼的同時,讓我們的代碼更具有普適性呢?

初識 Iterable

ES6 的新特性給了我想要的答案,Iteration Protocols。如果嫌MDN的描述太長,可以直接看下面等價的類型聲明。

interface Iterable {
  [Symbol.iterator](): Iterator;
}

interface Iterator {
  next(): IteratorResult;
}

interface IteratorResult {
  done: Boolean;
  value?: T;
}

interface IterableIterator {
  [Symbol.iterator](): Iterator;
  next(): IteratorResult;
}

所有實現(xiàn)一個Iterable接口的對象都可以通過諸如 for...of...、...itor 以及 Array.from 來訪問,當(dāng)next方法的返回值中done為true時,迭代結(jié)束。而且只有我們訪問next方法時,才會進入下一步迭代,是理想的Lazy結(jié)構(gòu)。

這時候我們看一下我們的 fibonacci 該怎么寫?

class Fibonacci implements IterableIterator {
  private prev = 1;
  private next = 1;

  public next() {
    let current = this.prev;
    this.prev = this.next;
    this.next = current + this.prev;
    return {
      done: false,
      value: current
    }
  }

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

const fib = new Fibonacci();
fib.next() // => { done: false, value: 1 }
fib.next() // => { done: false, value: 1 }
fib.next() // => { done: false, value: 2 }
// etc

到這里,我們已經(jīng)可以表達一個惰性的無限數(shù)列了。但是上面的代碼畢竟過于繁瑣,好在 ES6 同時給我們提供了 Generator, 可以讓我們很方便地書寫 IterableItorator,從某種意義上來講,Generator 可以說是上面代碼的語法糖。

使用Generator,上面的代碼可以簡化成下面的樣子。

export function* fibonacci() {
  let prev = 1;
  let next = 1;

  while (true) {
    yield prev;
    const temp = prev;
    prev = next;
    next = temp + prev;
  }
}

const fib = fibonacci();
// etc

這里不再去花段落介紹 Generator 的語法,不了解的同學(xué)可以先去閱讀下這篇文章 A Simple Guide to Understanding Javascript (ES6) Generators。

定義 Infinite List

接著上面的代碼往下寫,下面的代碼分別實現(xiàn)了文章開頭的 repeat, cycle, iterate, range 等方法。

export function* repeat(item: T) {
  while (true) {
    yield item;
  }
}

export function* cycle(items: Iterable) {
  while (true) {
    yield* [...items];
  }
}

export function* iterate(fn: (value: T) => T, initial: T) {
  let val = initial;
  while (true) {
    yield val;
    val = fn(val);
  }
}

export function* range(start: number, end = Infinity, step = 1) {
  while (start <= end) {
    yield start;
    start += step;
  }
}

可以看到,代碼是非常直觀且易于理解的。

定義 Operator

有了列表之后,我們需要在列表之上進行操作,下面的代碼分別實現(xiàn)了 map/filter/take/takeWhile 方法。

export function* map(fn: (value: T) => U, items: Iterable) {
  for (let item of items) {
    yield fn(item);
  }
}

export function* filter(
  predicate: (value: T) => boolean,
  items: Iterable
) {
  for (let item of items) {
    if (predicate(item)) {
      yield item;
    }
  }
}

export function* take(n: number, items: Iterable) {
  let i = 0;
  if (n < 1) return;

  for (let item of items) {
    yield item;
    i++;
    if (i >= n) {
      return;
    }
  }
}

function* takeWhile(
  predicate: (value: T) => boolean,
  items: Iterable
) {
  for (let item of items) {
    if (predicate(item)) {
      yield item;
    } else {
      return;
    }
  }
}

上面的代碼都是比較簡單的。比較難一點的是去實現(xiàn) zip 方法,即怎么把兩個列表合并成一個?

難點在于接收一個 Iterable 的對象的話,本身并不一定要實現(xiàn) next 方法的,比如 Array、String 等,同時Iterable對象也并不是都可以通過 index 來訪問的。此外,如果想先通過Array.from變成數(shù)組,然后在數(shù)組上進行操作,我們會遇到一個情況是我們傳入的 Iterable 對象是無限的,如上文的 fibonacci 一樣,這種情況下是不能使用 Array.from 的。

這時候我的一個思路是需要想辦法把一個 Iterable 的對象提升成為 IterableItorator 對象,然后通過 next 方法,逐一遍歷。

How ?幸好 Generator 給我們提供了一個 yield* 操作符,可以讓我們方便的定義出一個 lift 方法。

export function* lift(items: Iterable): IterableIterator {
  yield* items;
}

有了這個 lift 方法之后,就可以很方便的書寫 zip 方法和 zipWith 方法了。

export function* zip(
  seqA: Iterable,
  seqB: Iterable
): IterableIterator<[T, G]> {
  const itorA = lift(seqA);
  const itorB = lift(seqB);
  let valA = itorA.next();
  let valB = itorB.next();
  while (!valA.done || !valB.done) {
    yield [valA.value, valB.value];
    valA = itorA.next();
    valB = itorB.next();
  }
}

export function* zipWith(
  fn: (a: T, b: G) => R,
  seqA: Iterable,
  seqB: Iterable
): IterableIterator {
  const itorA = lift(seqA);
  const itorB = lift(seqB);
  let valA = itorA.next();
  let valB = itorB.next();
  while (!valA.done || !valB.done) {
    yield fn(valA.value, valB.value);
    valA = itorA.next();
    valB = itorB.next();
  }
}

更多的方法可以去底部的點開我的 repo,這里就不一一列舉了。

結(jié)語

Generator 和 Iterator 是 ES6 帶給我們的非常強大的語言層面的能力,它本身的求值可以看作是惰性的。

差不多在13年左右,TJ 的 co 剛出來的時候,其代碼的短小精悍可以說是相當(dāng)驚艷的。然而在我們的使用中,一來受限于瀏覽器兼容性,二來受限于我們的使用場景,個人認(rèn)為我們對其特性開發(fā)得還遠(yuǎn)遠(yuǎn)不夠。結(jié)合 IO、network,Generator 和 Iterator 還能為我們做更多的事情。

另外,需要特別說明的是,雖然這篇文章通篇是在講惰性列表,但是惰性列表并不是銀彈,相反的,惰性結(jié)構(gòu)的濫用會在程序的執(zhí)行過程中緩存大量的thunk,增大在內(nèi)存上的開銷。

最后,利益相關(guān) - 有贊招前端,簡歷請投 wangqiao@youzan.com。

完整代碼請移步 GitHub。

本文首發(fā)于有贊技術(shù)博客。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/98016.html

相關(guān)文章

  • 小李飛刀:python請你輕輕輕點虐

    摘要:迭代器可以直接作用于循環(huán)的對象統(tǒng)稱為可迭代對象。可以被函數(shù)調(diào)用并不斷返回下一個值的對象稱為迭代器。這個高階函數(shù),關(guān)鍵在于正確實現(xiàn)一個篩選函數(shù)。 又是日常嘮嗑的一小段 真的是非常話嘮的在下,日常給自己打點雞血吧。昨晚和老媽聊了一整晚,所以昨天并沒有更新。然后因為很快要開始算個稅減免的部分,對于溫飽線的在下而言,其實減免的可能就只是奶茶錢吧。工作的本質(zhì)是賺錢,我也很想在30歲之前完成財務(wù)自...

    Keagan 評論0 收藏0
  • [python] 關(guān)于 python 一些高級特性

    摘要:開始本文主要記錄廖大教程中高級特性這一節(jié)的內(nèi)容,并寫下我的一些理解。廖大的教程中是這樣說的函數(shù)是順序執(zhí)行,遇到語句或者最后一行函數(shù)語句就返回。 前言 用 python 差不多半年多了,從去年暑假開始接觸,從開始的懵逼,到寫了一些小爬蟲總算入門之后,許多作業(yè)也是能用 python 就用 python,基本拋棄了 C++。但是還是有些過于急躁了,能夠?qū)懸恍┖喍痰拇a,但是對于 python...

    Pines_Cheng 評論0 收藏0
  • Python:range 對象并不是迭代器

    摘要:簡評迭代器是惰性可迭代對象,函數(shù)在中是一個惰性的可迭代對象,那么是不是迭代器呢為什么。如果你不能將某些東西傳遞給函數(shù),那么它不是一個迭代器。的對象不是迭代器。 簡評:迭代器(iterator)是惰性可迭代對象(lazy iterable),range 函數(shù)在 Python 3 中是一個惰性的可迭代對象,那么 range 是不是迭代器呢?為什么。 TLNR:Python 3 中的 ran...

    draveness 評論0 收藏0
  • python迭代器與生成器小結(jié)

    摘要:迭代器要說生成器,必須首先說迭代器區(qū)分與講到迭代器,就需要區(qū)別幾個概念看著都差不多,其實不然。比如常見就是與分離實現(xiàn)的本身是可迭代對象,但不是迭代器,類似與但是又不同。 2016.3.10關(guān)于例子解釋的補充更新 源自我的博客 例子 老規(guī)矩,先上一個代碼: def add(s, x): return s + x def gen(): for i in range(...

    hellowoody 評論0 收藏0
  • [譯]JavaScript ES6迭代器指南

    摘要:前言又稱提供一個全新的迭代器的概念,它允許我們在語言層面上定義一個有限或無限的序列。后者可以被用來幫助我們理解迭代器。但是當(dāng)我們使用迭代器時,這個問題就迎刃而解了。是中的新語法,用來配合迭代器。這是因為數(shù)組的迭代器只返回其中預(yù)期的元素。 前言 EcmaScript 2015 (又稱ES6)提供一個全新的迭代器的概念,它允許我們在語言層面上定義一個(有限或無限的)序列。 暫時先拋開它...

    daryl 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<