Бессмысленная группа по нескольким свойствам - PullRequest
3 голосов
/ 13 июня 2019

Я немного борюсь с реализацией варианта groupBy, который позволил бы группировать по нескольким свойствам в стиле без точек .(Я использую машинопись & ramda).

Я хочу сгруппировать некоторые элементы, например, типа A по свойствам, возвращаемым функцией getProperties :: A a -> [b].В императивной парадигме реализация может выглядеть так:

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];
      }
    }
  }
}

Пример:

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, ... }],
};

Ответы [ 2 ]

3 голосов
/ 13 июня 2019

Я тебя начну -

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.y.z ]
    , items
    )
    
console.log(Object.fromEntries(result.entries()))

Обратите внимание, что последний элемент имеет B для .x и .y.z, поэтому он вставляется в группу B дважды .Мы изменили upsert, чтобы он не вставлял дублирующее значение -

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

Разверните фрагмент ниже, чтобы увидеть окончательный результат в своем браузере -

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.y.z ]
    , items
    )
    
console.log(Object.fromEntries(result.entries()))

Замечание об особом выводе SO: SO не будет отображать один и тот же объект дважды, вместо этого он даст ссылку на объект и напечатает эту ссылку там, где дубликатобъект появится.Например, /**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"
      }
    }
  ]
}

Что соответствует ожидаемому результату -

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

Это не бессмысленно, как вы просили, но я только сказал:начну ...

0 голосов
/ 13 июня 2019

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

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>

Выполнение ключей через [... new Set ( fn(x) ) ] - это просто быстрый способ удалить дубликаты из массива, возвращаемого fn (x). Остальная часть функции должна быть довольно понятной.

...