Рубрики
Без рубрики

То, что я узнал от записи шести функций, которые все сделали то же самое

Пару недель назад, туризм начал неофициальный алгоритм конкуренции на форуме Free Code Champ. Задача казалась достаточно простой: вернуть сумму всех кратных 3 или 5, которые находятся ниже ряд n, где n представляет собой входной параметр к функции. Но вместо

Автор оригинала: Jackson Bates.

Пару недель назад, туризм начал неофициальный алгоритм конкуренции на Бесплатный форум Code Camp Отказ

Задача казалась достаточно простой: вернуть сумму всех кратных 3 или 5, которые находятся ниже ряд n, где n представляет собой входной параметр к функции.

Но вместо того, чтобы просто найти какое-либо решение, конкуренция P1xT требовала вас сосредоточиться на эффективности. Он призвал вам написать свои собственные тесты и проводить представление о производительности ваших решений.

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

Функция № 1: массив, толкание, приращение

function arrayPushAndIncrement(n) {
	var array = [];
    var result = 0;
    for (var i = 1; i < n; i ++) {
        if (i % 3 == 0 || i % 5 == 0) {
            array.push(i);
        }
    }
    for (var num of array) {
        result += num;
    }
    return result;
}

module.exports = arrayPushAndIncrement; // this is necessary for testing

Для таких проблем, как это, мой мозг по умолчанию: построить массив, затем сделайте что-нибудь в этот массив.

Эта функция создает массив и толкает любые номера, которые соответствуют нашему условию (делится на 3 или 5). Затем он проводит сквозь этот массив, добавляя все значения вместе.

Настройка тестирования

Вот автоматизированные тесты для этой функции, которые используют Mocha и Chai, работающие на Nodejs.

Если вы хотите больше информации о установке Mocha и Chai, я написал Подробное руководство На форуме Free Code Camp.

Я написал простой тестовый скрипт, использующий значения P1XT. Обратите внимание, что в скрипте ниже функция включена в виде модуля:

// testMult.js

var should = require( 'chai' ).should();
var arrayPushAndIncrement = require( './arrayPushAndIncrement' );

describe('arrayPushAndIncrement', function() {
    it('should return 23 when passed 10', function() {
    	arrayPushAndIncrement(10).should.equal(23);
    })
    it('should return 78 when passed 20', function() {
    	arrayPushAndIncrement(20).should.equal(78);
    })
    it('should return 2318 when passed 100', function() {
    	arrayPushAndIncrement(100).should.equal(2318);
    })
    it('should return 23331668 when passed 10000', function() {
    	arrayPushAndIncrement(10000).should.equal(23331668);
    })
    it('should return 486804150 when passed 45678', function() {
    	arrayPushAndIncrement(45678).should.equal(486804150);
    })
})

Когда я провел тест, используя Mocha testmult.js Это вернуло следующее:

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

Функция № 2: массив, толчок, уменьшить

function arrayPushAndReduce(n) {
	var array = [];
    for (var i = 1; i < n; i ++) {
    	if (i % 3 == 0 || i % 5 == 0) {
        	array.push(i);
        }
	}  
    return array.reduce(function(prev, current) {
    	return prev + current;
    });
}

module.exports = arrayPushAndReduce;

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

Настройка тестирования производительности

Теперь, когда у нас есть две функции, мы можем сравнить их эффективность. Опять же, благодаря P1xt для предоставления этого сценария в предыдущем потоке форума.

// performance.js

var Benchmark = require( 'benchmark' );
var suite = new Benchmark.Suite;

var arrayPushAndIncrement = require( './arrayPushAndIncrement' );
var arrayPushAndReduce = require( './arrayPushAndReduce' );

// add tests
suite.add( 'arrayPushAndIncrement', function() {
		arrayPushAndIncrement(45678)
    })
    .add( 'arrayPushAndReduce', function() {
    	arrayPushAndReduce(45678)
    })
    // add listeners
    .on( 'cycle', function( event ) {
    	console.log( String( event.target ));
    })
    .on( 'complete', function() {
    	console.log( `Fastest is ${this.filter( 'fastest' ).map( 'name' )}`);
    })
    // run async
    .run({ 'async': true });

Если вы запустите это с Узел Performance.js Вы увидите следующий вывод терминала:

arrayPushAndIncrement x 270 ops/sec ±1.18% (81 runs sampled)
arrayPushAndReduce x 1,524 ops/sec ±0.79% (89 runs sampled)
Fastest is arrayPushAndReduce

Так что используя Уменьшить Метод дал нам функцию, которая была 5 раз быстрее !

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

Функция № 3: В то время как массив, уменьшить

Сейчас я всегда добираюсь до надежды для петля, я подумал, что проверил в то время как Контур альтернативы:

function whileLoopArrayReduce(n) {
    var array = [];
    while (n >= 1) {
    	n--;
        if (n%3==0||n%5==0) {
        	array.push(n);
        }  
    }  
    return array.reduce(function(prev, current) { 
    	return prev + current;
    });
}

module.exports = whileLoopArrayReduce;

И результат? Крошечный бит медленнее:

whileLoopArrayReduce x 1,504 ops/sec ±0.65% (88 runs sampled)

Функция № 4: В то время как, сумма, нет массивов

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

function whileSum(n) {
    var sum = 0;
    while (n >= 1) {
        n--;
        if (n%3==0||n%5==0) {
            sum += n;
        }
    }  
    return sum;
}

module.exports = whileSum;

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

whileSum x 7,311 ops/sec ±1.26% (91 runs sampled)

Еще одно массовое улучшение: почти 5 раз быстрее Опять же, и 27 раз быстрее чем моя оригинальная функция!

Функция № 5: для, сумма

Конечно, мы уже знаем, что для петли должен быть немного быстрее:

function forSum(n) {
    n = n-1;
    var sum = 0;
    for (n; n >= 1 ;n--) {
        (n%3==0||n%5==0) ? sum += n : null;
    }  
    return sum;
}

Это использует Ternary Operator для проверки состояния, но мое тестирование показало, что нетрарная версия этого является той же, производительностью.

forSum x 8,256 ops/sec ±0.24% (91 runs sampled)

Итак, немного быстрее снова.

Моя окончательная функция закончилась быть 28 раз быстрее чем мой оригинал.

Я чувствовал себя как чемпион.

Я был над луной.

Я отдыхал на лаврах.

Введите математику

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

function multSilgarth(N) {
    var threes = Math.floor(--N / 3);  
    var fives = Math.floor(N / 5);
    var fifteen = Math.floor(N / 15);
    return (3 * threes * (threes + 1) + 5 * fives * (fives + 1) - 15 * fifteen * (fifteen + 1)) / 2;
}

module.exports = multSilgarth;

Ждать его…

arrayPushAndIncrement x 279 ops/sec ±0.80% (83 runs sampled)
forSum x 8,256 ops/sec ±0.24% (91 runs sampled)
maths x 79,998,859 ops/sec ±0.81% (88 runs sampled)
Fastest is maths

Самый быстрый – математика

Так что выигрышная функция была примерно 9,690 раз быстрее чем мои лучшие усилия, и 275 858 раз быстрее чем мои первоначальные усилия.

Если вам нужна, я буду закончиться в Ханской академии, изучающей математику.

Спасибо всем, что участвовали, и поделились своими решениями в духе помощи другим туристам изучать новые методы.

Если вам интересно, вот запись P1XT на соревнованиях, а также все данные тестирования и тестирования:

P1XT/ALGO-OCT-17 Algo-Oct-17 – JavaScript Algorithm Challenge – 9 по 16 октября github.com.