ES6 (ECMAScript 6)에서 변경 가능한 변수없이 x 번 반복하는 메커니즘이 있습니까?
x
JavaScript에서 시간 을 반복하는 일반적인 방법 은 다음과 같습니다.
for (var i = 0; i < x; i++)
doStuff(i);
그러나 ++
연산자 를 사용 하거나 변경 가능한 변수를 원하지 않습니다 . ES6에는 x
시간을 다른 방식 으로 반복하는 방법이 있습니까? 나는 루비의 메커니즘을 좋아한다.
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에는 더 나은 메커니즘이 있기를 바랍니다.
확인!
아래 코드는 ES6 구문을 사용하여 작성되었지만 ES5 이하로 쉽게 작성할 수 있습니다. ES6은 "x 번 반복하는 메커니즘"을 만들 필요 는 없습니다
콜백에서 반복자가 필요하지 않은 경우 가장 간단한 구현입니다.
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'))
더 많은 것을 배우고 싶지 않다면 여기서 읽지 마십시오 ...
그러나 그에 대해 뭔가를 느끼게해야합니다 ...
- 단일 브랜치
if
문은 추악 합니다. 다른 브랜치에서는 어떻게됩니까? - 함수 본문의 여러 문장 / 표현 — 절차 문제가 혼합되어 있습니까?
- 암시 적으로 반환
undefined
-불완전한 부작용의 표시
"더 나은 방법이 없습니까?"
있습니다. 먼저 초기 구현을 다시 살펴 보겠습니다
// times :: Int -> (void -> void) -> void
const times = x => f => {
if (x > 0) {
f() // 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)))
구현 times
과repeat
지금 이것은 쉬운 일입니다. 거의 모든 작업이 이미 완료되었습니다.
// 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
입력으로 사용하고를 반환 i + 1
하기 때문에 f
매번 전달하는 반복자로 효과적으로 작동합니다 .
총알 문제 목록도 수정했습니다.
- 더 이상 못생긴 단일 지점
if
진술 - 단일 표현 체는 멋지게 분리 된 우려를 나타냅니다
- 더 이상 쓸모없고 암시 적으로 반환되지 않음
undefined
자바 스크립트 쉼표 연산자
마지막 예제가 어떻게 작동하는지 보는 데 어려움을 겪고 있다면 JavaScript의 가장 오래된 전투 축 중 하나에 대한 인식에 달려 있습니다. 쉼표 연산자 - 짧은에, 그것은 왼쪽에서 오른쪽으로 식을 평가하고 반환 마지막 평가 식의 값을
(expr1 :: a, expr2 :: b, expr3 :: c) :: c
위의 예에서 저는
(i => (f(i), i + 1))
간결한 글쓰기 방식입니다
(i => { f(i); return i + 1 })
테일 콜 최적화
재귀 적 구현만큼 섹시하지만이 시점에서 자바 스크립트 VM이 적절한 꼬리 호출 제거를 지원할 수 있다고 생각할 수 없기 때문에 권장하는 것은 무책임 할 것입니다. 바벨은 그것을 번역하는 데 사용되었지만 "파손되었습니다. 재 구현됩니다. "1 년 넘게 상태.
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
이것은 "하지만 기능하지 않습니다!"라고 말하는 많은 사람들을 갖게 될 것입니다. – 알다시피, 휴식을 취하십시오. 순수 표현식을 사용하여 상수 공간 반복을위한 Clojure 스타일 loop
/ recur
인터페이스를 구현할 수 있습니다 . 그중 아무것도 없습니다 .while
여기에서 우리는 추상적 인 while
우리와 멀리 loop
기능 - 그것은 특별한 찾습니다 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
은 Using ES2015 확산 연산자를 :
[...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 연산자를 사용하십시오 .
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
for (let i of Array(100).keys()) {
console.log(i)
}
최선의 해결책은 다음을 사용하는 것입니다 let
.
for (let i=0; i<100; i++) …
그러면 i
각 바디 평가에 대해 새로운 (변경 가능) 변수 가 생성되고 i
다른 곳이 아닌 해당 루프 구문의 증분 식에서 만 변경됩니다.
속임수를 쓰고 제 자신의 발전기를 만들 수있었습니다 적어도
i++
시야가 맞지 않습니다 :)
충분할 것입니다. 순수한 언어로도 모든 연산 (또는 적어도 그 해석기)은 돌연변이를 사용하는 프리미티브로 만들어집니다. 범위가 올바르게 지정되어 있으면 문제가 무엇인지 알 수 없습니다.
당신은 잘해야합니다
function* times(n) {
for (let i = 0; i < x; i++)
yield i;
}
for (const i of times(5))
console.log(i);
그러나
++
연산자 를 사용 하거나 변경 가능한 변수를 원하지 않습니다 .
그런 다음 유일한 선택은 재귀를 사용하는 것입니다. 가변성없이 생성기 함수를 정의 할 수도 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'
참고-범위 기능 정의
근본적으로 매우 유사한 코드 구성을 사용하는 유사 / 관련 질문은 밑줄의 범위 함수와 비슷한 (핵심) JavaScript에 편리한 범위 함수가있을 수 있습니다.
x부터 시작하여 n 개의 숫자로 배열을 만듭니다.
Underscore
_.range(x, x + n)
ES2015
Couple of alternatives:
Array(n).fill().map((_, i) => x + i)
Array.from(Array(n), (_, i) => x + i)
Demo using 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 ]
In a quick test I ran, with each of the above running a million times each using our solution and doStuff function, the former approach (Array(n).fill()) proved slightly faster.
const times = 4;
new Array(times).fill().map(() => console.log('test'));
This snippet will console.log
test
4 times.
I think it is pretty simple:
[...Array(3).keys()]
or
Array(3).fill()
Not something I would teach (or ever use in my code), but here's a codegolf-worthy solution without mutating a variable, no need for ES6:
Array.apply(null, {length: 10}).forEach(function(_, i){
doStuff(i);
})
More of an interesting proof-of-concept thing than a useful answer, really.
Array(100).fill().map((_,i)=> console.log(i) );
This version satisifies the OP's requirement for immutability. Also consider using reduce
instead of map
depending on your use case.
This is also an option if you don't mind a little mutation in your prototype.
Number.prototype.times = function(f) {
return Array(this.valueOf()).fill().map((_,i)=>f(i));
};
Now we can do this
((3).times(i=>console.log(i)));
+1 to arcseldon for the .fill
suggestion.
Afaik, there is no mechanism in ES6 similar to Ruby's times
method. But you can avoid mutation by using recursion:
let times = (i, cb, l = i) => {
if (i === 0) return;
cb(l - i);
times(i - 1, cb, l);
}
times(5, i => doStuff(i));
Demo: http://jsbin.com/koyecovano/1/edit?js,console
If you're willing to use a library, there's also lodash _.times
or underscore _.times
:
_.times(x, i => {
return doStuff(i)
})
Note this returns an array of the results, so it's really more like this ruby:
x.times.map { |i|
doStuff(i)
}
In the functional paradigm repeat
is usually an infinite recursive function. To use it we need either lazy evaluation or continuation passing style.
Lazy evaluated function repetition
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
);
I use a thunk (a function without arguments) to achieve lazy evaluation in Javascript.
Function repetition with continuation passing style
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 is a little scary at first. However, it always follows the same pattern: The last argument is the continuation (a function), which invokes its own body: k => k(...)
. Please note that CPS turns the application inside out, i.e. take(8) (repeat...)
becomes k(take(8)) (...)
where k
is the partially applied repeat
.
Conclusion
By separating the repetition (repeat
) from the termination condition (take
) we gain flexibility - separation of concerns up to its bitter end :D
Advantages of this solution
- Simplest to read / use (imo)
- Return value can be used as a sum, or just ignored
- Plain es6 version, also link to TypeScript version of the code
Disadvantages - Mutation. Being internal only I don't care, maybe some others will not either.
Examples and Code
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 version
https://codepen.io/whitneyland/pen/aVjaaE?editors=0011
addressing the functional aspect:
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);
});
Generators? Recursion? Why so much hatin' on mutatin'? ;-)
If it is acceptable as long as we "hide" it, then just accept the use of a unary operator and we can keep things simple:
Number.prototype.times = function(f) { let n=0 ; while(this.valueOf() > n) f(n++) }
Just like in ruby:
> (3).times(console.log)
0
1
2
'Programing' 카테고리의 다른 글
오류 : ': app : compileDebugKotlin'작업에 대한 실행이 실패했습니다. (0) | 2020.07.05 |
---|---|
jQuery로 현재 시간을 얻는 방법 (0) | 2020.07.05 |
Twig에서 클래스 상수에 액세스하는 방법은 무엇입니까? (0) | 2020.07.05 |
postgresql에서 월 및 연도별로 그룹 쿼리 결과 (0) | 2020.07.05 |
IllegalMonitorStateException없이 Java에서 대기 및 알림을 사용하는 방법은 무엇입니까? (0) | 2020.07.04 |