This article was originally published at: github.com/bigo-fronte… Welcome to follow and reprint.

preface

In e-commerce, what is a SKU?

To put it simply, for example, a skirt has red and white colors and sizes XL and XXL. We choose red and XL. Then the combination of this specification is an SKU.

Spu is the dress. Keep it separate.

When I first came into contact with this SKU selector, I thought it was just a simple combination of several tabs and then passed to the back end, but I didn’t know this SKU selector until the actual development. The back end tells you what specifications there are, such as colors and codes, and then tells you how to combine them:

this.product.skuAttrSortedList = [
  {
    "attrName": "Color"."attrNameId": "1"."attrValues": [
      "Red"."White"] {},"attrName": "Code number"."attrNameId": "2"."attrValues": [
      "XL"."XXL"]}];this.product.skuList = [
  {
    "showPrice": "6.00"."favType": 1."skuId": "1234"."originalSalePrice": "12.01"."salePrice": "11.01"."discount": "40%"."saleCurrency": "USD"."skuMinVolume": 1."skuIncreaseVolume": 1."maxBuyVolume": 99."skuStatus": 1."skuImg": "https://s4.forcloudcdn.com/item/images/dmc/7cdb5ab0-88b5-45a4-a5af-d683ccbd9336-750x1000.jpeg_750x0c.jpeg"."skuAttrList": [{"attrNameId": "12"."attrName": "Color"."attrValue": "Red"
      },
      {
        "attrNameId": "13"."attrName": "Code number"."attrValue": "XL"}]}, {"showPrice": "6.00"."favType": 1."skuId": "2345"."originalSalePrice": "12.01"."salePrice": "11.01"."saleCurrency": "USD"."discount": "40%"."skuMinVolume": 1."skuIncreaseVolume": 1."maxBuyVolume": 99."skuStatus": 1."skuImg": "https://s4.forcloudcdn.com/item/images/dmc/4a085065-0f92-4406-a55e-ae4150b91def-779x974.jpeg_750x0c.jpeg"."skuAttrList": [{"attrNameId": "12"."attrName": "Color"."attrValue": "Red"
      },
      {
        "attrNameId": "13"."attrName": "Code number"."attrValue": "XXL"}]}, {"skuId": "3456"."originalSalePrice": "12.01"."salePrice": "11.01"."saleCurrency": "USD"."discount": "40%"."skuMinVolume": 1."skuIncreaseVolume": 1."maxBuyVolume": 99."skuStatus": 1."skuImg": "https://s4.forcloudcdn.com/item/images/dmc/4a085065-0f92-4406-a55e-ae4150b91def-779x974.jpeg_750x0c.jpeg"."skuAttrList": [{"attrNameId": "12"."attrName": "Color"."attrValue": "White"
      },
      {
        "attrNameId": "13"."attrName": "Code number"."attrValue": "XXL"}}]];Copy the code

The only thing I can think of is loop cracking, where the time complexity gets bigger and bigger as the specs get bigger and bigger. It works, but JS execution on some low-end machines will lag.

I once found an article in the wechat public account, which talked about how to use matrix to solve the SKU algorithm problem. At that time, I looked at it briefly, wow, what a clever way! Now stalar e-commerce project can just be put into use, just do it!

The product requirement is like, when I select one specification, the other optional specifications can be clicked, the non-optional specifications can be grated out and the optional optional is based on the combination list that comes back from the back end, like SkuList has only one white SKU (white, XXL), so only XXL can be selected from the code number. Then why is there a red option? Of course, users want to change the color to see you have to allow ~

So how do we implement this filter with matrices?

Let’s draw a graph and use the intersection of matrix columns to get the remaining optional specifications. Let’s first get all the optional specifications and define two computational attributes:

vertex() {
      return (this.product.skuAttrSortedList || []).reduce((total, current) = > [...total, ...(current.attrValues || [])], []);
},
len() {
      return this.vertex.length;
}
Copy the code

The next step is to use this vertex to create an adjacency matrix with all zeros, which can be represented by a one-dimensional array.

this.adjoinArray = Array(this.len * this.len).fill(0);
Copy the code

So we have the following matrix:

If this. AdjoinArray [4] refers to B3, which index does B3 correspond to? We can figure out how to get to the index, that is, how to map the position of each point of the matrix to the index of a one-dimensional array.

Next we have to define an array specsS to hold the selected specifications for the corresponding property

specsS: [];
Copy the code

And initialize it

this.specsS = Array(this.product.skuAttrSortedList.length).fill(' ');
Copy the code

We need to fill the points that can be combined with 1. For example, we take red as the point, and according to the combination list of skuList, we can get XL and XXL reachable points, as well as white points of the same level (a method is needed to fill the same level points later).By using the symmetry of the adjacency matrix, it becomes

We need to turn the skuList list into a matrix fill 1 display. How do we do this in code? First, define a method for filling in 1

setAdjoinVertexs(side, sides) {
      let pIndex = ' ';
      for (let i = 0; i < this.vertex.length; i += 1) {
        if (side === this.vertex[i]) {
          pIndex = i;
          break;
        }
      }
      sides.forEach((item) = > {
        let index = ' ';
        for (let i = 0; i < this.vertex.length; i += 1) {
          if (item === this.vertex[i]) {
            index = i;
            break; }}this.adjoinArray[pIndex * this.len + index] = 1;
      });
    }
Copy the code

If we pass side = ‘red’, sides = [‘ red ‘,’XL’], the first loop actually finds the red row in the matrix, and pIndex gets 0

The second loop is to find the reachable column in the matrix, for example, find the column XL with index 2

If we find the row that started, the reachable column, then we can fill in their intersection with 1

Remember I asked you to figure out how to map the points of a matrix to the index of an array? So it’s going to be pIndex * this.len + index

Know the principle, hurry up the code:

initSpec() {
      this.specCombinationList.forEach((item) = > this.fillInSpec(item.skuAttrValueList));
    },
fillInSpec(params) {
      params.forEach((param) = > {
        this.setAdjoinVertexs(param, params);
      });
    },
setAdjoinVertexs(side, sides) {
      let pIndex = ' ';
      for (let i = 0; i < this.vertex.length; i += 1) {
        if (side === this.vertex[i]) {
          pIndex = i;
          break;
        }
      }
      sides.forEach((item) = > {
        let index = ' ';
        for (let i = 0; i < this.vertex.length; i += 1) {
          if (item === this.vertex[i]) {
            index = i;
            break; }}this.adjoinArray[pIndex * this.len + index] = 1;
      });
}
Copy the code

(Note: specCombinationList in initSpec is the processed skuList combinationlist)

But actually we chose red, and white is still optional, so we need to fill in sibling points

initSameLevel() {
      this.product.skuAttrSortedList.forEach((item) = > {
        const params = [];
        // Get vertices of the same level
        item.attrValues.forEach((val) = > {
          if (this.optionSpecs.includes(val)) params.push(val);
        });
        // Peer point creation
        this.fillInSpec(params);
      });
    }
Copy the code

What is the optionSpecs array here? In fact, the reachable points group, also known as the optional specification, is the purpose of our algorithm

So we need to define a calculated property, optionSpecs, that changes dynamically based on the user’s choices. And we said, well, what’s the initial value?

When we have executed initSpec, we know what the initial optional specifications areFrom this matrix we can find out what the initial optional specifications are.

How to find? We can take the union of all the columns, so if we take the union of all the columns in the matrix above we get [1, 1, 1, 1] which is, as long as one of the columns in that row is 1, the union is going to be 1, and the union is going to be 1 because the specifications of that row are optional at the beginning, so we can see that all four specifications are clickable at the beginning.

So how does this code evaluate the union of the columns so we’re going to treat each column as an array, and the x-th row union is going to be the index of each of the 7 arrays, x minus 1, and the result is going to be greater than or equal to 1.

// Get the column in which a specification is located
getVertexCol(param) {
      let idx = ' ';
      for (let i = 0; i < this.vertex.length; i += 1) {
        if (param === this.vertex[i]) {
          idx = i;
          break; }}const col = [];
      this.vertex.forEach((item, pIndex) = > {
        col.push(this.adjoinArray[idx + this.len * pIndex]);
      });
      return col;
    },
// Get the sum of each row and put it in an array
    getColSum(params) {
      const paramsVertex = params.map((param) = > this.getVertexCol(param));
      const paramsVertexSum = [];
      this.vertex.forEach((item, index) = > {
        const rowTotal = paramsVertex
          .map((value) = > value[index])
          .reduce((total, current) = > {
            let newTotal = total;
            newTotal += current || 0;
            return newTotal;
          }, 0);
        paramsVertexSum.push(rowTotal);
      });
      return paramsVertexSum;
    },
// Find the paramsVertexSum index (value 1) in the paramsVertexSum array and match it in the paramsVertexSum array
    getUnion(params) {
      const paramsColSum = this.getColSum(params);
      const union = [];
      paramsColSum.forEach((item, index) = > {
        if (item && this.vertex[index]) union.push(this.vertex[index]);
      });
      return union;
    }
Copy the code

We had the list of optional specs for initialization, the array of optionSpecs = four specs mentioned above, and we were able to fill in the siblings because we had to rule out property values that weren’t optional in the first place. The final initialized matrix is as follows:

When the user clicks on a specification, how do we get the remaining optional specifications?When we select the red column, we can know that the 1 has red, white, XL and XXL, then the remaining optional specifications are red, white, XL and XXL.

Since this example only has color and size, as long as we check the color column, we can see what size options areIf there is another specification, such as a set mealTherefore, it is not difficult to see that after we choose jersey red and XL, the remaining options can be obtained by finding the intersection. Focus on each row. When each cell in this row is 1, the result of the intersection of this row is 1.Therefore, the intersection result of red column and XL column is [1, 0, 1, 1, 1, 1], which can be selected as red, XL, XXL, package 1, package 2.

In fact, the choice of three specifications can not be so simple intersection, I will be in the second chapter for you to explain, but the principle is the same, the method of the current chapter only applies to two specifications!! The second link:

The skU selector is based on the matrix algorithm, which is that each time the user selects an attribute value, the user can dynamically obtain an array of remaining optional attributes

getIntersection(params) {
      const paramsColSum = this.getColSum(params);
      const intersection = [];
      paramsColSum.forEach((item, index) = > {
        if (item >= params.length && this.vertex[index]) intersection.push(this.vertex[index]);
      });
      return intersection;
    }
Copy the code

If the sum is greater than or equal to the number of selected columns (item >= params.length), the intersection is 1. The specification for the row whose intersection is 1 is pushed into the remaining optional specification array.

This way we can perfect the method we used to get the optional array of attributes called optionSpecs

computed: {
     optionSpecs() {  
 	return this.getSpecOptions(this.specsS); }},methods: {
    getSpecOptions(params) {
      let specOptionCanChoose = [];
      if (params.some(Boolean)) {
	specOptionCanChoose = this.getIntersection(params.filter(Boolean));
      } else {
        specOptionCanChoose = this.getUnion(this.vertex);
      }
      returnspecOptionCanChoose; }}Copy the code

Finally, we need two methods to determine whether the specification is optional and to determine whether the specification is selected

isActive(val, idx) {
      return this.specsS[idx] === val;
    },
    isOption(val) {
      return this.optionSpecs.includes(val);
    }
Copy the code

This allows us to disable non-optional specifications and highlight selected specifications.

The main reference above is an article written by the Gold Digger on how the SKU selection component is implemented using graph and matrix algorithms juejin.cn/post/684490… I just clear up his ideas and convert them into VUE components for practical application in e-commerce projects. Can you think about whether SKU components can be realized in the way of linked list? Haha?

Welcome everyone to leave a message to discuss, wish smooth work, happy life!

I’m bigO front. See you next time.