Изучайте каждую минуту работы алгоритма внешнего интерфейса (мультистандартный выбор товара)

внешний интерфейс

sku.png

Я полагаю, что каждый, кто увидит эту картинку, поймет, о чем мы будем говорить в этой статье, правильно — о решении проблемы выбора нескольких спецификаций продуктов.

В последнее время на Наггетсах я видел, что все изучают проблему «множественных спецификаций товаров», таких как晨曦大佬изСложен ли полный алгоритм перестановки артикулов для электронной коммерции? Изучите эту процедуру и полностью овладейте перестановкой и комбинацией.В этой статье большой парень написал, как добитьсяskuПолная аранжировка, идея очень хорошая, но она не тесно связана с бизнес-сценарием. Реальный бизнес-сценарий заключается в том, что нам нужно выяснить оставшиеся необязательные спецификации и необязательные спецификации в соответствии со спецификациями, каждый раз выбираемыми пользователем, и отображать их на странице внешнего интерфейса: то есть затенить не- дополнительные характеристики, то есть следующий эффект (можно нажатьздесьОцените конечный результат):

sku.gif

Итак, сегодня мы поговорим о решении этой проблемы.Это трудно объяснить внятно, но я считаю, что после прочтения этой статьи,skuЭто больше не будет вас беспокоить.

что такое ску

Прежде чем представить конкретное решение, давайте сначала представим, что такоеsku? skuтермин в бухгалтерском учете, известный как库存单元. говорить на английском? Проще говоря, у нас есть каждый вариант спецификации на картинке выше 👆, например深空灰色,64G, является спецификацией (sku). товар иskuОн относится к отношениям «один ко многим», то есть мы можем выбрать несколькоskuЧтобы определить конкретный продукт:

商品.png

Деловая сцена

Можно сказать, что пока вы работаете с продуктами, связанными с электронной коммерцией, такими как приложения для покупок, веб-сайты для покупок и т. д. Каждый продукт соответствует нескольким спецификациям, и пользователи могут выбирать свои собственные в соответствии с различные комбинации спецификаций желаемый продукт. Мы сами часто используем эту функцию в своей жизни, но она настолько проста, что поставила в тупик многих мелких партнеров.

То же самое верно и для автора. Когда я впервые столкнулся с этой сценой, автор подумал, что я должен быть в состоянии сделать это за один день и закончить работу идеально, но я был еще слишком молод. , В конце концов, нет никакого способа , но мы можем только стиснуть зубы и использовать метод насильственного решения (то есть непрерывного зацикливания) для решения задачи.Временная сложность очень высока, и она достигаетO(m*n)то естьO(n²), этот способ реализации на самом деле не невозможен (просто беги),Верно. Но позже я обнаружил, что когда у продукта много спецификаций, а производительность устройства пользователя не так хороша, то этот метод реализации приведет к слишком долгому времени работы, что показано на странице: когда пользователь нажимает «Характеристики», появляется будут явные лаги, ну как же так, клиенты теряются, как шеф может купить феррари🤔️? Поэтому я снова начал исследования.

картина

Случайно, когда я просматривал Zhihu, я увидел, что кто-то обсуждал,это数据结构, внезапная вспышка вдохновения, кажется, что наш多规格选择Вы также можете использовать графики в качестве метода решения, и, попробовав, это действительно осуществимо. А временная сложность всегоO(n), просто идеально. Итак, давайте представим,что? Верю в университет数据结构与算法Все мои одноклассники должны были это знать, но они должны были забыть об этом.

что такое график

На самом деле это раздел математики. Он делает снимки в качестве объектов исследования. Граф в теории графов представляет собой график, состоящий из ряда заданных точек и линии, соединяющей две точки. Этот вид графика обычно используется для описания определенной связи между определенными вещами. Линии соответствующих двух вещей имеют следующие отношения:

图.jpg

Обычно выделяют следующие категории:

  • Делятся на ориентированные и неориентированные графы.

  • Делятся на взвешенные и невзвешенные.

Ну, знать эти две концепции почти то же самое.Конечно, если вы хотите узнать больше о диаграммах и концепциях, см.здесь

Итак, нам нужно использовать неориентированный граф, что такое неориентированный граф, например:

无向图.png

Если между двумя вершинами есть связь, это означает, что две вершины взаимосвязаны. Маленькие друзья могут быть сбиты с толку, когда увидят это.Поскольку так много сказано, кажется, что это не имеет ничего общего с проблемой, которую мы хотим решить. Подумайте об этом сейчас, друзья: когда пользователи выбирают спецификации, не должно быть никакой последовательности. Предположим, что теперь мы рассматриваем каждую спецификацию как无向图один из顶点, мы можем использовать эти单项规格Спецификации комбинации, вы можете нарисовать картинку, как показано выше无向图.

матрица смежности

Предположим, мы нарисовали неориентированный граф, как показано выше 👆, тогда как мы можем представить этот граф с помощью нашего кода? используется здесь邻接矩阵

邻接矩阵На самом деле, это понятие в «Линейной алгебре». Я думаю, что многие друзья не будут незнакомы. В коде мы выражаем его с помощьюn x n2D-массив для абстрагирования матрицы смежности. Представим приведенный выше неориентированный граф 👆 с помощью матрицы смежности (двумерного массива):

邻接矩阵.png

Очевидно, что если две вершины взаимодействуют друг с другом (есть связь), то значение их соответствующего индекса равно 1, в противном случае — 0.

Что ж, следующий шаг — начать с высокой энергии, пожалуйста, следите за ним внимательно.

Предположим, теперь у нас есть следующий список спецификаций:

specList: [
  { title: "颜色", list: ["红色", "紫色"] },
  { title: "套餐", list: ["套餐一", "套餐二"] },
  { title: "内存", list: ["64G", "128G", "256G"] },
];

Доступные комбинации спецификаций:

specCombinationList: [
    { id: "1", specs: ["紫色", "套餐一", "64G"] },
    { id: "2", specs: ["紫色", "套餐一", "128G"] },
    { id: "3", specs: ["紫色", "套餐二", "128G"] },
    { id: "4", specs: ["红色", "套餐二", "256G"] }
  ],

Во-первых, мы основывались наspecListЗнай: У нас есть»颜色", "套餐", "内存"Три категории спецификации. Есть红色,紫色,套餐一,套餐二,64G,128G,256Gэти индивидуальные характеристики. Каждая отдельная спецификация элемента действует как вершина, поэтому существуют следующие вершины:

顶点.png

Тогда мы согласноspecCombinationListМы можем знать, какие комбинации спецификаций являются необязательными. Что ж, надо начинать оформлять.

согласно с{ id: "1", specs: ["紫色", "套餐一", "64G"] },Мы можем нарисовать:

开始画图.png

Далее рисуем черпак по тыкве: мы можемspecCombinationListОстальные данные отображаются следующим образом:

规格组合.png

Ну, у нас естьspecCombinationList(то есть необязательная комбинация спецификаций) завершает ненаправленный рисунок нашей спецификации. Теперь смоделируем выбор пользователя:

specCombinationList: [
    { id: "1", specs: ["紫色", "套餐一", "64G"] },
    { id: "2", specs: ["紫色", "套餐一", "128G"] },
    { id: "3", specs: ["紫色", "套餐二", "128G"] },
    { id: "4", specs: ["红色", "套餐二", "256G"] }
  ],

Предположим, что пользователь сначала выбирает紫色,согласно сspecCombinationList, мы обнаруживаем套餐一,套餐二,64G,128Gне является обязательным, на этот раз мы обнаружили проблему: очевидно, с紫色тот же уровень红色Это на самом деле необязательно. Так что эта картина на самом деле еще не закончена. Таким образом, спецификации того же типа, если они находятся в необязательных спецификациях, на самом деле должны быть подключены:

全部规格.png

Хорошо, неориентированный граф готов, теперь мы сопоставляем его с邻接矩阵Выше (на этом этапе друзьям настоятельно рекомендуется взять бумагу и карандаш, чтобы рисовать вместе):

顶点邻接矩阵.png

На данный момент, поздравляю, вы поняли больше половины 👏.

Что ж, на данный момент мы можем объявить окончательный вывод:

  • Когда пользователь впервые заходит на эту страницу, ему доступны все характеристики:

全部可选.png

  • Когда пользователь выбирает вершину, находятся все параметры для текущей вершины (то есть вершины, значение столбца которой равно 1):

选择一项.png

  • При выборе нескольких вершин варианты — это пересечение соседних точек каждой вершины: (то есть пересечение столбцов, в которых расположены выбранные вершины)

多个顶点交集.png

Код

Серьезно, я думаю, что мои друзья понимают приведенные выше объяснения 👆, я думаю, вы уже полностью понимаете, как этого достичь"多规格选择"Алгоритм. Но есть поговорка: только говори, не тренируйся фальшивой рукой! Тогда давайте вместе посмотрим, как это реализовать с помощью кода, интерфейсный фреймворк, используемый автором, здесьreact, я понял мысль, какой фреймворк используется тот же.

Вот идея сначала:

1. Согласно списку спецификаций (specList) для создания матрицы смежности (массива)

2. В соответствии с дополнительной комбинацией спецификаций (specCombinationList) заполните значение вершины

3. Получить все необязательные вершины, а затем заполнить значения вершин одного уровня в соответствии с необязательными вершинами

Создайте матрицу смежности

Во-первых, нам нужно предоставить класс для создания матрицы смежности. Матрица смежности, вам сначала нужно передать массив вершин:vertex, вам нужен массив, чтобы удерживать матрицу соседних:adjoinArray. Как мы упоминали выше, этот класс также должен обеспечивать вычисление并集а также交集Методы:

export type AdjoinType = Array<string>;

export default class AdjoinMatrix {
  vertex: AdjoinType; // 顶点数组
  quantity: number; // 矩阵长度
  adjoinArray: Array<number>; // 矩阵数组

  constructor(vertx: AdjoinType) {
    this.vertex = vertx;
    this.quantity = this.vertex.length;
    this.adjoinArray = [];
    this.init();
  }
  // 初始化数组
  init() {
    this.adjoinArray = Array(this.quantity * this.quantity).fill(0);
  }

  /*
   * @param id string
   * @param sides Array<string>
   *  传入一个顶点,和当前顶点可达的顶点数组,将对应位置置为1
   */
  setAdjoinVertexs(id: string, sides: AdjoinType) {
    const pIndex = this.vertex.indexOf(id);
    sides.forEach((item) => {
      const index = this.vertex.indexOf(item);
      this.adjoinArray[pIndex * this.quantity + index] = 1;
    });
  }

  /*
   * @param id string
   * 传入顶点的值,获取该顶点的列
   */
  getVertexCol(id: string) {
    const index = this.vertex.indexOf(id);
    const col: Array<number> = [];
    this.vertex.forEach((item, pIndex) => {
      col.push(this.adjoinArray[index + this.quantity * pIndex]);
    });
    return col;
  }

  /*
   * @param params Array<string>
   * 传入一个顶点数组,求出该数组所有顶点的列的合
   */
  getColSum(params: AdjoinType) {
    const paramsVertex = params.map((id) => this.getVertexCol(id));
    const paramsVertexSum: Array<number> = [];
    this.vertex.forEach((item, index) => {
      const rowtotal = paramsVertex
        .map((value) => value[index])
        .reduce((total, current) => {
          total += current || 0;
          return total;
        }, 0);
      paramsVertexSum.push(rowtotal);
    });
    return paramsVertexSum;
  }

  /*
   *  @param params Array<string>
   * 传入一个顶点数组,求出并集
   */
  getCollection(params: AdjoinType) {
    const paramsColSum = this.getColSum(params);
    let collections: AdjoinType = [];
    paramsColSum.forEach((item, index) => {
      if (item && this.vertex[index]) collections.push(this.vertex[index]);
    });
    return collections;
  }

  /*
   *  @param params Array<string>
   * 传入一个顶点数组,求出交集
   */
  getUnions(params: AdjoinType) {
    const paramsColSum = this.getColSum(params);
    let unions: AdjoinType = [];
    paramsColSum.forEach((item, index) => {
      if (item >= params.length && this.vertex[index]) unions.push(this.vertex[index]);
    });
    return unions;
  }
}

Имея этот класс, мы можем затем создать новый специально для генерации商品多规格选择класс, который наследуется отAdjoinMatrix.

Создайте多规格选择матрица смежности

Наша матрица смежности для выбора нескольких спецификаций должна предоставить метод для запроса дополнительных вершин:getSpecscOptions

import AdjoinMatrix from "./adjoin-martix";
import { AdjoinType } from "./adjoin-martix";
import { SpecCategoryType, CommoditySpecsType } from "../redux/reducer/spec-reducer";

export default class SpecAdjoinMatrix extends AdjoinMatrix {
  specList: Array<CommoditySpecsType>;
  specCombinationList: Array<SpecCategoryType>;

  constructor(specList: Array<CommoditySpecsType>, specCombinationList: Array<SpecCategoryType>) {
    super(specList.reduce((total: AdjoinType, current) => [...total, ...current.list], []));
    this.specList = specList;
    this.specCombinationList = specCombinationList;
    // 根据可选规格列表矩阵创建
    this.initSpec();
    // 同级顶点创建
    this.initSameLevel();
  }

  /**
   * 根据可选规格组合填写邻接矩阵的值
   */
  initSpec() {
    this.specCombinationList.forEach((item) => {
      this.fillInSpec(item.specs);
    });
  }
  // 填写同级点
  initSameLevel() {
    // 获得初始所有可选项
    const specsOption = this.getCollection(this.vertex);
    this.specList.forEach((item) => {
      const params: AdjoinType = [];
      // 获取同级别顶点
      item.list.forEach((value) => {
        if (specsOption.includes(value)) params.push(value);
      });
      // 同级点位创建
      this.fillInSpec(params);
    });
  }
  /*
   * 传入顶点数组,查询出可选规格
   * @param params
   */
  getSpecscOptions(params: AdjoinType) {
    let specOptionCanchoose: AdjoinType = [];
    if (params.some(Boolean)) {
      // 过滤一下选项
      specOptionCanchoose = this.getUnions(params.filter(Boolean));
    } else {
      // 所有可选项
      specOptionCanchoose = this.getCollection(this.vertex);
    }
    return specOptionCanchoose;
  }

  /*
   * @params
   * 填写邻接矩阵的值
   */
  fillInSpec(params: AdjoinType) {
    params.forEach((param) => {
      this.setAdjoinVertexs(param, params);
    });
  }
}

рендеринг страницы

Что ж, на данный момент мы уже можем использовать эти два класса на странице:

import React, { useState, useMemo } from "react";
import { useSelector } from "react-redux";
import { RootState } from "../redux/reducer/root-reducer";
import SpecAdjoinMatrix from "../utils/spec-adjoin-martix";
import "./spec.css";
const classNames = require("classnames");

const Spec: React.FC = () => {
  const { specList, specCombinationList } = useSelector((state: RootState) => state.spec);
  // 已选择的规格,长度为规格列表的长度
  const [specsS, setSpecsS] = useState(Array(specList.length).fill(""));

  // 创建一个规格矩阵
  const specAdjoinMatrix = useMemo(() => new SpecAdjoinMatrix(specList, specCombinationList), [specList, specCombinationList]);
  // 获得可选项表
  const optionSpecs = specAdjoinMatrix.getSpecscOptions(specsS);

  const handleClick = function(bool: boolean, text: string, index: number) {
    // 排除可选规格里面没有的规格
    if (specsS[index] !== text && !bool) return;
    // 根据text判断是否已经被选中了
    specsS[index] = specsS[index] === text ? "" : text;
    setSpecsS(specsS.slice());
  };

  return (
    <div className="container">
      {specList.map(({ title, list }, index) => (
        <div key={index}>
          <p className="title">{title}</p>
          <div className="specBox">
            {list.map((value, i) => {
              const isOption = optionSpecs.includes(value); // 当前规格是否可选
              const isActive = specsS.includes(value); // 当前规格是否被选
              return (
                <span
                  key={i}
                  className={classNames({
                    specOption: isOption,
                    specAction: isActive,
                    specDisabled: !isOption,
                  })}
                  onClick={() => handleClick(isOption, value, index)}
                >
                  {value}
                </span>
              );
            })}
          </div>
        </div>
      ))}
    </div>
  );
};

export default Spec;

Ладно, работа окончена, если у вас есть маленький напарник, который хочет увидеть эффект, можете проверитьздесь, Кроме того, я загрузил код на Gayhub, если кто-то из друзей хочет вытащить код на местный, чтобы увидеть, то, пожалуйста, нажмитеsku исходный код git.

Суммировать

Практика доказала, что вещи, полученные в колледже, действительно полезны. мы проходим, решено商品多规格选择эта проблема. При решении необязательных спецификаций изменена временная сложность по сравнению с исходной.O(n²)сталO(n). Однако стоит упомянуть, что邻接矩阵хранить, пространственная сложность становитсяO(n²), а также есть проблема траты места, ноНаверняка больше, чем邻接矩阵Этот метод хранения мы также можем использовать链表хранить, Маленькие друзья могут попробовать. Также, если используется链表Для хранения графа пространственная сложность будет ниже, но временная сложность будет выше.Как выбрать, зависит от веса мелких партнеров.

Удовлетворяя этот спрос в будущем, друзья обязательно поймут это за считанные минуты и уйдут с работы пораньше.

Я чувствую себя не так, программировать нелегко, если вы считаете, что эта статья полезна для вас, пожалуйста, поставьте лайк! !