Используйте ES6 Reduction или Map для вычисления сумм и процентов во вложенных объектах массива. - PullRequest
1 голос
/ 14 марта 2019

Извиняюсь, если заголовок не особо понятен. Ниже я сделал короткий воспроизводимый пример того, что я пытаюсь сделать:

var arrayOfPeopleStats = [
  person1 = { 
    this: { makes: 10, attempts: 15, pct: .666667 },
    that: { makes: 12, attempts: 16, pct: .75 },
    thos: { value: 10, rank: 24 },
    them: { value: 15, rank: 11 }
  },
  person2 = { 
    this: { makes: 5, attempts: 15, pct: .333333 },
    that: { makes: 10, attempts: 12, pct: .83333 },
    thos: { value: 4, rank: 40 },
    them: { value: 3, rank: 32 }
  },
  person3 = { 
    this: { makes: 14, attempts: 14, pct: 1 },
    that: { makes: 7, attempts: 8, pct: .875 },
    thos: { value: 12, rank: 12 },
    them: { value: 13, rank: 10 }
  }
]


var desiredOutput = [
  allPeople: {
    this: { makes: 29, attempts: 44, pct: .65909 },
    that: { makes: 29, attempts: 36, pct: .80555 },
    thos: { value: 26, rank: doesnt matter },
    them: { value: 31, rank: doesnt matter }
  }
]

arrayOfPeopleStats - массив с объектами person. У каждого объекта персонажа есть пара характеристик (то есть, в этом примере их), и каждая из этих характеристик является объектом с трио makes, attempts, steals или value, rank.

Я хотел бы суммировать попытки, попытки и значения по объектам статистики для каждого человека и вычислить новое значение pct для суммированных данных.

В скором времени я опубликую пост о попытке, которую я предпринял, используя ES6 Reduce, хотя я не уверен, что это лучший подход к проблеме. Любая помощь очень ценится в этом!

Ответы [ 3 ]

3 голосов
/ 14 марта 2019

Это поможет вам начать думать о ваших данных в терминах типов .

a Person - это объект с четырьмя свойствами,

  1. this, который является Stat объектом (который мы вскоре определим)
  2. that, который является другим Stat объектом
  3. thos, который является Rank объектом (который мы также определим)
  4. them, который является другим Rankобъект

Для продолжения мы указываем Stat как объект с тремя свойствами:

  1. makes, который является числом
  2. attemptsкоторый является другим числом
  3. pct, который является другим числом

И, наконец, Rank является объектом с двумя свойствами,

  1. value это число
  2. rank, которое является другим числом

Теперь мы можем рассматривать arrayOfPeopleStats как массив элементов, каждый элемент которого имеет тип Персона .Чтобы объединить похожие типы, мы определяем путь к create значениям нашего типа и concat (добавляем) их.

Сначала я определю меньшие типы, Stat -

const Stat =
  { create: (makes, attempts) =>
      ({ makes, attempts, pct: makes / attempts })

  , concat: (s1, s2) =>
      Stat .create
        ( s1.makes + s2.makes
        , s1.attempts + s2.attempts
        )
  }

Тип Rank аналогичен.Вы сказали, что «не имеет значения» что такое объединенное значение для rank, но это показывает, что выбор должен быть сделан.Вы можете выбрать одно или другое, добавить два значения вместе или какой-либо другой вариант полностью.В этой реализации мы выбираем наибольшее значение rank -

const Rank =
  { create: (value, rank) =>
      ({ value, rank })

  , concat: (r1, r2) =>
      Rank .create
        ( r1.value + r2.value
        , Math .max (r1.rank, r2.rank)
        ) 
  }

Наконец, мы реализуем Person.Я переименовал this, that, thos, them в a, b, c, d, потому что this является специальной переменной в JavaScript, и я думаю, что это примернемного легче следовать;данные в вашем вопросе все равно выглядят насмешливымиЗдесь важно отметить, что функция Person.concat опирается на Stat.concat и Rank.concat, сохраняя логику комбинации для каждого типа красиво отделенной -

const Person =
  { create: (a, b, c, d) =>
      ({ a, b, c, d })

  , concat: (p1, p2) =>
      Person .create
        ( Stat .concat (p1.a, p2.a)
        , Stat .concat (p1.b, p2.b)
        , Rank .concat (p1.c, p2.c)
        , Rank .concat (p1.d, p2.d)
        )
  }

Теперь объединяем все элементы person впросто уменьшите ваш массив, используя операцию Person.concat -

arrayOfPeopleStat .reduce (Person.concat)

// { a: { makes: 29, attempts: 44, pct: 0.6590909090909091 }
// , b: { makes: 29, attempts: 36, pct: 0.8055555555555556 }
// , c: { value: 26, rank: 40 }
// , d: { value: 31, rank: 32 }
// }

Обратите внимание: если у вас есть правильно определенные конструкторы данных, выписывание данных вручную с помощью { ... } является громоздким и подвержено ошибкам.Вместо этого используйте функцию create вашего типа -

var arrayOfPeopleStat =
  [ Person .create
      ( Stat .create (10, 15)
      , Stat .create (12, 16)
      , Rank .create (10, 24)
      , Rank .create (15, 11)
      )
  , Person .create
      ( Stat .create (5, 15)
      , Stat .create (10, 12)
      , Rank .create (4, 40)
      , Rank .create (3, 32)
      )
  , Person .create
      ( Stat .create (14, 14)
      , Stat .create (7, 8)
      , Rank .create (12, 12)
      , Rank .create (13, 10)
      )
  ]

Дополнительное преимущество использования create функций по сравнению с ручным выводом данных с помощью { ... } заключается в том, что конструкторы могут помочь вам.Обратите внимание, как pct автоматически вычисляется для Stat.Это не позволяет кому-либо написать недопустимую статистику, например { makes: 1, attempts: 2, pct: 3 }, где свойство pct не равно makes/attempts.Если получатель этих данных не проверяет, у нас нет способа обеспечить целостность свойства pct.С другой стороны, используя наш конструктор, Stat.create принимает только makes и attempts, а поле pct вычисляется автоматически, гарантируя правильное значение pct.

Конструктор также может помешать кому-либоот создания статистики, которая привела бы к ошибке, такой как Stat .create (10, 0), которая попыталась бы вычислить 10/0 (ошибка деления на ноль) для свойства pct -

const Stat =
  { create: (makes = 0, attempts = 0) =>
      attempts === 0
        ? { makes, attempts, pct: 0 }                // instead of throwing error
        : { makes, attempts, pct: makes / attempts } // safe to divide

  , concat: ...
  }

В конечном итоге эти вариантывам решать.Другой программист может одобрить ошибку в этой ситуации.Например, -

Stat .create (10, 0)
// Error: invalid Stat. makes (10) cannot be greater than attempts (0). attempts cannot be zero.

Результат reduce, конечно же, одинаков -

arrayOfPeopleStat .reduce (Person.concat)

// { a: { makes: 29, attempts: 44, pct: 0.6590909090909091 }
// , b: { makes: 29, attempts: 36, pct: 0.8055555555555556 }
// , c: { value: 26, rank: 40 }
// , d: { value: 31, rank: 32 }
// }

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

const Stat =
  { create: (makes, attempts) =>
      ({ makes, attempts, pct: makes / attempts })

  , concat: (s1, s2) =>
      Stat .create
        ( s1.makes + s2.makes
        , s1.attempts + s2.attempts
        )
  }

const Rank =
  { create: (value, rank) =>
      ({ value, rank })
  
  , concat: (r1, r2) =>
      Rank .create
        ( r1.value + r2.value
        , Math .max (r1.rank, r2.rank)
        ) 
  }

const Person =
  { create: (a, b, c, d) =>
      ({ a, b, c, d })

  , concat: (p1, p2) =>
      Person .create
        ( Stat .concat (p1.a, p2.a)
        , Stat .concat (p1.b, p2.b)
        , Rank .concat (p1.c, p2.c)
        , Rank .concat (p1.d, p2.d)
        )
  }

var data =
  [ Person .create
      ( Stat .create (10, 15)
      , Stat .create (12, 16)
      , Rank .create (10, 24)
      , Rank .create (15, 11)
      )
  , Person .create
      ( Stat .create (5, 15)
      , Stat .create (10, 12)
      , Rank .create (4, 40)
      , Rank .create (3, 32)
      )
  , Person .create
      ( Stat .create (14, 14)
      , Stat .create (7, 8)
      , Rank .create (12, 12)
      , Rank .create (13, 10)
      )
  ]

console .log (data .reduce (Person.concat))

// { a: { makes: 29, attempts: 44, pct: 0.6590909090909091 }
// , b: { makes: 29, attempts: 36, pct: 0.8055555555555556 }
// , c: { value: 26, rank: 40 }
// , d: { value: 31, rank: 32 }
// }

Но что произойдет, если data - пустой массив?

[] .reduce (Person.concat)
// Uncaught TypeError: Reduce of empty array with no initial value

Если часть нашего кода работает для некоторых массивов, норазрывы для других массивов, это плохо.Массив используется для представления нуля или более значений, поэтому мы хотим охватить все наши базы, включая случай нулевой статистики.Функция, которая определена для всех возможных значений ее входа, является общей функцией , и мы стремимся писать наши программы таким образом, когда это возможно.

Сообщение об ошибке предоставляет мудрость для исправления;мы должны предоставить начальное значение .Например, 0 in -

const add = (x, y) =>
  x + y

console .log
  ( [] .reduce (add, 0)           // 0
  , [ 1, 2, 3 ] .reduce (add, 0)  // 6
  )

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

Если мы пытаемся reduce массив типов Person, каково начальное значение для Person?

arrayOfPeopleStat .reduce (Person.concat, ???)

Мы определяем значение empty для каждого из наших типов, если это возможно.Мы начнем с Stat -

const Stat = 
  { empty:
      { makes: 0
      , attempts: 0
      , pct: 0
      }
  , create: ...
  , concat: ...
  }

Далее мы сделаем Rank -

const Rank =
  { empty:
      { value: 0
      , rank: 0
      }
  , create: ...
  , concat: ...
  }

Опять же, мы видим человека как составные данные, то есть данные, построенные из других данных.Мы используем Stat.empty и Rank.empty для создания Person.empty -

const Person =
  { empty:
      { a: Stat.empty
      , b: Stat.empty
      , c: Rank.empty
      , d: Rank.empty
      }
  , create: ...
  , concat: ...
  }

Теперь мы можем указать Person.empty в качестве начального значения и предотвратить появление отягчающих ошибок -

arrayOfPeopleStat .reduce (Person.concat, Person.empty)
// { a: { makes: 29, attempts: 44, pct: 0.6590909090909091 }
// , b: { makes: 29, attempts: 36, pct: 0.8055555555555556 }
// , c: { value: 26, rank: 40 }
// , d: { value: 31, rank: 32 }
// }

[] .reduce (Person.concat, Person.empty)
// { a: { makes: 0, attempts: 0, pct: 0 }
// , b: { makes: 0, attempts: 0, pct: 0 }
// , c: { value: 0, rank: 0 }
// , d: { value: 0, rank: 0 }
// }

В качестве бонуса у вас теперь есть базовое понимание моноидов .

1 голос
/ 14 марта 2019

При выборе между map и reduce вы можете подумать, что map вернет новое array с такой же длины, что и исходное .Это не то, что вы хотите, поэтому вы не должны использовать это.Вы действительно хотите уменьшить ваш массив до объекта.Так что просто используйте reduce:

О функции, чтобы уменьшить ее, вот одна из возможных:

var arrayOfPeopleStats=[person1={this:{makes:10,attempts:15,pct:.666667},that:{makes:12,attempts:16,pct:.75},thos:{value:10,rank:24},them:{value:15,rank:11}},person2={this:{makes:5,attempts:15,pct:.333333},that:{makes:10,attempts:12,pct:.83333},thos:{value:4,rank:40},them:{value:3,rank:32}},person3={this:{makes:14,attempts:14,pct:1},that:{makes:7,attempts:8,pct:.875},thos:{value:12,rank:12},them:{value:13,rank:10}}];

const resp = Object.values(arrayOfPeopleStats).reduce((obj, { this: localThis, that, thos, them }) => {
  obj.this = { makes: obj.this.makes + localThis.makes, attempts: obj.this.attempts + localThis.attempts };
  obj.this.pct = obj.this.makes / obj.this.attempts;
  obj.that = { makes: obj.that.makes + that.makes, attempts: obj.that.attempts + that.attempts };
  obj.that.pct = obj.that.makes / obj.that.attempts;
  obj.thos = { value: obj.thos.value + thos.value, rank: obj.thos.rank + thos.rank };
  obj.them = { value: obj.them.value + them.value, rank: obj.thos.rank + them.rank };
  return obj;
})

const formattedResp = [{ allPeople: resp }]

console.log(formattedResp)

И я не предлагаю вам использовать this в качестве имени свойства, если оно может конфликтовать с ключевым словом this.

0 голосов
/ 14 марта 2019
arrayOfPeopleStats.reduce((a, v) => {
  a.allPeople.this = {
    makes: a.allPeople.this.makes + v.this,
    attempts: a.allPeople.this.attempts + v.this.attempts,
  };
  a.allPeople.that = {
    makes: a.allPeople.that.makes + v.that.makes,
    attempts: a.allPeople.that.attempts + v.that.attempts,
  };
  a.allPeople.thos = {
    value: a.allPeople.thos.value + v.thos.value,
  };
  a.allPeople.them = {
    value: a.allPeople.them.value + v.them.value,
  };
  a.allPeople.this.pct = a.allPeople.this.makes / a.allPeople.this.attempts
  a.allPeople.that.pct = a.allPeople.that.makes / a.allPeople.that.attempts
  return a;
}, {allPeople: {
    this: { makes: 0, attempts: 0, pct: 0 },
    that: { makes: 0, attempts: 0, pct: 0 },
    thos: { value: 0, rank: 0 },
    them: { value: 0, rank: 0 }
  }
}});

Я проигнорировал ранг, так как вы сказали, что это не имеет значения, оно всегда будет 0 с вышеупомянутым решением.Я также решил не заключать результирующий объект в массив, как вы сделали в желаемом результате.См. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce для объяснения этого API.

...