Programing

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

lottogame 2020. 7. 5. 21:08
반응형

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


답변 : 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

참고URL : https://stackoverflow.com/questions/30452263/is-there-a-mechanism-to-loop-x-times-in-es6-ecmascript-6-without-mutable-varia

반응형