Я полагаю, что каждый, кто увидит эту картинку, поймет, о чем мы будем говорить в этой статье, правильно — о решении проблемы выбора нескольких спецификаций продуктов.
В последнее время на Наггетсах я видел, что все изучают проблему «множественных спецификаций товаров», таких как晨曦大佬
изСложен ли полный алгоритм перестановки артикулов для электронной коммерции? Изучите эту процедуру и полностью овладейте перестановкой и комбинацией.В этой статье большой парень написал, как добитьсяsku
Полная аранжировка, идея очень хорошая, но она не тесно связана с бизнес-сценарием. Реальный бизнес-сценарий заключается в том, что нам нужно выяснить оставшиеся необязательные спецификации и необязательные спецификации в соответствии со спецификациями, каждый раз выбираемыми пользователем, и отображать их на странице внешнего интерфейса: то есть затенить не- дополнительные характеристики, то есть следующий эффект (можно нажатьздесьОцените конечный результат):
Итак, сегодня мы поговорим о решении этой проблемы.Это трудно объяснить внятно, но я считаю, что после прочтения этой статьи,sku
Это больше не будет вас беспокоить.
что такое ску
Прежде чем представить конкретное решение, давайте сначала представим, что такоеsku
? sku
термин в бухгалтерском учете, известный как库存单元
. говорить на английском? Проще говоря, у нас есть каждый вариант спецификации на картинке выше 👆, например深空灰色
,64G
, является спецификацией (sku
). товар иsku
Он относится к отношениям «один ко многим», то есть мы можем выбрать несколькоsku
Чтобы определить конкретный продукт:
Деловая сцена
Можно сказать, что пока вы работаете с продуктами, связанными с электронной коммерцией, такими как приложения для покупок, веб-сайты для покупок и т. д. Каждый продукт соответствует нескольким спецификациям, и пользователи могут выбирать свои собственные в соответствии с различные комбинации спецификаций желаемый продукт. Мы сами часто используем эту функцию в своей жизни, но она настолько проста, что поставила в тупик многих мелких партнеров.
То же самое верно и для автора. Когда я впервые столкнулся с этой сценой, автор подумал, что я должен быть в состоянии сделать это за один день и закончить работу идеально, но я был еще слишком молод. , В конце концов, нет никакого способа , но мы можем только стиснуть зубы и использовать метод насильственного решения (то есть непрерывного зацикливания) для решения задачи.Временная сложность очень высока, и она достигаетO(m*n)
то естьO(n²)
, этот способ реализации на самом деле не невозможен (просто беги),Верно. Но позже я обнаружил, что когда у продукта много спецификаций, а производительность устройства пользователя не так хороша, то этот метод реализации приведет к слишком долгому времени работы, что показано на странице: когда пользователь нажимает «Характеристики», появляется будут явные лаги, ну как же так, клиенты теряются, как шеф может купить феррари🤔️? Поэтому я снова начал исследования.
картина
Случайно, когда я просматривал Zhihu, я увидел, что кто-то обсуждал图
,это数据结构
, внезапная вспышка вдохновения, кажется, что наш多规格选择
Вы также можете использовать графики в качестве метода решения, и, попробовав, это действительно осуществимо. А временная сложность всегоO(n)
, просто идеально. Итак, давайте представим图
,что图
? Верю в университет数据结构与算法
Все мои одноклассники должны были это знать, но они должны были забыть об этом.
что такое график
图
На самом деле это раздел математики. Он делает снимки в качестве объектов исследования. Граф в теории графов представляет собой график, состоящий из ряда заданных точек и линии, соединяющей две точки. Этот вид графика обычно используется для описания определенной связи между определенными вещами. Линии соответствующих двух вещей имеют следующие отношения:
图
Обычно выделяют следующие категории:
-
Делятся на ориентированные и неориентированные графы.
-
Делятся на взвешенные и невзвешенные.
Ну, знать эти две концепции почти то же самое.Конечно, если вы хотите узнать больше о диаграммах и концепциях, см.здесь
Итак, нам нужно использовать неориентированный граф, что такое неориентированный граф, например:
Если между двумя вершинами есть связь, это означает, что две вершины взаимосвязаны. Маленькие друзья могут быть сбиты с толку, когда увидят это.Поскольку так много сказано, кажется, что это не имеет ничего общего с проблемой, которую мы хотим решить. Подумайте об этом сейчас, друзья: когда пользователи выбирают спецификации, не должно быть никакой последовательности. Предположим, что теперь мы рассматриваем каждую спецификацию как无向图
один из顶点
, мы можем использовать эти单项规格
Спецификации комбинации, вы можете нарисовать картинку, как показано выше无向图
.
матрица смежности
Предположим, мы нарисовали неориентированный граф, как показано выше 👆, тогда как мы можем представить этот граф с помощью нашего кода? используется здесь邻接矩阵
邻接矩阵
На самом деле, это понятие в «Линейной алгебре». Я думаю, что многие друзья не будут незнакомы. В коде мы выражаем его с помощьюn x n
2D-массив для абстрагирования матрицы смежности. Представим приведенный выше неориентированный граф 👆 с помощью матрицы смежности (двумерного массива):
Очевидно, что если две вершины взаимодействуют друг с другом (есть связь), то значение их соответствующего индекса равно 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
эти индивидуальные характеристики. Каждая отдельная спецификация элемента действует как вершина, поэтому существуют следующие вершины:
Тогда мы согласноspecCombinationList
Мы можем знать, какие комбинации спецификаций являются необязательными. Что ж, надо начинать оформлять.
согласно с{ id: "1", specs: ["紫色", "套餐一", "64G"] },
Мы можем нарисовать:
Далее рисуем черпак по тыкве: мы можемspecCombinationList
Остальные данные отображаются следующим образом:
Ну, у нас естьspecCombinationList
(то есть необязательная комбинация спецификаций) завершает ненаправленный рисунок нашей спецификации. Теперь смоделируем выбор пользователя:
specCombinationList: [
{ id: "1", specs: ["紫色", "套餐一", "64G"] },
{ id: "2", specs: ["紫色", "套餐一", "128G"] },
{ id: "3", specs: ["紫色", "套餐二", "128G"] },
{ id: "4", specs: ["红色", "套餐二", "256G"] }
],
Предположим, что пользователь сначала выбирает紫色
,согласно сspecCombinationList
, мы обнаруживаем套餐一
,套餐二
,64G
,128G
не является обязательным, на этот раз мы обнаружили проблему: очевидно, с紫色
тот же уровень红色
Это на самом деле необязательно. Так что эта картина на самом деле еще не закончена. Таким образом, спецификации того же типа, если они находятся в необязательных спецификациях, на самом деле должны быть подключены:
Хорошо, неориентированный граф готов, теперь мы сопоставляем его с邻接矩阵
Выше (на этом этапе друзьям настоятельно рекомендуется взять бумагу и карандаш, чтобы рисовать вместе):
На данный момент, поздравляю, вы поняли больше половины 👏.
Что ж, на данный момент мы можем объявить окончательный вывод:
- Когда пользователь впервые заходит на эту страницу, ему доступны все характеристики:
- Когда пользователь выбирает вершину, находятся все параметры для текущей вершины (то есть вершины, значение столбца которой равно 1):
- При выборе нескольких вершин варианты — это пересечение соседних точек каждой вершины: (то есть пересечение столбцов, в которых расположены выбранные вершины)
Код
Серьезно, я думаю, что мои друзья понимают приведенные выше объяснения 👆, я думаю, вы уже полностью понимаете, как этого достичь"多规格选择
"Алгоритм. Но есть поговорка: только говори, не тренируйся фальшивой рукой! Тогда давайте вместе посмотрим, как это реализовать с помощью кода, интерфейсный фреймворк, используемый автором, здесь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²)
, а также есть проблема траты места, но图
Наверняка больше, чем邻接矩阵
Этот метод хранения мы также можем использовать链表
хранить图
, Маленькие друзья могут попробовать. Также, если используется链表
Для хранения графа пространственная сложность будет ниже, но временная сложность будет выше.Как выбрать, зависит от веса мелких партнеров.
Удовлетворяя этот спрос в будущем, друзья обязательно поймут это за считанные минуты и уйдут с работы пораньше.
Я чувствую себя не так, программировать нелегко, если вы считаете, что эта статья полезна для вас, пожалуйста, поставьте лайк! !