intersection、intersectionBy、intersectionWidth

intersection()、intersectionBy()、intersectionWidth()

intersection()、intersectionBy()、intersectionWidth()方法用於對傳入的數組求交集,獲取存在於每個數組中的元素,生成新的數組返回,其內部都是基於baseIntersection方法;

剛開始看baseIntersection方法實現的時候可能會有點困難,它裏面引用了許多方法,讓理解起來有點困難,再看源碼之前,先思考一個問題,如果讓我們自己來實現一個方法,找出多個數組中都存在的元素並返回,我們該如何實現呢?

很容易能想到的就是:對於多個數組(array1、array2、array3 … ),由於我們最終想要的是存在於每個數組中的元素,所以我們可以對其中任意一個數組進行遍歷,讓其中的每個元素與剩餘數組進行對比,如果該元素在剩餘數組中都存在,那麼該元素就是我們最終想要的元素;

baseIntersection方法的實現跟上面的差不多,不同的地方是它不是任意選一個數組進行遍歷的,它總是取傳入數組參數的第一個數組進行遍歷,具體如下:

// baseIntersection.js
// baseIntersection方法用於求arrays中每個數組的交集元素,最終返回的交集元素值均來自arrays的第一個元素
function baseIntersection(arrays, iteratee, comparator) {
  const includes = comparator ? arrayIncludesWith : arrayIncludes
  // 獲取arrays數組第一個參數的長度
  const length = arrays[0].length
  // 獲取arrays數組的長度
  const othLength = arrays.length
  // 根據arrays數組的長度生成一個新數組caches
  const caches = new Array(othLength)
  const result = []

  let array
  let maxLength = Infinity
  let othIndex = othLength

  // 從後向前遍歷arrays數組
  while (othIndex--) {
    // 獲取當前索引在arrays數組中對應的數組對象
    array = arrays[othIndex]
    // 如果otherIndex索引大於0且iteratee函數存在,對當前數組對象調用map方法,也就是對當前數組對象中的每個元素調用iteratee方法,由其返回值組成一個新的數組返回
    if (othIndex && iteratee) {
      array = map(array, (value) => iteratee(value))
    }
    maxLength = Math.min(array.length, maxLength)
    // 下面代碼的意思就是什麼時候使用緩存cache,有以下情況:
    // 當comparator存在時不啓用;
    // comparator不存在,iteratee存在時啓用
    // comparator不存在,iteratee不存在,但是arrays數組第一個元素的長度大於等於120且遍歷的當前array數組的長度大於等於120時啓用
    // 值得注意的是當othIndex爲0時,caches[0]是一個空的SetCache實例,因爲othIndex && array爲0
    caches[othIndex] = !comparator && (iteratee || (length >= 120 && array.length >= 120))
      ? new SetCache(othIndex && array)
      : undefined
  }
  // array取arrays數組中第一個元素
  array = arrays[0]

  // index爲-1,後面循環array數組時需要用到
  let index = -1
  // seen取caches數組中第一個元素
  const seen = caches[0]

  // 遍歷arrays中的第一個數組元素也就是array數組,將array數組的每個元素與arrays數組中剩餘的數組進行對比
  outer:
  while (++index < length && result.length < maxLength) {

    // 獲取array數組中當前index對應的值
    let value = array[index]
    // 如果iteratee存在,則用iteratee處理value,後續操作將使用處理後的結果,如果iteratee不存在,則使用value進行後續操作
    const computed = iteratee ? iteratee(value) : value

    // 如果comparator存在或value不等於0時,value取自身,否則讓其爲0處理
    value = (comparator || value !== 0) ? value : 0
    // 判斷seen是否存在,存在的話,利用cacheHas方法判斷seen數組中是否包含computed元素
    // 如果seen不存在,則調用includes方法,判斷返回數組result中是否包含computed
    // cacheHas(seen, computed)判斷的目的是防止array中有相同元素且該元素存在於所有數組中時,再走一遍對比過程
    // includes(result, computed, comparator)判斷的作用於seen中判斷是否有computed一致
    /* 
      不過在看這兒代碼的時候有個疑問,那就是爲什麼seen中判斷的時候用的是computed而result中判斷的時候用value?
    */

    if (!(seen
          ? cacheHas(seen, computed)
          : includes(result, computed, comparator)
        )) {
      othIndex = othLength
      // 從右向左循環遍歷剩餘arrays或caches數組,依次判斷arrays或caches數組中的每個數組中是否包含computed,如果遇到有數組不包含,則退出arrays數組的遍歷,繼續進行外層循環(也就是arrays第一個數組元素的遍歷)
      // 遍歷時使用--otherIndex,這樣就不會遍歷到arrays或caches數組的第一個元素,也就是array或seen數組
      while (--othIndex) {
        const cache = caches[othIndex]
        // 遍歷的時候先查看緩存是否存在,存在的話,使用caches[othIndex],不存在的話使用arrays[othIndex]
        // 遍歷時,當前數組中不存在computed,則跳出當前的arrays或caches循環
        if (!(cache
              ? cacheHas(cache, computed)
              : includes(arrays[othIndex], computed, comparator))
            ) {
          continue outer
        }
      }
      // 遍歷完arrays或caches數組後,如果seen存在,則將computed添加到seen中
      /* 
        爲什麼需要將computed添加到seen中呢?
        這是因爲上面再循環arrays時,就是否啓用緩存有這麼一句:caches[othIndex] = !comparator && (iteratee || (length >= 120 && array.length >= 120)) ?
          new SetCache(othIndex && array) :
          undefined
        其中對於arrays的第一個元素,也就是otherIndex爲0時,caches[0]是一個空的SetCache實例,其中沒有元素值,
        當遍歷完arrays或caches數組沒有跳出循環時,說明computed是存在於所有數組中,
        於是將其添加到seen中,防止array數組中相同元素在走一遍對比過程
      */
      if (seen) {
        seen.push(computed)
      }
      // 遍歷完arrays數組之後,如果沒有跳出循環,則表明computed在每個數組中都存在,於是將value添加到result中
      result.push(value)
    }
  }
  return result
}

看完了baseIntersection的實現方法,接下來就是分析intersection、intersectionBy、intersectionWidth方法的源碼,如下:

// intersection.js
function intersection(...arrays) {
  // 對入參進行處理,遍歷參數數組arrays,並對其中每個元素調用castArrayLikeObject方法,使得arrays數組最終爲一個由類數組組成的數組
  const mapped = map(arrays, castArrayLikeObject)
  // 當mapped的長度大於0且mapped數組的第一個元素與arrays數組中的第一個元素相等時,調用baseIntersection方法,否則返回空數組
  // 爲什麼要進行mapped[0] === arrays[0]的判斷?
  // 因爲intersection方法實際上是對傳入的數組求交集元素,在得到交集元素後需要從第一個參數中這些交集元素返回,如果mapped[0]不等於arrays[0],說明arrays[0]並不是個類數組對象,無法將交集元素從其中返回
  return (mapped.length && mapped[0] === arrays[0])
    ? baseIntersection(mapped)
    : []
}
// intersectionBy.js
function intersectionBy(...arrays) {
  // 取arrays最後一個元素作爲iteratee
  let iteratee = last(arrays)
  // 對入參進行處理,遍歷參數數組arrays,並對其中每個元素調用castArrayLikeObject方法,使得arrays數組最終爲一個由類數組組成的數組
  const mapped = map(arrays, castArrayLikeObject)

  // 判斷iteratee與mapped最後一個元素是否相等,相等說明iteratee是個類數組,而不是一個函數,則讓iteratee爲undefined,否則,說明iteratee是個函數,應該將mapped最後一個元素去除掉
  if (iteratee === last(mapped)) {
    iteratee = undefined
  } else {
    mapped.pop()
  }
  // 當mapped的長度大於0且mapped數組的第一個元素與arrays數組中的第一個元素相等時,調用baseIntersection方法,否則返回空數組
  // 爲什麼要進行mapped[0] === arrays[0]的判斷?
  // 因爲intersection方法實際上是對傳入的數組求交集元素,在得到交集元素後需要從第一個參數中這些交集元素返回,如果mapped[0]不等於arrays[0],說明arrays[0]並不是個類數組對象,無法將交集元素從其中返回
  return (mapped.length && mapped[0] === arrays[0])
    ? baseIntersection(mapped, iteratee)
    : []
}
// intersectionWidth.js
function intersectionWith(...arrays) {
  // 取arrays最後一個元素作爲comparator
  let comparator = last(arrays)
  // 對入參進行處理,遍歷參數數組arrays,並對其中每個元素調用castArrayLikeObject方法,使得arrays數組最終爲一個由類數組組成的數組
  const mapped = map(arrays, castArrayLikeObject)

  // 判斷comparator是不是function,如果不是,comparator取undefined
  comparator = typeof comparator == 'function' ? comparator : undefined
  // 如果comparator存在,則將mapped最後一個元素去除掉
  if (comparator) {
    mapped.pop()
  }
  // 當mapped的長度大於0且mapped數組的第一個元素與arrays數組中的第一個元素相等時,調用baseIntersection方法,否則返回空數組
  // 爲什麼要進行mapped[0] === arrays[0]的判斷?
  // 因爲intersection方法實際上是對傳入的數組求交集元素,在得到交集元素後需要從第一個參數中這些交集元素返回,如果mapped[0]不等於arrays[0],說明arrays[0]並不是個類數組對象,無法將交集元素從其中返回
  return (mapped.length && mapped[0] === arrays[0])
    ? baseIntersection(mapped, undefined, comparator)
    : []
}
example:
_.intersection([1, 2, 2, 4], [2, 3, 2, 5])
// [2]

實現思路:

對於多個數組(array1、array2、array3 … ),由於我們最終想要的是存在於每個數組中的元素,所以我們選取array1數組進行遍歷,讓array1其中的每個元素與剩餘數組(array2、array3 …)進行對比,如果該元素在剩餘數組中都存在,那麼該元素就是我們最終想要的元素;

2019/6/22

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章