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


157

xJavaScriptで時間をループさせる一般的な方法は次のとおりです。

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


3
可変ループ制御変数に問題があるのはなぜですか?ただの原則?
doldt 2015年

1
@doldt-私はJavaScriptを教えようとしていますが、可変変数の概念を後でまで遅らせる実験をしています

5
ここでは本当に話題から外れていますが、ES6ジェネレーター(またはその他の新しい高レベルの概念)に移行することは、可変変数について学ぶ前に良い考えでしょうか?:)
doldt

5
@doldt-多分、私は実験しています。JavaScriptへの関数型言語アプローチの採用。
に。

letを使用して、ループ内でその変数を宣言します。そのスコープはループで終わります。
ncmathsadist

回答:


156

OK!

以下のコードは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 + 1f毎回渡すイテレータとして効果的に機能します。

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

  • 醜い単一の分岐ifステートメントはもうありません
  • 単一式のボディは、うまく分離された懸念を示します
  • もはや役に立たない、暗黙的に返される undefined

JavaScriptコンマ演算子、

最後の例がどのように機能しているかがわからない場合は、JavaScriptの最も古いバトル軸の1つに対する認識に依存します。コンマ演算子 -要するに、それは左から右に式を評価し、返す最後に評価式の値を

(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ため、スタックセーフにするためにの実装を再検討する必要があります。

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

ここではwhileloop関数を抽象化します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


24
複雑すぎるようです(特にと混同していますg => g(g)(x))。私のソリューションのように、1次の関数よりも高次の関数の利点はありますか?
Pavlo、2015年

1
@naomik:リンクを投稿していただきありがとうございます。とても有難い。
ピネダ

1
@AlfonsoPérez発言ありがとうございます。どこかでちょっとしたヒントを
教えてもらえ

1
@naomikさらばTCO!私は荒廃しています。

10
この回答は、かなりの努力が必要だったので、受け入れられ、評価されているようですが、良い回答だとは思いません。質問に対する正解は「いいえ」です。あなたがしたように回避策をリストすることは役に立ちますが、その直後にあなたはより良い方法があると述べました。なぜあなたはその答えをそのままにして、一番悪いものを一番上に置かないのですか?なぜカンマ演算子を説明しているのですか?なぜClojureを呼び出すのですか?なぜ、一般的に、2文字の解答を持つ質問に対して非常に多くの接線があるのですか?単純な質問は、ユーザーがいくつかのきちんとしたプログラミングの事実についてプレゼンテーションを行うための単なるプラットフォームではありません。
Timofey 'Sasha' Kondrashov

266

ES2015 Spreadオペレーターの使用:

[...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

26
より良い:Array.from(Array(10), (_, i) => i*10)
Bergi

6
これが最良の答えです。ES6!すごい!
GergelyFehérvári2017年

3
:あなたはイテレータ(i)を必要としない場合は、この作るために、キーと値の両方を除外することができます[...Array(10)].forEach(() => console.log('looping 10 times');
スターリングボーン

9
それで、それを捨てるためだけにN要素の配列全体を割り当てますか?
Kugel

2
誰かがクーゲルによる以前のコメントに対処しましたか?私は同じことを
アルマン

37
for (let i of Array(100).keys()) {
    console.log(i)
}

これは機能するので、すばらしいことです。しかし、追加の作業が必要であり、これがArrayキーの使用目的ではないという意味で、少し醜いです。
に。

@at。確かに。しかし[0..x]、JSのhaskellの同義語が私の回答よりも簡潔であるかどうかはわかりません。
zerkms

これ以上簡潔なものはないというのは正しいかもしれません。
に。

OK、この作品は間の違いを与えられた理由を私は理解Array.prototype.keysしてObject.prototype.keys、それは確かに一見混乱しています。
マークリード

1
ES2015でTCOを伴う@cchamberlain(ただし、どこにも実装されていませんか?)はそれほど心配する必要はないかもしれませんが、確かに:-)
zerkms

29

私は最良の解決策は使用することだと思います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*)。


1
私はこのオプションが好きです-素晴らしくてシンプルです!
DanV 2016年

2
これは単純で要点があり、上記の多くの回答のように配列を割り当てません
Kugel

@Kugel 2番目はスタックに割り当てられる可能性があります
Bergi

@Bergi
Kugel



11

回答: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に便利なRange関数があり、アンダースコアのrange関数に似ているかもしれません。

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())はわずかに高速でした。


8
Array(100).fill().map((_,i)=> console.log(i) );

このバージョンは、不変性に対するOPの要件を満たしています。また 、ユースケースreducemap依存する代わりに使用を検討してください。

これは、プロトタイプの小さな変更を気にしない場合のオプションでもあります。

Number.prototype.times = function(f) {
   return Array(this.valueOf()).fill().map((_,i)=>f(i));
};

今、これを行うことができます

((3).times(i=>console.log(i)));

.fill提案のためにアークセルドンへの+1 。


否決fillメソッドは、 IEやOperaやPhantomJSではサポートされていません
morhook

8

ここに別の良い選択肢があります:

Array.from({ length: 3}).map(...);

@Dave Morseがコメントで指摘したようmapに、Array.from関数の2番目のパラメーターを次のように使用して、呼び出しを取り除くこともできます。

Array.from({ length: 3 }, () => (...))


2
これは受け入れられる答えになるはずです!小さな提案の1つ-Array.fromを使用して、無料で必要なマップのような機能を既に取得しています。Array.from({ length: label.length }, (_, i) => (...)) これにより、マップの呼び出しを開始するためだけに空の一時配列を作成する手間が省け ます。
Dave Morse

7

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

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

実際のところ、有用な答えというよりは、概念を実証するための興味深いものです。


Coudn't Array.apply(null, {length: 10})だけでもArray(10)
パブロ2015年

1
@パブロ、実際には違います。Array(10)は、長さが10の配列を作成しますが、その中で定義されているキーがないため、この場合、forEach構成は使用できません。しかし、実際にはforEachを使用しない場合は簡略化できます。zerkmsの回答を参照してください(ただし、ES6を使用しています!)。
doldt 2015年

創造的な@doldtですが、私は教えやすくてシンプルなものを探しています。
に。

5

私はパーティーに遅れますが、この質問は検索結果に頻繁に表示されるため、長くない一方で読みやすさの点で最良であると考える解決策を追加したいと思います(これはコードベースIMOに理想的です)。 。それは変化しますが、私はKISSの原則とのトレードオフを行います。

let times = 5
while( times-- )
    console.log(times)
// logs 4, 3, 2, 1, 0

3
私がより高次のラムダフェティッシュパーティーとしか表現できないことについて、理性の声であることをありがとう。私も無害なGoogleへの最初の進路をたどってこのQ&Aに行き着き、ここでの回答のほとんどによって正気を冒とくされました。あなたのものは、私が簡単な問題の簡単な解決策と考えるリストの最初のものです。
Martin Devillers

これの唯一の問題はtimes、ループ内で変数を使用する場合、少し直観に反することです。おそらくcountdown、より優れたネーミングになるでしょう。それ以外の場合は、ページ上で最も明確で明確な回答です。
トニーブラスナス

3

Afaik、Rubyのtimes方法に似たES6のメカニズムはありません。しかし、再帰を使用することで変異を避けることができます:

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


私はこのアプローチが好きで、再帰が大好きです。しかし、新しいJavaScriptユーザーのループを表示するためのもっと簡単なものが好きです。
に。


2

機能パラダイムで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)) (...)krepeat

結論

繰り返し(repeat)を終了条件()から分離することにより、take柔軟性を獲得します。


1

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

  • 最も読みやすい/使用する(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

機能面に対処する:

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

5
「機能的な側面に対処する」そして、可変ループで命令ループを使用しますi。では、times昔ながらのものを使用する理由は何forですか?
zerkms、

のような再利用var twice = times(2);
Nina Scholz

では、なぜfor2回だけ使用しないのですか?
zerkms

私は使用することを恐れていません。質問は、variabeleを使用しないことでした。しかし、結果は常にある種のキャッシング、つまり変数です。
Nina Scholz

1
「ヴァリアベレを使わないものでした」---そして、あなたはまだそれを使います- i++。関数で受け入れられないものをラップすることでどのように改善されるかは明らかではありません。
zerkms

0

発電機?再帰?なぜそんなにmutatinを嫌うのですか?;-)

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

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

ルビーのように:

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

2
いいね:「なぜmutatinにそんなに嫌いなの?」
サレフ2017年

1
シンプルにするために賛成、モンキーパッチでルビースタイルに移行しすぎることを否定します。それらの悪い悪いサルにノーと言ってください。
mrm

1
@mrmはこの「モンキーパッチ」です。これは単なる拡張機能の例ではありませんか?
受け入れて

いいえ。Number(またはString、Array、または作成していないその他のクラス)に関数を追加することは、定義上、ポリフィルまたはサルパッチであり、ポリフィルでさえ推奨されません。「サルパッチ」、「ポリフィル」、推奨される代替案「ポニーフィル」の定義をお読みください。それがあなたの望みです。
mrm '31年

Numberを拡張するには、次のようにします。class SuperNumber extends Number {times(fn){for(let i = 0; i <this; i ++){fn(i); }}}
Alexander

0

@Tiemeの回答をヘルパー関数でラップしました。

TypeScriptの場合:

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']

0

私はこれを作りました:

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あなたは、画像のx量をプリロードする必要がある場合に便利-変数は、それがループした回数の量を返します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.