Это поможет вам начать думать о ваших данных в терминах типов .
a Person
- это объект с четырьмя свойствами,
this
, который является Stat
объектом (который мы вскоре определим) that
, который является другим Stat
объектом thos
, который является Rank
объектом (который мы также определим) them
, который является другим Rank
объект
Для продолжения мы указываем Stat
как объект с тремя свойствами:
makes
, который является числом attempts
который является другим числом pct
, который является другим числом
И, наконец, Rank
является объектом с двумя свойствами,
value
это число 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 }
// }
В качестве бонуса у вас теперь есть базовое понимание моноидов .