Красноречивое упражнения на JavaScript: найти средний возраст от предков массива на века


Я решил упражнение из книги "красноречивое на JavaScript". Я хочу поделиться с вами решение. Упражнение из главы 5. Подводя итоги, я должен найти средний возраст от предков массива на века. Вот мое решение:

var ancestry = JSON.parse(require('../resources/ancestry'))

//Top-down approach, functions are defined after their use
//Necessary side-effect in function to print the output
function printAverageAgePerCentury(ancestry){
    var averageAgePerCentury = getAverageAgePerCentury(ancestry);
    Object.keys(averageAgePerCentury).forEach(function(century){
        console.log(century.concat(": ").concat(averageAgePerCentury[century]))
    })
}

function getAverageAgePerCentury(ancestry){
    var agesPerCentury = getAgesPerCentury(ancestry); 
    return agesPerCentury.reduce(function(averageAgePerCentury, agePerCentury){
        if(averageAgePerCentury[agePerCentury.century]){
            averageAgePerCentury[agePerCentury.century] = [average(averageAgePerCentury[agePerCentury.century].concat(agePerCentury.age))];
            return averageAgePerCentury;
        } else {
            averageAgePerCentury[agePerCentury.century] = [agePerCentury.age];
            return averageAgePerCentury;
        }
    }, {})
}

function getAgesPerCentury(ancestry){
    return ancestry.map(function(person){
        return {
            century : whichCentury(person),
            age : person.died - person.born
        }
    })
}

function average(array){
    function plus(a,b){ return a + b}
    return array.reduce(plus) / array.length
}

function whichCentury(person){
    return Math.ceil(person.died / 100)
}

//--------------------------------//

printAverageAgePerCentury(ancestry);
  1. Что вы думаете о подходе "сверху вниз"? Вы предпочитаете наоборот? Где функции определены до их использования.
  2. Можно сделать это решение более читабельным, более элегантно?
  3. Что вы можете сделать, чтобы сделать его лучше?


Комментарии
3 ответа

Неправильный расчет среднего

average([1, 2, 3, 4]) Это \$\dfrac{1+2+3+4}{4}=2.5\$.

Однако, из-за как вы называете reduce() в getAverageAgePerCentury():


averageAgePerCentury[agePerCentury.century] = [average(averageAgePerCentury[agePerCentury.century].concat(agePerCentury.age))];

... вы бы реально рассчитывать average([average([average([average([1]), 2]), 3]), 4]), которые дадут результат 3.125 — взвешенный в пользу более поздних записях.

Делаешь слишком много

Основной причиной ошибок, я думаю, что getAverageAgePerCentury() делает много работы. Он создает карту вв. до возраста людей, которые погибли в этом веке, и вычисляет среднее из этих значений.

Я бы определил две служебные функции, вдохновленный Лодашь по _.mapValues() и _.groupBy().

/**
* Creates a new object in which all of the original obj's values are
* transformed by callback(value).
*/
function mapValues(obj, callback) {
return Object.keys(obj).reduce(function(result, key) {
result[key] = callback(obj[key]);
return result;
}, {});
}

/**
* Given an array of objects, creates a new object in which all objects where
* attrExtractor(obj) having the same value are grouped together in an array.
*/
function groupBy(objs, attrExtractor) {
return objs.reduce(function(result, obj) {
var attr = attrExtractor(obj);
if (attr in result) {
result[attr].push(obj);
} else {
result[attr] = [obj];
}
return result;
}, {});
}

Потом, в остальной части решение выглядит проще.

function average(array) {
return array.reduce(function(a, b) { return a + b }) / array.length;
}

function averageAgePerCentury(ancestry) {
function deathCentury(person) { return Math.ceil(person.died / 100) }
function deathAge(person) { return person.died - person.born }

return mapValues(
mapValues(
groupBy(ancestry, deathCentury),
function(persons) { return persons.map(deathAge) }
),
average
);
}

Обратите внимание, что я переименовал getAverageAgePerCentury(). Функция "добытчик" должен извлечь то, что уже существует. Здесь, вы делаете расчет. Вы называете Math.sin(x)не Math.getSin(x)так же именования принцип применяется здесь.

2
ответ дан 2 февраля 2018 в 11:02 Источник Поделиться

Я рефакторинг функции должны быть короче и ЕС6, и изменил форматирование, чтобы сделать его более удобным для чтения. Вы также можете рассмотреть возможность сделать некоторые из ваших имен переменных и функций, короче. Кроме того, не уверен, что заказ у вас для своих функций.

const ancestry = JSON.parse(require('../resources/ancestry'));

const average = (array) => array.reduce((a,b) => a + b) / array.length;
const whichCentury = (person) => Math.ceil(person.died / 100);

const printAverageAgePerCentury = (ancestry) => {
const averageAgePerCentury = getAverageAgePerCentury(ancestry);
Object.keys(averageAgePerCentury).forEach((century) => {
console.log(century.concat(": ").concat(averageAgePerCentury[century]));
});
};

const getAgesPerCentury = (ancestry) => {
return ancestry.map((person) => {
return {
century : whichCentury(person),
age : person.died - person.born
};
});
};

const getAverageAgePerCentury = (ancestry) => {
return getAgesPerCentury(ancestry).reduce((averageAgePerCentury, agePerCentury) => {
if (averageAgePerCentury[agePerCentury.century]) {
averageAgePerCentury[agePerCentury.century] = [average(averageAgePerCentury[agePerCentury.century].concat(agePerCentury.age))];
return averageAgePerCentury;
} else {
averageAgePerCentury[agePerCentury.century] = [agePerCentury.age];
return averageAgePerCentury;
}
}, {});
}

0
ответ дан 2 февраля 2018 в 10:02 Источник Поделиться

Красноречивый

Красноречивым является простым, эффективным и обработки и использования памяти, легко понять, и легко усвояемым.

Простота

Не храните ненужные данные, это добавляет сложности.

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

Эффективность

Делаешь минимальную работу возможной.

Для получения результата требует, один проход в группу, сумму, и считать данные. Затем один проход на протяжении веков для отображения результатов. С 39 человек и 6 веков. количество итераций должно быть 45.

Ваше решение проходит за 118 раз сведения более чем в 2 раза больше, чем нужно. Решение по 200_success улучшает, что немного с 90 итерационных шагов, вдвое больше, чем нужно.

Понятно.

Код должен быть легко следовать.

В старые времена кодирования был стиль кода обычно называют спагетти-код. Чтобы понять это, вы должны следовать сложный путь обработки. Каждый раз, когда вы прыгать из одной части исходного кода к другому, она нарушит логику потока хранится в вашей голове. После несвязного исходный код жесткий.

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

Это редко, как это работает, и функции трассировки, потому что имя функции не хватает информации, чтобы понять процесс-это современная форма спагетти-кода.

Адаптивность

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

Может код обработки плохих данных, может код ручки различаются большими наборами данных. Что нужно изменить, чтобы сделать его распределенное решение.

Красноречивое решение.

Минимальный итерации проходит, эффективно, без несущественных данных, которые хранятся, и только 45 итераций.

Понятно, не море вложенных вызовов функций, логика прослеживается довольно много, сверху до низу, без шаг в несвязанном неадекватно названные функции.

Может быть немного проще, но я пошел на адаптивность за простоты. В generateGroupKey позволяет различные группировки.

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

Никаких побочных эффектов и чиста, как белый снег.

const groupByCentury = person => (person.died / 100 | 0) + 1;

groupAndAverageAges(ancestry, groupByCentury, console.log);

function groupAndAverageAges(ancestry, generateGroupKey, output) {
const groups = new Map();

for (const person of ancestry) {
const group = generateGroupKey(person);
var stats = groups.get(group);
if (stats === undefined) {
stats = {group : group, sum : 0, count : 0};
groups.set(group, stats);
}
stats.sum += person.died - person.born;
stats.count += 1;
}

for (const group of groups.values()) {
output({
grouping : group.group,
meanAge : Math.round(group.sum / group.count)
});
}
}

@Аарон Голдсмит спасибо за редактирование предложение, но у меня правило, что это не объект недвижимости, короткая стрелка на одной линии, как стандартного свойства. Так что я оставить линию как есть, а не ломать его.

Альтернатива.

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

Эта версия возвращает массив с элементом для каждого века.

const groupByCentury = person => (person.died / 100 | 0) + 1;

const meanAgeGroupedByCentury = groupAndAverageAges(ancestry, groupByCentury);

function groupAndAverageAges(ancestry, generateGroupKey) {
const groups = new Map();
for (const person of ancestry) {
const groupKey = generateGroupKey(person);
let stats = groups.get(groupKey);
if (! stats) {
stats = {group : groupKey, sum : 0, count : 0};
groups.set(groupKey, stats);
}
stats.sum += person.died - person.born;
stats.count += 1;

}
return [...groups.values()].map(group => ({
group : group.group,
averageAge : Math.round(group.sum / group.count)
})
);
}

0
ответ дан 3 февраля 2018 в 03:02 Источник Поделиться