我正在尝试解决有关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";
}
答案 0 :(得分:1)
当性能重要且可读性降低不是问题时,您应该:
首选for
和while
循环,而不是map
,forEach
...
在JavaScript
的最近(2016)版本中,它看起来像for
循环,特别是反向for
循环,是性能最佳的选项。
另外,请记住,您不需要使用其3个表达式。例如,对我来说......:
let found = 0;
for (;found < 10;) {
和
let j = chars.length;
for (;j;) {
始终返回比初始化时段和while
循环中的初始化更好的结果,尽管在后一种情况下差异不是那么大。
在foo.bar
或while
的表达式(例如for
)中使用for (let i = 0; i < array.length; ++i)
时,更愿意声明限制上述条件,以便每次都不进行评估,因为它涉及object lookup:
const totalElements = array.length;
for (let i = 0; i < totalElements; ++i) { ... }
首选预增量而不是后增量。后者将创建一个临时变量,存储预增量值,它将返回给您,而前者将首先执行增量,然后返回增量值。不需要临时变量。
有关详情,请参阅post increment vs pre increment - Javascript Optimization
实际上,如果无论你使用哪一个,结果都是一样的,那么我建议你尽可能使用预增量。
避免使用具有等效表达式的内置方法。例如,(n + '')
的效果比n.toString()
更高。
避免类型转换,并且更喜欢使用整数而不是浮点数或字符串,因为现代JS引擎标记变量类型。这意味着,一方面,改变类型会有性能损失,另一方面,一致地使用它们将允许引擎进行某些类型特定的优化。
尽可能使用bitwise operators。例如,可以使用Math.floor(a /b)
或a / b | 0
进行整数除法,这几乎是原来的两倍。
有关JavaScript
中整数除法的更多信息,请参阅此有趣的帖子:How to perform integer division and get the remainder in JavaScript?
首选对象查询(object.prop
或object['prop']
),而不是使用Array.prototype.indexOf(...)
。
请参阅Javascript: what lookup is faster: array.indexOf vs object hash?
当存在更简单结构或不可变数据结构的替代方法时,避免使用arrays
和objects
及相关方法。
例如,@ RobG的解决方案使用splice
。虽然我不了解内部实现,但可能是将已删除的元素移动到再次压缩数组并更新其length
。
但是,使用我的解决方案,数组的length
始终是相同的,您只是将其值从false
更改为true
,这会减少开销,因为& #39;不需要重新分配空间。
尽可能使用类型化数组,虽然我在这里尝试了Uint8Array
,但都指定了true
和1
,但没有一个改进时间;前者实际上几乎翻了一倍的时间,第一个保持它们或多或少相同。也许它有BooleanArray
它可以工作。
请记住,这只是我认为可能有助于加快您的示例的一些技术或功能的列表。我强烈建议您阅读我添加的外部链接,以便更好地了解它们的工作方式和原因,以及它们可以应用于何处。
此外,通常情况下,您保留代码的较低级别,即使用基本数据类型和操作,将是最高性能。
为了证明这一点,我在下面向您展示了此代码的高度优化版本,该版本使用整数除法(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`);
&#13;
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's 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`);
&#13;
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
答案 1 :(得分:0)
以下代码没有特殊优化,并使用所有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 方法可能很慢,因此这是一个大约twice as fast的字符串版本:
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次,因此移出条件并不是很重要。另一种方法是包含一个计数器,每次将一个数字添加到 found 时,该计数器会递增,但这并不是为了优化性能,而是找到合理的工作并通过(未公开的)测试在代码战中。
答案 2 :(得分:0)
另一种方法:
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));
&#13;