Я пытаюсь решить вопрос о CodeWars, который был частью квалификационных раундов Google CodeJam 2016. https://www.codewars.com/kata/bleatrix-trotter-the-counting-sheep/train/javascript
Я считаю, что мой код учитывает все тестовые примеры, моя единственная проблема заключается в том, что он не может быть отправлен на кодовые слова, потому что он имеет время работы более 12000 мс.
Как я могу сделать свой код более эффективным? также есть лучшая практика для проверки того, будет ли цикл бесконечным.
function trotter(n) {
var tracker = [];
var sum = [];
var snacker = n;
for(var i = 0; tracker.length < 10; i++){
sum = snacker.toString().split('');
sum.forEach(function(num) {
if (tracker.indexOf(num) == -1) {
tracker.push(num);
}
});
snacker += n;
}
return tracker.length === 10 ? snacker - n : "INSOMNIA";
}
Когда производительность и снижение читаемости не являются проблемой, вы должны:
Предпочитайте for
и в while
циклы, а не встроенные, такие как map
, forEach
...
В последних версиях JavaScript
(2016) это похоже на то, что for
циклов, особенно обратных for
циклов, является наиболее эффективным вариантом.
Кроме того, имейте в виду, что вам не нужно использовать свои 3 выражения. Например, для меня...:
let found = 0;
for (;found < 10;) {
а также
let j = chars.length;
for (;j;) {
последовательно возвращаются лучшие результаты, чем то же самое с инициализацией в слоте инициализации и чем в while
циклы, хотя в этом последнем случае разница не столь велика.
Более того, см. Javascript Performance: While vs For Loops
При использовании foo.bar
через некоторое while
или for
выражения, например, for (let я = 0; я < array.length; ++i)
, предпочитайте объявлять предельное условие выше, чтобы оно не оценивалось каждый раз, так как это включает поиск объектов:
const totalElements = array.length;
for (let i = 0; i < totalElements; ++i) { ... }
Предпочитайте pre-increment вместо post-increment. Последний создаст временную переменную, хранящую значение pre-incrementation, которое вернется к вам, в то время как первое сначала выполнит приращение, а затем вернет увеличенное значение. Никакой временной переменной не требуется.
Подробнее об этом см. В разделе post increment vs pre increment - Оптимизация Javascript
Фактически, если результат будет таким же, независимо от того, какой из них вы используете, я бы посоветовал вам использовать предварительный приращение, когда это возможно.
Избегайте встроенных методов, имеющих эквивалентное выражение. Например, (n + '')
более совершенен, чем n.toString()
.
Избегайте преобразований типов и предпочитайте использовать целые числа, а не плавающие или строки, как современные типы переменных тегов JS-движков. Это означает, что, с одной стороны, изменение типов будет иметь штраф за производительность, а с другой стороны, что использование их последовательно позволит движку выполнять некоторую оптимизацию по типу.
Подробнее об этом см. Https://www.html5rocks.com/en/tutorials/speed/v8/.
По возможности используйте побитовые операторы. Например, целочисленное деление может быть выполнено с помощью Math.floor(a/b)
или с a/b | 0
a/b | 0
, что почти в два раза быстрее.
Подробнее о целочисленном делении в JavaScript
см. В этом интересном разделе: Как выполнить целочисленное деление и получить остаток в JavaScript?
Предпочитайте поиск объекта (object.prop
или object['prop']
) вместо использования Array.prototype.indexOf(...)
.
См. Javascript: какой поиск выполняется быстрее: array.indexOf против хеша объекта?
Избегайте использования arrays
и objects
и связанных с ними методов, когда есть альтернативы с более простыми структурами или неуязвимыми структурами данных.
Например, решение @RobG использует splice
. Хотя я не знаю о внутренней реализации, возможно, это перемещение элементов, которые появляются после удаленных, чтобы снова объединить массив и обновить его length
.
Однако с моим решением length
массива всегда одна и та же, и вы просто меняете свои значения от false
до true
, что требует гораздо меньших накладных расходов, так как нет необходимости перераспределять пространство.
Если возможно, используйте типизированные массивы, хотя я попытался Uint8Array
здесь Uint8Array
, назначив ему true
и 1
, и ни один из них не улучшил время; первый фактически почти удвоил время, а первый сохранил их более или менее одинаковыми. Возможно, это был BooleanArray
который мог бы работать.
Имейте в виду, что это всего лишь список некоторых методов или функций, которые, я думаю, могут помочь ускорить ваш пример. Я настоятельно рекомендую вам прочитать внешние ссылки, которые я добавил, чтобы лучше понять, как и почему они работают, и где они могут быть применены.
Более того, в целом, на более низком уровне вы сохраняете свой код, т.е. Используете основные типы данных и операции, наиболее эффективными будут.
Чтобы доказать это, ниже я покажу вам высоко оптимизированную версию этого кода, которая использует целочисленное деление (n/10) | 0
(n/10) | 0
и остаток (%
).
function trotter(N) {
if (N === 0) return 'INSOMNIA';
const digits = [false, false, false, false, false, false, false, false, false, false];
let n;
let last = 0;
let found = 0;
for (;found < 10;) {
n = last += N;
for (;n;) {
const digit = n % 10;
n = (n / 10) | 0;
if (!digits[digit]) {
digits[digit] = true;
++found;
}
}
}
return last;
}
const numbers = [0, 2, 7, 125, 1625, 1692];
const outputs = ['INSOMNIA', 90, 70, 9000, 9750, 5076];
// CHECK IT WORKS FIRST:
numbers.map((number, index) => {
if (trotter(number) !== outputs[index]) {
console.log('EXPECTED = ' + outputs[index]);
console.log(' GOT = ' + trotter(number));
throw new Error('Incorrect value.');
}
});
// PERF. TEST:
const ITERATIONS = 1000000;
const t0 = performance.now();
for (let i = 0; i < ITERATIONS; ++i) {
numbers.map((number, index) => trotter(number));
}
const t1 = performance.now();
console.log('AVG. TIME: ${ (t1 - t0) / ITERATIONS } ms. with ${ ITERATIONS } ITERATIONS');
AVG. TIME: 0.0033206450000000005 ms. with 1000000 ITERATIONS
BROWSER: Google Chrome Version 59.0.3071.86 (Official Build) (64-bit)
OS: macOS Sierra
BRAND, MODEL: MacBook Pro (Retina, 15-inch, Mid 2015)
PROCESSOR: 2,8 GHz Intel Core i7
MEMORY: 16 GB 1600 MHz DDR3
Ниже вы можете увидеть мой первоначальный ответ, который использует некоторые из других оптимизаций, перечисленных здесь, но все же преобразует number
переменного n
в string
и использует String.prototype.split()
, чтобы получить свои цифры.
Это почти в 5 раз медленнее, чем выше!
function trotter(N) {
if (N === 0) return 'INSOMNIA';
const digits = [false, false, false, false, false, false, false, false, false, false];
let n = N;
let i = 0;
let found = 0;
for (;found < 10;) {
// There no need for this multiplication:
n = N * ++i;
// Type conversion + Built-in String.prototype.split(), both can
// be avoided:
const chars = (n + '').split('');
let j = chars.length;
for (;j;) {
const digit = chars[--j];
if (!digits[digit]) {
digits[digit] = true;
++found;
}
}
}
return n;
}
const numbers = [0, 2, 7, 125, 1625, 1692];
const outputs = ['INSOMNIA', 90, 70, 9000, 9750, 5076];
// CHECK IT WORKS FIRST:
numbers.map((number, index) => {
if (trotter(number) !== outputs[index]) {
console.log('EXPECTED = ' + outputs[index]);
console.log(' GOT = ' + trotter(number));
throw new Error('Incorrect value.');
}
});
// PERF. TEST:
const ITERATIONS = 1000000;
const t0 = performance.now();
for (let i = 0; i < ITERATIONS; ++i) {
numbers.map((number, index) => trotter(number));
}
const t1 = performance.now();
console.log('AVG. TIME: ${ (t1 - t0) / ITERATIONS } ms. with ${ ITERATIONS } ITERATIONS');
AVG. TIME: 0.016428575000000004 ms. with 1000000 ITERATIONS
BROWSER: Google Chrome Version 59.0.3071.86 (Official Build) (64-bit)
OS: macOS Sierra
BRAND, MODEL: MacBook Pro (Retina, 15-inch, Mid 2015)
PROCESSOR: 2,8 GHz Intel Core i7
MEMORY: 16 GB 1600 MHz DDR3
Другой подход:
function trotter(n){
var unseen = '0123456789', last = 0;
while (unseen != '' && last < 72*n) {
last += n;
var reg = new RegExp('[' + last + ']+', 'g');
unseen = unseen.replace(reg, '');
}
return unseen != '' ? 'INSOMNIA' : last;
};
console.log('0 : ' + trotter(0));
console.log('125 : ' + trotter(125));
console.log('1625: ' + trotter(1625));
RegExp
и Array.prototype.replace
производительность. Я попытался измерить ваше время выполнения нескольких итераций, используя настройки теста в моем ответе, и мне пришлось убить вкладку ...: \
Следующий код не имеет специальных оптимизаций и использует все методы ECMA-262 ed 3. Он провел полный набор тестов за 339 мс.
function trotter(n){
var nums = ['0','1','2','3','4','5','6','7','8','9'];
var i = 1;
// Zero creates an infinite loop, so skip it
if (n !== 0) {
// While there are numbers to remove, keep going
// Limit loops to 100 just in case
while (nums.length && i < 100) {
// Get test number as an array of digits
var d = ('' + (n * i)).split('');
// For each digit, if in nums remove it
for (var j=0, jLen=d.length; j<jLen; j++) {
var idx = nums.indexOf(d[j]);
if (idx > -1) nums.splice(idx, 1);
}
i++;
}
}
// If there are numbers left, didn't get to sleep
// Otherwise, return last number seen (put d back together and make a numer)
return nums.length? 'INSOMNIA' : Number(d.join(''));
}
console.log('0 : ' + trotter(0));
console.log('125 : ' + trotter(125));
console.log('1625: ' + trotter(1625));
Большинство случаев разрешаются примерно через 10 итераций, однако 125, 1250, 12500 и т.д. Принимают 73 итерации, которые, по моему мнению, наиболее важны для любого числа.
Поскольку методы Array могут быть медленными, вот строковая версия, которая примерно в два раза быстрее:
function trotter(n){
var found = '';
if (n !== 0) {
var i = 1;
while (found.length < 10) {
var d = i * n + '';
var j = d.length;
while (j) {
var c = d[--j];
var idx = found.indexOf(c);
if (idx == -1) found += c;
}
i++;
}
}
return found.length == 10? +d : 'INSOMNIA';
}
[0, // Infinte loop case
125, // Max loop case
1625] // just a number
.forEach(function (n) {
console.log(n + ' : ' + trotter(n));
});
Хотя while (found.length)
не является оптимальным, он обычно вычисляется только в 10 раз, поэтому не очень важно перемещаться за пределы условия. Альтернативой является включение счетчика, который увеличивается каждый раз, когда цифра добавляется к найденному, но это не значит оптимизировать производительность так же, как найти что-то разумное, которое работает и передает (нераскрытые) тесты на кодовых таблицах.