[javascript] ES6 (ECMAScript 6)에서 변경 가능한 변수없이 x 번 반복하는 메커니즘이 있습니까?

xJavaScript에서 시간 을 반복하는 일반적인 방법 은 다음과 같습니다.

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)))

구현 timesrepeat

지금 이것은 쉬운 일입니다. 거의 모든 작업이 이미 완료되었습니다.

// 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 스택 안전을 하기 위해 합니다.

아래의 코드는 않습니다 가변 변수를 사용하지 않는 nx모든 돌연변이가에 국한되어 있지만, 참고 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*).


답변

const times = 4;
new Array(times).fill().map(() => console.log('test'));

이 스 니펫은 console.log test4 번입니다.


답변

나는 그것이 매우 간단하다고 생각합니다.

[...Array(3).keys()]

또는

Array(3).fill()


답변

답변 : 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 개의 숫자로 배열을 만듭니다.

밑줄

_.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 ())이 약간 더 빨랐습니다.