GroupBy и уменьшите массив объектов в стиле pointfree - PullRequest
3 голосов
/ 29 марта 2019

Я недавно начал использовать Ramda и пытался найти бессмысленный способ написания метода для уменьшения массива объектов.

Вот массив объектов:

const someObj = [
    {
        name: 'A',
        city: 1,
        other: {
            playtime: 30
        }
    },
    {
        name: 'B',
        city: 2,
        other: {
            playtime: 20
        }
    },
    {
        name: 'c',
        city: 1,
        other: {
            playtime: 20
        }
    }
];

Я пытаюсь уменьшить объект, используя ramda в стиле poinfree, например

{
    '1': {
        count: 2,
        avg_play_time: 20 + 30 / count
    },
    '2': {
        count: 1,
        avg_play_time: 20 / count
    }
}

Я могу сделать это, используя метод уменьшения массива, но не уверен, как я могу написать то же самое в стиле ramda pointfree. Любое предложение будет оценено.

Ответы [ 5 ]

4 голосов
/ 29 марта 2019

Одним из решений было бы сделать что-то вроде этого:

// An optic to extract the nested playtime value
// Coupled with a `lift` operation which allows it to be applied over a collection
// Effectively A -> B => A[] -> B[]
const playtimes = R.lift(R.path(['other', 'playtime']))

R.pipe(
  // Group the provided array by the city value
  R.groupBy(R.prop('city')),
  // Return a body specification which computes each property based on the 
  // provided function value.
  R.map(R.applySpec({
    count: R.length,
    average: R.pipe(playtimes, R.mean)
  }))
)(someObj)
2 голосов
/ 29 марта 2019

Я бы написал так, надеюсь, это поможет!

const stats = R.pipe(
  R.groupBy(R.prop('city')),
  R.map(
    R.applySpec({
      count: R.length,
      avg_play_time: R.pipe(
        R.map(R.path(['other', 'playtime'])),
        R.mean,
      ),
    }),
  ),  
);

const data = [
  { name: 'A', city: 1, other: { playtime: 30 } },
  { name: 'B', city: 2, other: { playtime: 20 } },
  { name: 'c', city: 1, other: { playtime: 20 } },
];

console.log('result', stats(data));
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>
2 голосов
/ 29 марта 2019

Вот еще одно предложение, использующее reduceBy с отображением функции applySpec на каждое свойство результирующего объекта:

Идея состоит в том, чтобы преобразовать someObj в этот объект, используя getPlaytimeByCity:

{ 1: [30, 20],
  2: [20]}

Затем вы можете отобразить функцию stats для каждого свойства этого объекта:

stats({ 1: [30, 20], 2: [20]});
// { 1: {count: 2, avg_play_time: 25}, 
//   2: {count: 1, avg_play_time: 20}}

const someObj = [
    { name: 'A',
      city: 1,
      other: { playtime: 30 }},
    { name: 'B',
      city: 2,
      other: { playtime: 20 }},
    { name: 'c',
      city: 1,
      other: { playtime: 20 }}
];

const city = prop('city');
const playtime = path(['other', 'playtime']);
const stats = applySpec({count: length, avg_play_time: mean});
const collectPlaytime = useWith(flip(append), [identity, playtime]);
const getPlaytimeByCity = reduceBy(collectPlaytime, [], city);

console.log(

  map(stats, getPlaytimeByCity(someObj))
  
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>
<script>const {prop, path, useWith, flip, append, identity, applySpec, length, mean, reduceBy, map} = R;</script>
2 голосов
/ 29 марта 2019

Рамда также имеет другую функцию под названием R.reduceBy, которая обеспечивает что-то среднее между reduce и groupBy, позволяя вам складывать значения с помощью соответствующих ключей вместе.

Таким образом, вы можете создать тип данных, подобный следующему, который подсчитывает значения до усреднения.

const Avg = (count, val) => ({ count, val })
Avg.of = val => Avg(1, val)
Avg.concat = (a, b) => Avg(a.count + b.count, a.val + b.val)
Avg.getAverage = ({ count, val }) => val / count
Avg.empty = Avg(0, 0)

Затем объедините их вместе, используя R.reduceBy.

const avgCities = R.reduceBy(
  (avg, a) => Avg.concat(avg, Avg.of(a.other.playtime)),
  Avg.empty,
  x => x.city
)

Затем вытяните средние значения из Avg в форму конечных объектов.

const buildAvg = R.applySpec({
  count: x => x.count,
  avg_play_time: Avg.getAverage
})

И, наконец, соединяем их вместе, сопоставляя buildAvg значения в объекте.

const fn = R.pipe(avgCities, R.map(buildAvg))
fn(someObj)
1 голос
/ 29 марта 2019

Мне нравятся все остальные ответы, данные до сих пор. Поэтому, естественно, я хочу добавить свой. ; -)

Вот версия, которая использует reduceBy для отслеживания количества и среднего значения. Это не сработало бы, если вы искали медианное значение или какую-либо другую статистику, но, учитывая количество, среднее значение и новое значение, мы можем напрямую рассчитать новое значение и среднее значение. Это позволяет нам повторять данные только один раз за счет выполнения некоторой арифметики на каждой итерации.

const transform = reduceBy(
  ({count, avg_play_time}, {other: {playtime}}) => ({
    count: count + 1,
    avg_play_time: (avg_play_time * count + playtime) / (count + 1)
  }),
  {count: 0, avg_play_time: 0},
  prop('city')
)
const someObj = [{city: 1, name: "A", other: {playtime: 30}}, {city: 2, name: "B", other: {playtime: 20}}, {city: 1, name: "c", other: {playtime: 20}}]

console.log(transform(someObj))
<script src="https://bundle.run/ramda@0.26.1"></script>
<script>
const {reduceBy, prop} = ramda
</script>

Это не бессмысленно. Хотя я большой поклонник стиля без очков, я использую его только тогда, когда это применимо. Я думаю, что искать его ради себя - ошибка.

Обратите внимание, что ответ Скотта Кристофера может быть легко изменен для использования такого рода расчетов

...