可以單個因子構(gòu)成一個SKU的實現(xiàn)

最近做了一版需求,和淘寶等商城App有些不一樣,我們的商品單個因子就可以構(gòu)成一個SKU,特地記錄下來,給也有這種需求的App提供一個思路。

需求

名詞解釋:

  • 規(guī)格:即SKU。
  • 因子組:構(gòu)成商品規(guī)格的一個維度,比如:"座位數(shù)"。
  • 因子:因子組中具體的一個因子,比如說:"5座及以下","6座","7座及以上"。

靈活的因子

每一個因子組最多只能選擇一個因子,來構(gòu)成一個規(guī)格。

舉個例子,假設(shè)鈑金噴漆欄目里面有一個商品叫做 "前保險杠",該商品有兩個因子組:

  • 漆類
    • 普通漆
    • 金屬漆
  • 鈑金
    • 普通鈑金
    • 復(fù)雜鈑金

那么一共可以有8種規(guī)格:

  • 普通漆
  • 金屬漆
  • 普通鈑金
  • 復(fù)雜鈑金
  • 普通漆-普通鈑金
  • 普通漆-復(fù)雜鈑金
  • 金屬漆-普通鈑金
  • 金屬漆-復(fù)雜鈑金

在實際情況中,在配置的時候可能會刪除幾種不需要的規(guī)格,比如說刪除規(guī)格:"普通鈑金" 、 "復(fù)雜鈑金" 和 "金屬漆-復(fù)雜鈑金"。

對用戶來說,只選一個 "漆類" 可以構(gòu)成一個規(guī)格,也可以再搭配一個 "鈑金" 構(gòu)成另一種規(guī)格。只是選擇 "金屬漆" 的時候只能選擇 "普通鈑金",不能選擇 "復(fù)雜鈑金"。

默認選中

在顯示選擇規(guī)格彈窗的時候,默認勾選一組可以構(gòu)成一個規(guī)格的因子,盡量讓構(gòu)成選中的因子位于因子組中靠前的位置。

舉個例子,假設(shè)后一個商品 "后保險杠" ,它也有 "漆類" 和 "鈑金" 兩個因子組,刪除規(guī)格:"普通漆" 和 "普通漆-普通鈑金",可用規(guī)格如下:

  • 金屬漆
  • 普通鈑金
  • 復(fù)雜鈑金
  • 普通漆-復(fù)雜鈑金
  • 金屬漆-普通鈑金
  • 金屬漆-復(fù)雜鈑金

那么默認選中的就是 "普通漆-復(fù)雜鈑金"。

后端返回數(shù)據(jù)

后臺返回的一個商品的 JSON 數(shù)據(jù)結(jié)構(gòu)如下:

{
  "id": 1,
  "name": "前保險杠",
  "factorGroupList": [
    {
      "id": 10,
      "name": "漆類",
      "list": [
        {
          "id": 11,
          "name": "普通漆"
        }
      ]
    }
  ],
  "specificationList": [
    {
      "id": 101,
      "originalPrice": 1000,
      "discountPrice": 1000,
      "factorIdList": [
        11
      ]
    }
  ]
}
  • factorGroupList 是一個數(shù)組,里面的每一項都代表該商品的一個因子組,比如 "漆類"。在因子組中,list 字段中是具體的因子,比如:"普通漆"、"金屬漆"。
  • specificationList 是一個數(shù)組,里面的每一項都代表該商品的一種可用規(guī)格。
    • originalPrice 是原價,discountPrice 是折扣價,如果折扣價等于原價,則沒有折扣價,這里的單位為分。
    • factorIdList 是一個數(shù)組,里面的每一項代表著該規(guī)格所包含的因子id。

算法思路

因子一共有三種狀態(tài),所以在 FactorEntity 里創(chuàng)建了一個枚舉類 Status,并新增一個 Status 類型的 status 字段來標識當前因子的狀態(tài)。

/**
 * 因子狀態(tài)
 */
enum class Status {
    /**
     * 不可用
     */
    DISABLED,
    /**
     * 可用
     */
    AVAILABLE,
    /**
     * 選中
     */
    SELECTED
}

考慮到選擇規(guī)格的邏輯其實和UI邏輯相對獨立,所以專門新建了一個類 ChooseSpecificationCalculator 來處理選擇規(guī)格的計算。

初始化

創(chuàng)建一個列表來裝當前選中的因子,在選中和取消選中因子的時候?qū)υ摿斜磉M行增加或者刪除因子的操作;

// 選中因子列表
private val selectedFactorList = ArrayList<FactorEntity>()

FactorEntity 中新增一個字段 specificationList 放所有包含該因子的規(guī)格,這樣后續(xù)處理該因子的時候不必每次都遍歷整個規(guī)格列表,只需要在初始化的時候?qū)γ恳粋€因子遍歷一次整個規(guī)格列表就好。

product.factorGroupList.forEach { factorGroup ->
    factorGroup.list.forEach { factor ->
        factor.specificationList.addAll(product.specificationList.filter {
            it.factorIdList.contains(factor.id)
        })
    }
}

對每一個因子組遍歷一次,刪除 specificationList 為空的因子,然后再遍歷因子組列表,刪除因子數(shù)為空的因子組,給剩余的每一個因子 status 賦值為 AVAILABLE

// 移除沒有規(guī)格的因子
product.factorGroupList.forEach { factorGroup ->
    factorGroup.list = factorGroup.list.filter {
        it.specificationList.isNotEmpty()
    } as ArrayList<FactorEntity>
}

// 移除沒有因子的因子組
product.factorGroupList = product.factorGroupList.filter {
    it.list.isNotEmpty()
} as ArrayList<FactorGroupEntity>

// 默認所有因子可用(經(jīng)過上一步的篩選,剩下的因子至少包含一個規(guī)格,在沒有選中因子的時候,所有的因子都是可用的)
product.factorGroupList.forEach { factorGroup ->
    factorGroup.list.forEach { factor ->
        factor.status = FactorEntity.Status.AVAILABLE
    }
}

注意:這里之所以這么處理是因為需求要求刪除無用的因子,如果你們的需求需要保留不可用因子的話,可以判斷 specificationList 是否為空,為空的 status 賦值為 DISABLED,不為空的 status 賦值為 AVAILABLE。

選中因子

只處理處理可用狀態(tài)的因子,如果當前因子組存在選中因子,則將該因子變?yōu)榭捎脿顟B(tài)。然后將本次操作的因子狀態(tài)置為選中狀態(tài),已選中因子列表加入該因子,然后更新所有因子的狀態(tài)。

fun selectedFactor(factor: FactorEntity) {
    if (factor.status != FactorEntity.Status.AVAILABLE) {
        return
    }
    
    // 如果當前因子組存在選中因子,則將該因子變?yōu)榭捎脿顟B(tài)
    val factorGroup = product.factorGroupList.find { it.list.contains(factor) }!!
    factorGroup.list.find { it.status == FactorEntity.Status.SELECTED }?.let {
        removeSelectedFactor(it)
    }
    
    addSelectedFactor(factor)
    updateAllFactorStatus(factor)
}

取消選中因子

只處理處理選中狀態(tài)的因子,將因子狀態(tài)置為可用狀態(tài),已選中因子列表移除該因子,然后更新所有因子的狀態(tài)。

fun unselectedFactor(factor: FactorEntity) {
    if (factor.status != FactorEntity.Status.SELECTED) {
        return
    }
    
    removeSelectedFactor(factor)
    updateAllFactorStatus(factor)
}

更新因子狀態(tài)

這里是算法的核心部分,這里拿判斷因子 A 的狀態(tài)為例。

使用已選中因子列表的因子 id 加上因子 Aid 構(gòu)成一個集合 B,然后去遍歷因子 AspecificationList 中的規(guī)格,看是否能找到一個規(guī)格滿足集合 B 是其 factorIdList 的子集。

在這里,判斷一個集合是另一個集合的子集采用的是 containsAll 方法,你如果需要考慮優(yōu)化的話,可以參考淘寶團隊的sku組合查詢算法探索。

val find = factor.specificationList.find { specification ->
    specification.factorIdList.containsAll(factorIdList)
}
if (find == null) {
    factor.status = FactorEntity.Status.DISABLED
} else {
    factor.status = FactorEntity.Status.AVAILABLE
}

獲得選中規(guī)格

如果選中因子列表為空,則返回 null。

拿出選中因子列表中的第一個因子,遍歷該因子的 specificationList 字段中的每一個規(guī)格,如果發(fā)現(xiàn)有規(guī)格滿足其字段 factorIdList 的數(shù)量等于 selectedFactorList 的數(shù)量 ,selectedFactorList 構(gòu)成的因子 id 集合是其factorIdList 的子集,則該規(guī)格為當前因子構(gòu)成的規(guī)格。

如果遍歷完后還找不到對應(yīng)的規(guī)格,則返回 null。

fun getSelectedSpecification(): SpecificationEntity? {
    if (selectedFactorList.isEmpty()) {
        return null
    }
    
    // 對包含選中因子列表中第一個的因子的規(guī)格進行遍歷,查看當前選中的因子列表是否能構(gòu)成一個規(guī)格
    return selectedFactorList.first().specificationList.find { specification ->
        specification.factorIdList.size == selectedFactorList.size &&
                specification.factorIdList.containsAll(selectedFactorIdList)
    }
}

默認選中

一開始的時候,覺得這里是一個難點,但是等把上面的算法都實現(xiàn)后,回過頭來考慮這個問題的時候,問題已經(jīng)變得很簡單了。

直接遍歷因子組列表,從因子組中找到第一個可用的因子,選中它,如果能構(gòu)成一個規(guī)格則結(jié)束,否則繼續(xù)選中下一個因子組中的第一個可用因子,直到構(gòu)成一個規(guī)格。

run breaking@{
    product.factorGroupList.forEach forEach1@{ factorGroup ->
        factorGroup.list.forEach forEach2@{ factor ->
            if (factor.status == FactorEntity.Status.AVAILABLE) {
                // 選中該因子
                selectedFactor(factor)
                if (getSelectedSpecification() != null) {
                    // 找到規(guī)格,結(jié)束尋找
                    return@breaking
                }
                return@forEach1
            }
        }
    }
}

總結(jié)

這個算法和淘寶等商城App的選擇規(guī)格算法雖然有一定的差異性,但是把刪除的規(guī)格當作已經(jīng)賣完的規(guī)格,再加上規(guī)格數(shù)量,那么就和淘寶等商城App的邏輯差不多了。

項目源碼:choose-specification

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容