web-dev-qa-db-ja.com

ES6(ECMAScript 6)で可変変数なしでx回ループするメカニズムはありますか?

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でより良いメカニズムがあることを望んでいます。

114
at.

OK!

以下のコードはES6構文を使用して記述されていますが、ES5またはそれ以下でも簡単に記述できます。 ES6はnot「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ステートメントはstatementsい—他のブランチで何が起こるか?
  • 関数本体内の複数のステートメント/式—プロシージャの問題は混在していますか?
  • 暗黙的に返された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に渡す反復子として効果的に機能します。

問題の箇条書きも修正しました

  • Ugい単一ブランチifステートメントはもうありません
  • 単一式の本文は、適切に分離された懸念事項を示します
  • 無駄に、暗黙的に返されるundefinedはありません

JavaScriptコンマ演算子、

最後の例がどのように機能するかを理解できない場合は、JavaScriptの最も古い戦闘軸の1つを認識しているかどうかに依存します。 カンマ演算子 –要するに、左から右に式を評価し、returns最後に評価された式の値

(expr1 :: a, expr2 :: b, expr3 :: c) :: c

上記の例では、私は使用しています

(i => (f(i), i + 1))

これは簡潔な書き方です

(i => { f(i); return i + 1 })

テールコールの最適化

再帰的な実装と同じくらいセクシーですが、この時点で JavaScript VM が適切なテールコールの除去をサポートしていると考えることができるので、それらを推奨することは無責任です。 1年以上にわたって「壊れた;再実装する」状態にあります。

repeat (1e6) (someFunc) (x)
// => RangeError: Maximum call stack size exceeded

そのため、repeatの実装を再検討して、スタックセーフにする必要があります。

以下のコードdoesは可変変数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

「これは機能的ではありません!」と言っている人がたくさんいるでしょう。 –ただリラックスしてください。 pure expression;を使用して、定数スペースループ用のClojureスタイルのloop/recurインターフェイスを実装できます。そのwhileのものはありません。

ここで、while関数を使用してloopを抽象化します。ループを実行し続けるために特別なrecur型を探します。 nonrecur型が検出されると、ループが終了し、計算の結果が返されます

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
126
user633183

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
172
Tieme
for (let i of Array(100).keys()) {
    console.log(i)
}
30
zerkms

最良の解決策は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*ではテールコールの除去が利用できないため)。

22
Bergi

回答: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'

サイドノート-範囲関数の定義

基本的に非常に類似したコード構造を使用する同様の/関連する質問は、アンダースコアの範囲関数に似た便利なRange関数が(コア)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関数を使用してそれぞれ100万回実行され、前者のアプローチ(Array(n).fill())がわずかに高速であることが証明されました。

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

このスニペットはconsole.logtestを4回実行します。

8
Hossam Mourad

とても簡単だと思います。

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

または

Array(3).fill()
7

私が教えるもの(またはコードで使用するもの)ではありませんが、変数を変更せず、ES6を必要としないコードゴルフに値するソリューションを以下に示します。

Array.apply(null, {length: 10}).forEach(function(_, i){
    doStuff(i);
})

実用的な答えというよりも、概念実証として興味深いものです。

6
doldt
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)));

.fill提案については、arcseldonに+1。

6
Tom

ライブラリを使用する場合は、 lodash _.times または アンダースコア_.times もあります。

_.times(x, i => {
   return doStuff(i)
})

これは結果の配列を返すため、実際にはこのRubyに似ていることに注意してください。

x.times.map { |i|
  doStuff(i)
}
3
ronen

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

デモ: http://jsbin.com/koyecovano/1/edit?js,console

3
Pavlo

関数型パラダイムでは、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で遅延評価を実現しています。

継続渡しスタイルでの関数の繰り返し

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

2
user6445533

発電機?再帰? なぜムタチンを嫌うのか? ;-)

それを「隠す」限り許容できる場合は、単項演算子の使用を受け入れるだけで、物事をシンプルに保つ

Number.prototype.times = function(f) { let n=0 ; while(this.valueOf() > n) f(n++) }

Rubyのように:

> (3).times(console.log)
0
1
2
0
conny

このソリューションの利点

  • 読み取り/使用が最も簡単(imo)
  • 戻り値は合計として使用するか、単に無視することができます
  • プレーンes6バージョン、 TypeScriptバージョン へのリンク

欠点-突然変異。社内のみであるため、私は気にしません。他の人も気にしないかもしれません。

例とコード

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

0
whitneyland

機能面に対処する:

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);
});
0
Nina Scholz