5 个回答

我还是强烈推荐写前端的朋友可以多用用 reduce,真的是太好用了,完整代码如下,提供了任意多个属性都被支持的能力,但是没有对值进行去重,这个应该是在组装 arr 时就进行的处理:

function combine(properties_, props_) {
  if (!properties_) {
    throw new Error('Properties 未提供')
  }
  const properties = Array.isArray(properties_)
    ? properties_.reduce((a, b) => ({
          ...a,
          [b.name]: [b.value].concat(a[b.name] || [])
      }), {})
    : properties_;
  const keys = Object.keys(properties)
  const props = Array.isArray(props_) ? [...props_] : keys.filter(key => properties[key].length > 0);
  const emptyObject = keys.filter(key => properties[key].length === 0).reduce((a, b) => ({...a, [b]: undefined}), {});
  const prop = props.pop();
  const objects = properties[prop].map(value => ({ ...emptyObject, [prop]: value}));
  return props.length === 0 ? objects : objects.reduce((a, b) => 
    a.concat(
      combine(properties, props).reduce((x, y) => 
          [...x, {...b, ...y}],
          []
        )
    ),
    []
  )
}

console.log(JSON.stringify(combine([
  { name: "color", value: "白色" },
  { name: "color", value: "黑色" },
  { name: "size", value: "x" },
  { name: "size", value: "M" },
]), null, 2));

这个应该是一个可以自定义数据字段的商城类的系统里面的,数组中每一项都是某一个类型定义的属性,处理逻辑基本上就是两步:

  1. 找到所有可用的属性名称以及该属性所有可用的值
  2. 按值不同进行组合
const arr = [
  { name: "color", value: "白色" },
  { name: "color", value: "黑色" },
  { name: "size", value: "x" },
  { name: "size", value: "M" },
];

// 找到所有可用的属性名称以及该属性所有可用的值
const properties = arr.reduce((a, b) => ({
    ...a,
    [b.name]: [
        // 如果 a[b.name] 已经存在,那就把当前的值都继承下来
        ...(Array.isArray(a[b.name]) ? a[b.name] : []),
         b.value
    ]
}), {});

得到的结果为:

{
  "color": [
    "白色",
    "黑色"
  ],
  "size": [
    "x",
    "M"
  ]
}

然后开始组合:

function combine(properties, props_) {
  // 得到当前还有哪些 props_ 没有被合并,如果 props_ 为 undefined,则表示当前是第一层遍历
  let props = Array.isArray(props_) ? [...props_] : Object.keys(properties).filter(key => properties[key].length > 0);
  // 得到所有没有值的 props,这些 props 直接添加到对象上即可
  let emptyProps = Object.keys(properties).filter(key => properties[key].length === 0);
  // 由无可用值的 prop 组成的对象
  const emptyObject = emptyProps.reduce((a, b) => ({...a, [b]: undefined}), {});
  //  取出第一个属性
  const prop = props.pop();
  //  得到当前属性可用的所有的对象
  const objects = properties[prop].map(value => ({ ...emptyObject, [prop]: value}));
  // 得到其它属性的所有可用的对象并合并
  // 如果已没有 props 了,那么就是最里层的遍历了
  if (props.length === 0) {
    return objects
  }
  // 否则遍历 objects
  return objects.reduce((a, b) => 
    a.concat(
      // 合并未合并的属性
      combine(properties, props).reduce((x, y) => 
          // 将未合并的属性对象与 objects 合并
          [...x, {...b, ...y}],
          []
        )
    ),
    []
  )
}

console.log(JSON.stringify(combine(properties), null, 2));

得到如下结果:

[
  {
    "size": "x",
    "color": "白色"
  },
  {
    "size": "x",
    "color": "黑色"
  },
  {
    "size": "M",
    "color": "白色"
  },
  {
    "size": "M",
    "color": "黑色"
  }
]

使用下面这样的更多属性测试:

const arr = [
  { name: "color", value: "白色" },
  { name: "color", value: "黑色" },
  { name: "size", value: "x" },
  { name: "size", value: "M" },
  { name: "model", value: "A" },
  { name: "model", value: "B" },
  { name: "model", value: "C" },
];

console.log(JSON.stringify(arr.reduce((a, b) => ({
    ...a,
    [b.name]: [
        // 如果 a[b.name] 已经存在,那就把当前的值都继承下来
        ...(Array.isArray(a[b.name]) ? a[b.name] : []),
         b.value
    ]
}), {}), null, 2));

得到如下结果:

[
  {
    "model": "A",
    "size": "x",
    "color": "白色"
  },
  {
    "model": "A",
    "size": "x",
    "color": "黑色"
  },
  {
    "model": "A",
    "size": "M",
    "color": "白色"
  },
  {
    "model": "A",
    "size": "M",
    "color": "黑色"
  },
  {
    "model": "B",
    "size": "x",
    "color": "白色"
  },
  {
    "model": "B",
    "size": "x",
    "color": "黑色"
  },
  {
    "model": "B",
    "size": "M",
    "color": "白色"
  },
  {
    "model": "B",
    "size": "M",
    "color": "黑色"
  },
  {
    "model": "C",
    "size": "x",
    "color": "白色"
  },
  {
    "model": "C",
    "size": "x",
    "color": "黑色"
  },
  {
    "model": "C",
    "size": "M",
    "color": "白色"
  },
  {
    "model": "C",
    "size": "M",
    "color": "黑色"
  }
]

最后封装一个函数:

const generate = (props) => combine(props.reduce((a, b) => ({
    ...a,
    [b.name]: [
        // 如果 a[b.name] 已经存在,那就把当前的值都继承下来
        ...(Array.isArray(a[b.name]) ? a[b.name] : []),
         b.value
    ]
}), {}))
// 转换成多维数组
function getMultidimensional(arr) {
  const data = {};
  arr.forEach((item) => {
    const list = data[item.name] || [];
    list.push(item);
    data[item.name] = list;
  });
  return Object.keys(data).map((key) => data[key]);
}

// 求笛卡尔积
function getCartesianProduct(array) {
  if (!Array.isArray(array)) return [[]]; // 判断数组
  array = array.filter((_) => _.length > 0); // 清除数组里面的空数组
  if (array.length < 2) return array; // 数组少于两个 GG
  const list1 = array.splice(0, 1)[0]; // 取出第一个
  const list2 = array.splice(0, 1)[0]; // 取出第二个
  const list = [];
  list1.forEach((_list1) => {
    list2.forEach((_list2) => {
      if (_list1.name) {
        list.push({
          [_list1.name]: _list1.value,
          [_list2.name]: _list2.value,
        });
      } else {
        list.push({
          ..._list1,
          [_list2.name]: _list2.value,
        });
      }
    });
  });
  return getCartesianProduct([list].concat(array));
}
const arr = [
  { name: "color", value: "白色" },
  { name: "color", value: "黑色" },
  { name: "size", value: "x" },
  { name: "size", value: "M" },
];
const list = getCartesianProduct(getMultidimensional(arr));
console.log(list);

image.png

先给试验数据(@唐云 下次记得贴代码出来)

const arr = [
    {
        "productAttributeld": 2,
        "value": "白色",
        "name": "color"
    },
    {
        "productAttributeld": 2,
        "value": "黑色",
        "name": "color"
    },
    {
        "productAttributeld": 5,
        "value": "M",
        "name": "size"
    },
    {
        "productAttributeld": 5,
        "value": "X",
        "name": "size"
    }
];

然后(借助了 lodash 的 groupBy,因为懒得自己写)

import _ from "lodash";

// 先按 productAttributeld 分组,将分组结构转成数组(数组的数组)
const attributes = _(arr).groupBy(it => it.productAttributeld)
    .mapValues(list => list.map(({ name, value }) => ({ [name]: value })))
    .values()
    .value();

console.log(attributes);
// [
//     [ { color: '白色' }, { color: '黑色' } ],
//     [ { size: 'M' }, { size: 'X' } ]
// ]

// 笛卡尔积
// 以 [{}] 作为初始值,可以理解为数学算累积乘积的初始值 1
// 遍历上述属性组列表,用上次的乘积结果(第 1 次是初始值)中的每一个元素,与新的属性组相乘,
// 再展开成一维数组
const result = attributes.reduce(
    (r, attributeValues) => r.flatMap(it => attributeValues.map(attr => ({ ...it, ...attr }))),
    [{}]);

console.log(result);

示意图:

image.png

function getGroupResult(dataList) {
  return dataList.reduce((r, { name, value }) => {
    r[name] = r[name] || [];
    r[name].push(value);
    return r;
  }, {})
}

function combination(groupInfo) {
  return Object.entries(groupInfo).reduce((result, [key, valueList]) => {
    return valueList.reduce((subResult, value) => {
      const tail = result.map(i => ({ ...i, [key]: value }));
      return subResult.concat(tail)
    }, [])
  }, [{}])
}

调用:

const groupResult = getGroupResult(data);
const result = combination(groupResult);
console.log(result);

image.png

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
宣传栏