简体   繁体   English

由多个属性组成的无点组

[英]Point-free group by multiple properties

I'm struggling a bit with implementing a variant groupBy that would allow grouping by multiple properties in a point-free style. 我正在努力实现一个变量groupBy ,它允许以无样式对多个属性进行分组。 (I'm using typescript & ramda). (我正在使用打字稿和ramda)。

I want to group some elements say of type A by properties returned from function getProperties :: A a -> [b] . 我想通过从函数getProperties :: A a -> [b]返回的属性对类型A的一些元素进行分组。 In imperative paradigm the implementation could look like that: 在命令式范例中,实现可能如下所示:

const getProperties = (item: Item): Array<keyof Item> => [item.x, item.y.z];

const groupByMany = (items: Item[]): Dictionary<Item[]> => {
  let groupped: Dictionary<Item[]> = {};
  for (let item of items) {
    for (let key of getProperties(item)) {
      if (groupped[key]) {
        groupped[key].push(item);
      } else {
        groupped[key] = [item];
      }
    }
  }
}

Example: 例:

const items = [
  { i: 1, x: 'A', y: { z: 'B' } },
  { i: 2, x: 'A' },
  { i: 3, x: 'B', y: { z: 'B' } },
];
const expectedOutput = {
  A: [ { i: 1, ... }, { i: 2, ... }],
  B: [ { i: 1, ... }, { i: 3, ... }],
};

I'll get you started - 我会帮你的 -

 const reduce = (f, init, xs) => xs .reduce (f, init) const upsert = (m, k, v) => m .has (k) ? m .get (k) .push (v) : m .set (k, [ v ]) const groupByMany = (f, xs) => reduce ( (m, x) => ( f (x) .forEach (k => k && upsert (m, k, x)) , m ) , new Map , xs ) const items = [ { i: 1, x: 'A', y: { z: 'B' } } , { i: 2, x: 'A' } , { i: 3, x: 'B', y: { z: 'B' } } ] const result = groupByMany ( item => [ item.x, item.y && item.yz ] , items ) console.log(Object.fromEntries(result.entries())) 

Notice how the last item has a B for .x and .yz so it get's inserted into the B group twice . 注意最后一项如何为.x .yz设置B ,以便将它插入B两次 We change upsert so it will not insert a duplicate value - 我们更改upsert以便它不会插入重复值 -

const upsert = (m, k, v) =>
  m .has (k)
    ? m .get (k) .includes (v)
      ? m
      : m .get (k) .push (v)
    : m .set (k, [ v ])

Expand the snippet below to see the final result in your own browser - 展开下面的代码段,在自己的浏览器中查看最终结果 -

 const reduce = (f, init, xs) => xs .reduce (f, init) const upsert = (m, k, v) => m .has (k) ? m .get (k) .includes (v) ? m : m .get (k) .push (v) : m .set (k, [ v ]) const groupByMany = (f, xs) => reduce ( (m, x) => ( f (x) .forEach (k => k && upsert (m, k, x)) , m ) , new Map , xs ) const items = [ { i: 1, x: 'A', y: { z: 'B' } } , { i: 2, x: 'A' } , { i: 3, x: 'B', y: { z: 'B' } } ] const result = groupByMany ( item => [ item.x, item.y && item.yz ] , items ) console.log(Object.fromEntries(result.entries())) 


A note on SO's peculiar output: SO will not display the same object twice, instead it will give an object a reference, and print that reference where the duplicate object would appear. 关于SO的特殊输出的注释:SO将不会显示相同的对象两次,而是它将为对象提供引用,并在复制对象出现的位置打印该引用。 For example /**id:3**/ in the program's output - 例如/**id:3**/在程序的输出中 -

{
  "A": [
    {
      /**id:3**/
      "i": 1,
      "x": "A",
      "y": {
        "z": "B"
      }
    },
    {
      "i": 2,
      "x": "A"
    }
  ],
  "B": [
    /**ref:3**/,
    {
      "i": 3,
      "x": "B",
      "y": {
        "z": "B"
      }
    }
  ]
}

Which matches your expected output - 哪个符合您的预期输出 -

const expectedOutput = {
  A: [ { i: 1, ... }, { i: 2, ... }],
  B: [ { i: 1, ... }, { i: 3, ... }],
};

It's not point-free like you asked for, but I only said I'd get you started ... 这不像你要求的那样没有点,但我只是说我会让你开始......

I couldn't tell from the question whether you wanted something that made it easy for you to code point-free or if for some reason you were looking for an actual point-free implementation. 我无法从问题中看出你是否想要一些能让你轻松编写无点代码的东西,或者由于某些原因你是否正在寻找一个实际的无点实现。 If it's the latter, then I'm afraid this will be no help. 如果是后者,那么我担心这将无济于事。 But it's a fairly simple 但这很简单

 const groupByMany = (fn) => (xs) => xs .reduce ( (a, x) => [...new Set ( fn(x) )] . reduce ( (a, k) => k ? {...a, [k]: [... (a [k] || []), x ] } : a , a ) , {} ) // const getProperties = (item) => [path(['x'], item), path(['y', 'z'], item)] const getProperties = juxt ( [path (['x']), path (['y', 'z']) ] ) const items = [{ i: 1, x: 'A', y: { z: 'B' } }, { i: 2, x: 'A'}, { i: 3, x: 'B', y: { z: 'B' } }] console .log ( groupByMany (getProperties) (items) ) 
 <script src="https://bundle.run/ramda@0.26.1"></script></script> <script>const { juxt, path } = ramda </script> 

Running the keys through [... new Set ( fn(x) ) ] is just a quick way to eliminate duplicates from the array returned by fn (x) . 通过[... new Set ( fn(x) ) ]运行键只是消除fn (x)返回的数组中重复项的快速方法。 The rest of the function should be pretty clear. 功能的其余部分应该非常清楚。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM