是否有一种机制可以在没有可变变量的情况下在 ES6 (ECMAScript 6) 中循环 x 次?
Is there a mechanism to loop x times in ES6 (ECMAScript 6) without mutable variables?
在JavaScript中循环x
次的典型方法是:
for (var i = 0; i < x; i++)
doStuff(i);
但我根本不想使用 ++
运算符或任何可变变量。那么,在 ES6 中,有没有办法以另一种方式循环 x
次?我喜欢 Ruby 的机制:
x.times do |i|
do_stuff(i)
end
JavaScript/ES6 中有任何相似之处吗?我可以作弊并制作自己的发电机:
function* times(x) {
for (var i = 0; i < x; i++)
yield i;
}
for (var i of times(5)) {
console.log(i);
}
当然我还在用i++
。至少它是看不见的:),但我希望 ES6 中有更好的机制。
Afaik,ES6 中没有类似于Ruby 的times
方法的机制。但是你可以通过使用递归来避免突变:
let times = (i, cb, l = i) => {
if (i === 0) return;
cb(l - i);
times(i - 1, cb, l);
}
times(5, i => doStuff(i));
我不会教(或在我的代码中使用过),但这是一个值得代码高尔夫的解决方案,无需改变变量,不需要 ES6:
Array.apply(null, {length: 10}).forEach(function(_, i){
doStuff(i);
})
与其说是有用的答案,不如说是有趣的概念验证。
for (let i of Array(100).keys()) {
console.log(i)
}
好的!
下面的代码是使用 ES6 语法编写的,但也可以很容易地用 ES5 或更少的语法编写。 ES6 不是创建"mechanism to loop x times"
的要求
如果回调中不需要迭代器,这是最简单的实现
const times = x => f => {
if (x > 0) {
f()
times (x - 1) (f)
}
}
// use it
times (3) (() => console.log('hi'))
// or define intermediate functions for reuse
let twice = times (2)
// twice the power !
twice (() => console.log('double vision'))
如果你确实需要迭代器,你可以使用带计数器参数的命名内部函数为你迭代
const times = n => f => {
let iter = i => {
if (i === n) return
f (i)
iter (i + 1)
}
return iter (0)
}
times (3) (i => console.log(i, 'hi'))
Stop reading here if you don't like learning more things ...
但是这些应该有些不对劲...
- 单个分支
if
语句很难看 — 另一个分支会发生什么?
- 函数体中有多个 statements/expressions — 过程问题是否混合?
- 隐式返回
undefined
— 表示不纯、有副作用的函数
"Isn't there a better way ?"
有。让我们首先回顾一下我们的初始实现
// times :: Int -> (void -> void) -> void
const times = x => f => {
if (x > 0) {
<b>f()</b> // has to be side-effecting function
times (x - 1) (f)
}
}
当然,这很简单,但请注意我们如何只调用 f()
而没有对其进行任何操作。这确实限制了我们可以重复多次的函数类型。即使我们有可用的迭代器,f(i)
也没有多用途。
如果我们从一种更好的函数重复过程开始呢?也许可以更好地利用输入和输出。
泛型函数重复
// repeat :: forall a. Int -> (a -> a) -> a -> a
const repeat = n => f => x => {
if (n > 0)
return repeat (n - 1) (f) (f (x))
else
return x
}
// power :: Int -> Int -> Int
const power = base => exp => {
// repeat <exp> times, <base> * <x>, starting with 1
return repeat (exp) (x => base * x) (1)
}
console.log(power (2) (8))
// => 256
上面,我们定义了一个通用的 repeat
函数,它接受一个额外的输入,用于启动单个函数的重复应用。
// repeat 3 times, the function f, starting with x ...
var result = repeat (3) (f) (x)
// is the same as ...
var result = f(f(f(x)))
用 repeat
实现 times
好吧,现在这很容易;几乎所有的工作都已经完成。
// repeat :: forall a. Int -> (a -> a) -> a -> a
const repeat = n => f => x => {
if (n > 0)
return repeat (n - 1) (f) (f (x))
else
return x
}
// times :: Int -> (Int -> Int) -> Int
const times = n=> f=>
repeat (n) (i => (f(i), i + 1)) (0)
// use it
times (3) (i => console.log(i, 'hi'))
由于我们的函数将 i
作为输入,returns i + 1
,这有效地用作我们每次传递给 f
的迭代器。
我们也修复了问题列表
- 不再有丑陋的单分支
if
语句
- 单表达式正文表示关注点分离得很好
- 不再无用,隐式返回
undefined
JavaScript逗号运算符,
如果您无法理解最后一个示例的工作原理,这取决于您对 JavaScript 最古老的战斧之一的认识; comma operator——简而言之,它从左到右计算表达式,returns最后计算的表达式的值
(expr1 :: a, expr2 :: b, expr3 :: c) :: c
在我们上面的例子中,我使用
(i => (f(i), i + 1))
这只是一种简洁的写法
(i => { f(i); return i + 1 })
尾调用优化
尽管递归实现很性感,但在这一点上,我推荐它们是不负责任的,因为没有JavaScript VM我能想到支持适当的尾调用消除——babel 用于转换它,但是它已经处于 "broken; will reimplement" 状态一年多了。
repeat (1e6) (someFunc) (x)
// => RangeError: Maximum call stack size exceeded
因此,我们应该重新审视 repeat
的实现,以使其成为堆栈安全的。
下面的代码确实使用了可变变量n
和x
,但请注意,所有突变都局限于repeat
函数——没有从函数外部可以看到状态变化(突变)
// repeat :: Int -> (a -> a) -> (a -> a)
const repeat = n => f => x =>
{
let m = 0, acc = x
while (m < n)
(m = m + 1, acc = f (acc))
return acc
}
// inc :: Int -> Int
const inc = x =>
x + 1
console.log (repeat (1e8) (inc) (0))
// 100000000
这会让很多人说 "but that's not functional !" – 我知道,放轻松。我们可以使用 纯表达式 为常量 space 循环实现 Clojure 风格的 loop
/recur
接口; none 个 while
东西。
这里我们用 loop
函数将 while
抽象出来——它寻找一个特殊的 recur
类型来保持循环 运行。当遇到非recur
类型时,循环结束,返回计算结果
const recur = (...args) =>
({ type: recur, args })
const loop = f =>
{
let acc = f ()
while (acc.type === recur)
acc = f (...acc.args)
return acc
}
const repeat = $n => f => x =>
loop ((n = $n, acc = x) =>
n === 0
? acc
: recur (n - 1, f (acc)))
const inc = x =>
x + 1
const fibonacci = $n =>
loop ((n = $n, a = 0, b = 1) =>
n === 0
? a
: recur (n - 1, b, a + b))
console.log (repeat (1e7) (inc) (0)) // 10000000
console.log (fibonacci (100)) // 354224848179262000000
解决功能方面的问题:
function times(n, f) {
var _f = function (f) {
var i;
for (i = 0; i < n; i++) {
f(i);
}
};
return typeof f === 'function' && _f(f) || _f;
}
times(6)(function (v) {
console.log('in parts: ' + v);
});
times(6, function (v) {
console.log('complete: ' + v);
});
我认为最好的解决方案是使用 let
:
for (let i=0; i<100; i++) …
这将为每个主体评估创建一个新的(可变的)i
变量,并确保 i
仅在该循环语法的增量表达式中更改,而不是从其他任何地方更改。
I could kind of cheat and make my own generator. At least i++
is out of sight :)
我觉得应该够了。即使在纯语言中,所有操作(或至少是它们的解释器)都是从使用变异的原语构建的。只要范围适当,我就看不出有什么问题。
你应该没问题
function* times(n) {
for (let i = 0; i < n; i++)
yield i;
}
for (const i of times(5)) {
console.log(i);
}
But I don't want to use the ++
operator or have any mutable variables at all.
那么你唯一的选择就是使用递归。您也可以定义不带可变 i
的生成器函数:
function* range(i, n) {
if (i >= n) return;
yield i;
return yield* range(i+1, n);
}
times = (n) => range(0, n);
但这对我来说似乎太过分了,并且可能会出现性能问题(因为尾调用消除不适用于 return yield*
)。
答案:2015 年 12 月 9 日
就我个人而言,我发现接受的答案既简洁(好)又简洁(坏)。欣赏这个说法可能是主观的,所以请阅读这个答案,看看你是否同意
问题中给出的例子类似于Ruby的:
x.times do |i|
do_stuff(i)
end
在 JS 中使用下面的表达方式将允许:
times(x)(doStuff(i));
代码如下:
let times = (n) => {
return (f) => {
Array(n).fill().map((_, i) => f(i));
};
};
就是这样!
简单示例用法:
let cheer = () => console.log('Hip hip hooray!');
times(3)(cheer);
//Hip hip hooray!
//Hip hip hooray!
//Hip hip hooray!
或者,按照已接受答案的示例:
let doStuff = (i) => console.log(i, ' hi'),
once = times(1),
twice = times(2),
thrice = times(3);
once(doStuff);
//0 ' hi'
twice(doStuff);
//0 ' hi'
//1 ' hi'
thrice(doStuff);
//0 ' hi'
//1 ' hi'
//2 ' hi'
旁注 - 定义 运行ge 函数
一个类似/相关的问题,它使用基本非常相似的代码结构,可能是(核心)JavaScript 中是否有一个方便的 Range 函数,类似于下划线的 运行ge 函数。
创建一个n个数的数组,从x开始
下划线
_.range(x, x + n)
ES2015
几个备选方案:
Array(n).fill().map((_, i) => x + i)
Array.from(Array(n), (_, i) => x + i)
使用 n = 10、x = 1 的演示:
> Array(10).fill().map((_, i) => i + 1)
// [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
> Array.from(Array(10), (_, i) => i + 1)
// [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
在我 运行 的快速测试中,上述每个 运行 使用我们的解决方案和 doStuff 函数各一百万次,前一种方法 (Array(n).fill())事实证明速度稍快。
Array(100).fill().map((_,i)=> console.log(i) );
此版本满足 OP 对不变性的要求。根据您的用例,还可以考虑使用 reduce
而不是 map
。
如果您不介意原型中的一点变化,这也是一个选项。
Number.prototype.times = function(f) {
return Array(this.valueOf()).fill().map((_,i)=>f(i));
};
现在我们可以做到了
((3).times(i=>console.log(i)));
+1 到 arcseldon 以获得 .fill
建议。
[...Array(n)].map()
const res = [...Array(10)].map((_, i) => {
return i * 10;
});
// as a one liner
const res = [...Array(10)].map((_, i) => i * 10);
或者如果您不需要结果:
[...Array(10)].forEach((_, i) => {
console.log(i);
});
// as a one liner
[...Array(10)].forEach((_, i) => console.log(i));
或使用 ES2015 Array.from operator:
Array.from(...)
const res = Array.from(Array(10)).map((_, i) => {
return i * 10;
});
// as a one liner
const res = Array.from(Array(10)).map((_, i) => i * 10);
请注意,如果您只需要重复一个字符串,您可以使用 String.prototype.repeat。
console.log("0".repeat(10))
// 0000000000
如果你愿意使用图书馆,还有 lodash _.times
or underscore _.times
:
_.times(x, i => {
return doStuff(i)
})
注意这个 returns 结果数组,所以它更像这样 ruby:
x.times.map { |i|
doStuff(i)
}
在函数范式中repeat
通常是一个无限递归函数。要使用它,我们需要延迟评估或连续传递样式。
惰性评估函数重复
const repeat = f => x => [x, () => repeat(f) (f(x))];
const take = n => ([x, f]) => n === 0 ? x : take(n - 1) (f());
console.log(
take(8) (repeat(x => x * 2) (1)) // 256
);
我在 Javascript.
中使用 thunk(一个没有参数的函数)来实现惰性求值
具有连续传递样式的函数重复
const repeat = f => x => [x, k => k(repeat(f) (f(x)))];
const take = n => ([x, k]) => n === 0 ? x : k(take(n - 1));
console.log(
take(8) (repeat(x => x * 2) (1)) // 256
);
CPS 一开始有点吓人。然而,它总是遵循相同的模式:最后一个参数是延续(一个函数),它调用它自己的主体:k => k(...)
。请注意,CPS 将应用程序从里到外翻转,即 take(8) (repeat...)
变为 k(take(8)) (...)
,其中 k
是部分应用的 repeat
.
结论
通过将重复 (repeat
) 与终止条件 (take
) 分开,我们获得了灵活性 - 将关注点分离到痛苦的结局 :D
发电机?递归? 为什么这么多讨厌变异? ;-)
如果我们"hide"可以接受,那么就接受一元运算符的使用,我们可以保持简单:
Number.prototype.times = function(f) { let n=0 ; while(this.valueOf() > n) f(n++) }
就像在ruby中一样:
> (3).times(console.log)
0
1
2
我觉得很简单:
[...Array(3).keys()]
或
Array(3).fill()
const times = 4;
new Array(times).fill().map(() => console.log('test'));
此代码段将 console.log
test
4 次。
此解决方案的优势
- 阅读/使用最简单 (imo)
- Return 值可以作为求和,或者直接忽略
- 普通es6版本,还有link到TypeScript version的代码
缺点
- 突变。只是内部我不在乎,也许其他人也不会。
示例和代码
times(5, 3) // 15 (3+3+3+3+3)
times(5, (i) => Math.pow(2,i) ) // 31 (1+2+4+8+16)
times(5, '<br/>') // <br/><br/><br/><br/><br/>
times(3, (i, count) => { // name[0], name[1], name[2]
let n = 'name[' + i + ']'
if (i < count-1)
n += ', '
return n
})
function times(count, callbackOrScalar) {
let type = typeof callbackOrScalar
let sum
if (type === 'number') sum = 0
else if (type === 'string') sum = ''
for (let j = 0; j < count; j++) {
if (type === 'function') {
const callback = callbackOrScalar
const result = callback(j, count)
if (typeof result === 'number' || typeof result === 'string')
sum = sum === undefined ? result : sum + result
}
else if (type === 'number' || type === 'string') {
const scalar = callbackOrScalar
sum = sum === undefined ? scalar : sum + scalar
}
}
return sum
}
TypeScipt 版本
https://codepen.io/whitneyland/pen/aVjaaE?editors=0011
我来晚了,但由于这个问题经常出现在搜索结果中,我只想添加一个我认为在可读性方面最好的解决方案,而不是很长(这是理想的对于任何代码库 IMO)。它会发生变化,但我会为 KISS 原则做出权衡。
let times = 5
while( times-- )
console.log(times)
// logs 4, 3, 2, 1, 0
这是另一个不错的选择:
Array.from({ length: 3}).map(...);
最好,正如@Dave Morse 在评论中指出的那样,您也可以通过使用 Array.from
函数的第二个参数来摆脱 map
调用,如下所示:
Array.from({ length: 3 }, () => (...))
我用辅助函数包装了@Tieme 的回答。
在打字稿中:
export const mapN = <T = any[]>(count: number, fn: (...args: any[]) => T): T[] => [...Array(count)].map((_, i) => fn())
现在您可以运行:
const arr: string[] = mapN(3, () => 'something')
// returns ['something', 'something', 'something']
我做了这个:
function repeat(func, times) {
for (var i=0; i<times; i++) {
func(i);
}
}
用法:
repeat(function(i) {
console.log("Hello, World! - "+i);
}, 5)
/*
Returns:
Hello, World! - 0
Hello, World! - 1
Hello, World! - 2
Hello, World! - 3
Hello, World! - 4
*/
i
变量 returns 已循环的次数 - 如果您需要预加载 x 数量的图像,则很有用。
我只是要把它放在这里。如果您正在寻找不使用数组的紧凑函数并且您对 mutability/immutability 没有问题:
var g =x=>{/*your code goes here*/x-1>0?g(x-1):null};
我能想到的在范围
内创建list/array的最简单方法
Array.from(Array(max-min+1), (_, index) => index+min)
对我来说,这是对许多级别的开发人员来说最容易理解的答案
const times = (n, callback) => {
while (n) {
callback();
n--;
}
}
times(10, ()=> console.log('hello'))
在JavaScript中循环x
次的典型方法是:
for (var i = 0; i < x; i++)
doStuff(i);
但我根本不想使用 ++
运算符或任何可变变量。那么,在 ES6 中,有没有办法以另一种方式循环 x
次?我喜欢 Ruby 的机制:
x.times do |i|
do_stuff(i)
end
JavaScript/ES6 中有任何相似之处吗?我可以作弊并制作自己的发电机:
function* times(x) {
for (var i = 0; i < x; i++)
yield i;
}
for (var i of times(5)) {
console.log(i);
}
当然我还在用i++
。至少它是看不见的:),但我希望 ES6 中有更好的机制。
Afaik,ES6 中没有类似于Ruby 的times
方法的机制。但是你可以通过使用递归来避免突变:
let times = (i, cb, l = i) => {
if (i === 0) return;
cb(l - i);
times(i - 1, cb, l);
}
times(5, i => doStuff(i));
我不会教(或在我的代码中使用过),但这是一个值得代码高尔夫的解决方案,无需改变变量,不需要 ES6:
Array.apply(null, {length: 10}).forEach(function(_, i){
doStuff(i);
})
与其说是有用的答案,不如说是有趣的概念验证。
for (let i of Array(100).keys()) {
console.log(i)
}
好的!
下面的代码是使用 ES6 语法编写的,但也可以很容易地用 ES5 或更少的语法编写。 ES6 不是创建"mechanism to loop x times"
的要求如果回调中不需要迭代器,这是最简单的实现
const times = x => f => {
if (x > 0) {
f()
times (x - 1) (f)
}
}
// use it
times (3) (() => console.log('hi'))
// or define intermediate functions for reuse
let twice = times (2)
// twice the power !
twice (() => console.log('double vision'))
如果你确实需要迭代器,你可以使用带计数器参数的命名内部函数为你迭代
const times = n => f => {
let iter = i => {
if (i === n) return
f (i)
iter (i + 1)
}
return iter (0)
}
times (3) (i => console.log(i, 'hi'))
Stop reading here if you don't like learning more things ...
但是这些应该有些不对劲...
- 单个分支
if
语句很难看 — 另一个分支会发生什么? - 函数体中有多个 statements/expressions — 过程问题是否混合?
- 隐式返回
undefined
— 表示不纯、有副作用的函数
"Isn't there a better way ?"
有。让我们首先回顾一下我们的初始实现
// times :: Int -> (void -> void) -> void
const times = x => f => {
if (x > 0) {
<b>f()</b> // has to be side-effecting function
times (x - 1) (f)
}
}
当然,这很简单,但请注意我们如何只调用 f()
而没有对其进行任何操作。这确实限制了我们可以重复多次的函数类型。即使我们有可用的迭代器,f(i)
也没有多用途。
如果我们从一种更好的函数重复过程开始呢?也许可以更好地利用输入和输出。
泛型函数重复
// repeat :: forall a. Int -> (a -> a) -> a -> a
const repeat = n => f => x => {
if (n > 0)
return repeat (n - 1) (f) (f (x))
else
return x
}
// power :: Int -> Int -> Int
const power = base => exp => {
// repeat <exp> times, <base> * <x>, starting with 1
return repeat (exp) (x => base * x) (1)
}
console.log(power (2) (8))
// => 256
上面,我们定义了一个通用的 repeat
函数,它接受一个额外的输入,用于启动单个函数的重复应用。
// repeat 3 times, the function f, starting with x ...
var result = repeat (3) (f) (x)
// is the same as ...
var result = f(f(f(x)))
用 repeat
times
好吧,现在这很容易;几乎所有的工作都已经完成。
// repeat :: forall a. Int -> (a -> a) -> a -> a
const repeat = n => f => x => {
if (n > 0)
return repeat (n - 1) (f) (f (x))
else
return x
}
// times :: Int -> (Int -> Int) -> Int
const times = n=> f=>
repeat (n) (i => (f(i), i + 1)) (0)
// use it
times (3) (i => console.log(i, 'hi'))
由于我们的函数将 i
作为输入,returns i + 1
,这有效地用作我们每次传递给 f
的迭代器。
我们也修复了问题列表
- 不再有丑陋的单分支
if
语句 - 单表达式正文表示关注点分离得很好
- 不再无用,隐式返回
undefined
JavaScript逗号运算符,
如果您无法理解最后一个示例的工作原理,这取决于您对 JavaScript 最古老的战斧之一的认识; comma operator——简而言之,它从左到右计算表达式,returns最后计算的表达式的值
(expr1 :: a, expr2 :: b, expr3 :: c) :: c
在我们上面的例子中,我使用
(i => (f(i), i + 1))
这只是一种简洁的写法
(i => { f(i); return i + 1 })
尾调用优化
尽管递归实现很性感,但在这一点上,我推荐它们是不负责任的,因为没有JavaScript VM我能想到支持适当的尾调用消除——babel 用于转换它,但是它已经处于 "broken; will reimplement" 状态一年多了。
repeat (1e6) (someFunc) (x)
// => RangeError: Maximum call stack size exceeded
因此,我们应该重新审视 repeat
的实现,以使其成为堆栈安全的。
下面的代码确实使用了可变变量n
和x
,但请注意,所有突变都局限于repeat
函数——没有从函数外部可以看到状态变化(突变)
// repeat :: Int -> (a -> a) -> (a -> a)
const repeat = n => f => x =>
{
let m = 0, acc = x
while (m < n)
(m = m + 1, acc = f (acc))
return acc
}
// inc :: Int -> Int
const inc = x =>
x + 1
console.log (repeat (1e8) (inc) (0))
// 100000000
这会让很多人说 "but that's not functional !" – 我知道,放轻松。我们可以使用 纯表达式 为常量 space 循环实现 Clojure 风格的 loop
/recur
接口; none 个 while
东西。
这里我们用 loop
函数将 while
抽象出来——它寻找一个特殊的 recur
类型来保持循环 运行。当遇到非recur
类型时,循环结束,返回计算结果
const recur = (...args) =>
({ type: recur, args })
const loop = f =>
{
let acc = f ()
while (acc.type === recur)
acc = f (...acc.args)
return acc
}
const repeat = $n => f => x =>
loop ((n = $n, acc = x) =>
n === 0
? acc
: recur (n - 1, f (acc)))
const inc = x =>
x + 1
const fibonacci = $n =>
loop ((n = $n, a = 0, b = 1) =>
n === 0
? a
: recur (n - 1, b, a + b))
console.log (repeat (1e7) (inc) (0)) // 10000000
console.log (fibonacci (100)) // 354224848179262000000
解决功能方面的问题:
function times(n, f) {
var _f = function (f) {
var i;
for (i = 0; i < n; i++) {
f(i);
}
};
return typeof f === 'function' && _f(f) || _f;
}
times(6)(function (v) {
console.log('in parts: ' + v);
});
times(6, function (v) {
console.log('complete: ' + v);
});
我认为最好的解决方案是使用 let
:
for (let i=0; i<100; i++) …
这将为每个主体评估创建一个新的(可变的)i
变量,并确保 i
仅在该循环语法的增量表达式中更改,而不是从其他任何地方更改。
I could kind of cheat and make my own generator. At least
i++
is out of sight :)
我觉得应该够了。即使在纯语言中,所有操作(或至少是它们的解释器)都是从使用变异的原语构建的。只要范围适当,我就看不出有什么问题。
你应该没问题
function* times(n) {
for (let i = 0; i < n; i++)
yield i;
}
for (const i of times(5)) {
console.log(i);
}
But I don't want to use the
++
operator or have any mutable variables at all.
那么你唯一的选择就是使用递归。您也可以定义不带可变 i
的生成器函数:
function* range(i, n) {
if (i >= n) return;
yield i;
return yield* range(i+1, n);
}
times = (n) => range(0, n);
但这对我来说似乎太过分了,并且可能会出现性能问题(因为尾调用消除不适用于 return yield*
)。
答案:2015 年 12 月 9 日
就我个人而言,我发现接受的答案既简洁(好)又简洁(坏)。欣赏这个说法可能是主观的,所以请阅读这个答案,看看你是否同意
问题中给出的例子类似于Ruby的:
x.times do |i|
do_stuff(i)
end
在 JS 中使用下面的表达方式将允许:
times(x)(doStuff(i));
代码如下:
let times = (n) => {
return (f) => {
Array(n).fill().map((_, i) => f(i));
};
};
就是这样!
简单示例用法:
let cheer = () => console.log('Hip hip hooray!');
times(3)(cheer);
//Hip hip hooray!
//Hip hip hooray!
//Hip hip hooray!
或者,按照已接受答案的示例:
let doStuff = (i) => console.log(i, ' hi'),
once = times(1),
twice = times(2),
thrice = times(3);
once(doStuff);
//0 ' hi'
twice(doStuff);
//0 ' hi'
//1 ' hi'
thrice(doStuff);
//0 ' hi'
//1 ' hi'
//2 ' hi'
旁注 - 定义 运行ge 函数
一个类似/相关的问题,它使用基本非常相似的代码结构,可能是(核心)JavaScript 中是否有一个方便的 Range 函数,类似于下划线的 运行ge 函数。
创建一个n个数的数组,从x开始
下划线
_.range(x, x + n)
ES2015
几个备选方案:
Array(n).fill().map((_, i) => x + i)
Array.from(Array(n), (_, i) => x + i)
使用 n = 10、x = 1 的演示:
> Array(10).fill().map((_, i) => i + 1)
// [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
> Array.from(Array(10), (_, i) => i + 1)
// [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
在我 运行 的快速测试中,上述每个 运行 使用我们的解决方案和 doStuff 函数各一百万次,前一种方法 (Array(n).fill())事实证明速度稍快。
Array(100).fill().map((_,i)=> console.log(i) );
此版本满足 OP 对不变性的要求。根据您的用例,还可以考虑使用 reduce
而不是 map
。
如果您不介意原型中的一点变化,这也是一个选项。
Number.prototype.times = function(f) {
return Array(this.valueOf()).fill().map((_,i)=>f(i));
};
现在我们可以做到了
((3).times(i=>console.log(i)));
+1 到 arcseldon 以获得 .fill
建议。
[...Array(n)].map()
const res = [...Array(10)].map((_, i) => {
return i * 10;
});
// as a one liner
const res = [...Array(10)].map((_, i) => i * 10);
或者如果您不需要结果:
[...Array(10)].forEach((_, i) => {
console.log(i);
});
// as a one liner
[...Array(10)].forEach((_, i) => console.log(i));
或使用 ES2015 Array.from operator:
Array.from(...)
const res = Array.from(Array(10)).map((_, i) => {
return i * 10;
});
// as a one liner
const res = Array.from(Array(10)).map((_, i) => i * 10);
请注意,如果您只需要重复一个字符串,您可以使用 String.prototype.repeat。
console.log("0".repeat(10))
// 0000000000
如果你愿意使用图书馆,还有 lodash _.times
or underscore _.times
:
_.times(x, i => {
return doStuff(i)
})
注意这个 returns 结果数组,所以它更像这样 ruby:
x.times.map { |i|
doStuff(i)
}
在函数范式中repeat
通常是一个无限递归函数。要使用它,我们需要延迟评估或连续传递样式。
惰性评估函数重复
const repeat = f => x => [x, () => repeat(f) (f(x))];
const take = n => ([x, f]) => n === 0 ? x : take(n - 1) (f());
console.log(
take(8) (repeat(x => x * 2) (1)) // 256
);
我在 Javascript.
中使用 thunk(一个没有参数的函数)来实现惰性求值具有连续传递样式的函数重复
const repeat = f => x => [x, k => k(repeat(f) (f(x)))];
const take = n => ([x, k]) => n === 0 ? x : k(take(n - 1));
console.log(
take(8) (repeat(x => x * 2) (1)) // 256
);
CPS 一开始有点吓人。然而,它总是遵循相同的模式:最后一个参数是延续(一个函数),它调用它自己的主体:k => k(...)
。请注意,CPS 将应用程序从里到外翻转,即 take(8) (repeat...)
变为 k(take(8)) (...)
,其中 k
是部分应用的 repeat
.
结论
通过将重复 (repeat
) 与终止条件 (take
) 分开,我们获得了灵活性 - 将关注点分离到痛苦的结局 :D
发电机?递归? 为什么这么多讨厌变异? ;-)
如果我们"hide"可以接受,那么就接受一元运算符的使用,我们可以保持简单:
Number.prototype.times = function(f) { let n=0 ; while(this.valueOf() > n) f(n++) }
就像在ruby中一样:
> (3).times(console.log)
0
1
2
我觉得很简单:
[...Array(3).keys()]
或
Array(3).fill()
const times = 4;
new Array(times).fill().map(() => console.log('test'));
此代码段将 console.log
test
4 次。
此解决方案的优势
- 阅读/使用最简单 (imo)
- Return 值可以作为求和,或者直接忽略
- 普通es6版本,还有link到TypeScript version的代码
缺点 - 突变。只是内部我不在乎,也许其他人也不会。
示例和代码
times(5, 3) // 15 (3+3+3+3+3)
times(5, (i) => Math.pow(2,i) ) // 31 (1+2+4+8+16)
times(5, '<br/>') // <br/><br/><br/><br/><br/>
times(3, (i, count) => { // name[0], name[1], name[2]
let n = 'name[' + i + ']'
if (i < count-1)
n += ', '
return n
})
function times(count, callbackOrScalar) {
let type = typeof callbackOrScalar
let sum
if (type === 'number') sum = 0
else if (type === 'string') sum = ''
for (let j = 0; j < count; j++) {
if (type === 'function') {
const callback = callbackOrScalar
const result = callback(j, count)
if (typeof result === 'number' || typeof result === 'string')
sum = sum === undefined ? result : sum + result
}
else if (type === 'number' || type === 'string') {
const scalar = callbackOrScalar
sum = sum === undefined ? scalar : sum + scalar
}
}
return sum
}
TypeScipt 版本
https://codepen.io/whitneyland/pen/aVjaaE?editors=0011
我来晚了,但由于这个问题经常出现在搜索结果中,我只想添加一个我认为在可读性方面最好的解决方案,而不是很长(这是理想的对于任何代码库 IMO)。它会发生变化,但我会为 KISS 原则做出权衡。
let times = 5
while( times-- )
console.log(times)
// logs 4, 3, 2, 1, 0
这是另一个不错的选择:
Array.from({ length: 3}).map(...);
最好,正如@Dave Morse 在评论中指出的那样,您也可以通过使用 Array.from
函数的第二个参数来摆脱 map
调用,如下所示:
Array.from({ length: 3 }, () => (...))
我用辅助函数包装了@Tieme 的回答。
在打字稿中:
export const mapN = <T = any[]>(count: number, fn: (...args: any[]) => T): T[] => [...Array(count)].map((_, i) => fn())
现在您可以运行:
const arr: string[] = mapN(3, () => 'something')
// returns ['something', 'something', 'something']
我做了这个:
function repeat(func, times) {
for (var i=0; i<times; i++) {
func(i);
}
}
用法:
repeat(function(i) {
console.log("Hello, World! - "+i);
}, 5)
/*
Returns:
Hello, World! - 0
Hello, World! - 1
Hello, World! - 2
Hello, World! - 3
Hello, World! - 4
*/
i
变量 returns 已循环的次数 - 如果您需要预加载 x 数量的图像,则很有用。
我只是要把它放在这里。如果您正在寻找不使用数组的紧凑函数并且您对 mutability/immutability 没有问题:
var g =x=>{/*your code goes here*/x-1>0?g(x-1):null};
我能想到的在范围
内创建list/array的最简单方法Array.from(Array(max-min+1), (_, index) => index+min)
对我来说,这是对许多级别的开发人员来说最容易理解的答案
const times = (n, callback) => {
while (n) {
callback();
n--;
}
}
times(10, ()=> console.log('hello'))