具有 属性 功能的多级 groupby

multi level groupby with function on a property

这与 类似,但有一点给我带来麻烦。除了两级分组之外,我希望内部分组依据是 属性 值的处理版本。

考虑这样的数据:

const data = [ 
  { top: 'top1',
    name: 'junk-key-a-101' },
  { top: 'top1',
    name: 'junk-key-b-102' },
  { top: 'top2',
    name: 'junk-key-c-103' },
  { top: 'top2',
    name: 'junk-key-c-104' } ];

我可以取出密钥,处理它并使其独一无二:

const getZoneFromName = n =>  join('-', slice(1, 3, split('-', n)));
uniq(map(getZoneFromName, pluck('name', data)));

这会让我得到一个不错的清单:

["key-a", "key-b", "key-c"]

我可以将列表分为两个级别:

const groupByTopThenZone = pipe(
  groupBy(prop("top")),
  map(groupBy(prop("name")))
);
groupByTopThenZone(data);

但我不知道如何将它们组合起来以获得以下输出:

{
    top1: {
        "key-a": [
            {
                name: "junk-key-a-101",
                top: "top1"
            }
        ],
        "key-b": [
            {
                name: "junk-key-b-102",
                top: "top1"
            }
        ]
    },
    top2: {
        "key-c": [
            {
                name: "junk-key-c-103",
                top: "top2"
            },
            {
                name: "junk-key-c-104",
                top: "top2"
            }
        ]
    }
}

我觉得我有点傻,我不能得到这个。有任何想法吗? Here是玩的地方

这不是使用 ramda,而是 vanilla JS。

const data = [ 
  { top: 'top1',
    name: 'junk-key-a-101' },
  { top: 'top1',
    name: 'junk-key-b-102' },
  { top: 'top2',
    name: 'junk-key-c-103' },
  { top: 'top2',
    name: 'junk-key-c-104' } ];

const res = data.reduce((acc, val, ind, arr) => {
  const top = val.top;
  // if the top does not exist in the obj, create it
  if (!acc[top]) {
    acc[top] = {};
  }
  // get the key through split. you could also use a regex here
  const keyFragments = val.name.split('-');
  const key = [keyFragments[1], keyFragments[2]].join('-');
  // if the key obj prop does not exist yet, create the array
  if (!acc[top][key]) {
    acc[top][key] = []; 
  }
  // push the value
  acc[top][key].push({ name: val.name, top: val.top });
  return acc;
}, {});
console.log(res);

你们非常亲密。只需将这些函数与 compose/pipe 结合起来就可以了。

(注意这里也是getZoneFromName的简化版。)

const {pipe, groupBy, map, prop, slice} = R

//const getZoneFromName = n =>  join('-', slice(1, 3, split('-', n)));
const getZoneFromName = slice(5, -4)

const groupByTopThenZone = pipe(
  groupBy(prop("top")),
  map(groupBy(pipe(prop("name"), getZoneFromName)))
)

const data = [{"name": "junk-key-a-101", "top": "top1"}, {"name": "junk-key-b-102", "top": "top1"}, {"name": "junk-key-c-103", "top": "top2"}, {"name": "junk-key-c-104", "top": "top2"}]

console.log(groupByTopThenZone(data))
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>

当然这个函数简化了那么多,内联它可能更容易:

const groupByTopThenZone = pipe(
  groupBy(prop("top")),
  map(groupBy(pipe(prop("name"), slice(5, -4)))
)

要记住的主要事情是 groupBy 不一定与 prop 绑定。我们可以对 any String/Number/Symbol 生成函数的结果进行分组。

另一种方法是构建每个最终对象并将它们全部合并:

你可以转换这个对象:

{
  "top": "top1",
  "name": "junk-key-a-101"
}

进入这个:

{
  "top1": {
    "key-a": [
      {
        "name": "junk-key-a-101",
        "top": "top1"
      }
    ]
  }
}

具有这些功能:

const key = slice(5, -4);

const obj = ({top, name}) => ({
  [top]: {
    [key(name)]: [
      {top, name}
    ]
  }
});

现在您可以迭代数据,转换每个对象并将它们合并在一起:

const groupByTopTenZone = reduce(useWith(mergeDeepWith(concat), [identity, obj]), {});

完整示例:

const {slice, useWith, identity, reduce, mergeDeepWith, concat} = R;

const data = [ 
  { top: 'top1',
    name: 'junk-key-a-101' },
  { top: 'top1',
    name: 'junk-key-b-102' },
  { top: 'top2',
    name: 'junk-key-c-103' },
  { top: 'top2',
    name: 'junk-key-c-104' }
];


const key = slice(5, -4);

const obj = ({top, name}) => ({
  [top]: {
    [key(name)]: [
      {top, name}
    ]
  }
});

const groupByTopTenZone = reduce(useWith(mergeDeepWith(concat), [identity, obj]), {});

console.log(
  
  groupByTopTenZone(data)

)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>