JavaScriptイテレータを配列に変換する


171

JavaScript EC6の新しいMapオブジェクトを使用しようとしています。これは、FirefoxとChromeの最新バージョンで既にサポートされているためです。

しかし、[key, value]ペアでうまく機能する古典的なマップ、フィルターなどのメソッドが不足しているため、「関数型」プログラミングでは非常に制限されています。forEachがありますが、コールバック結果は返されません。

私は、その変換ができればmap.entries()、私はその後、標準を使用することができ、単純な配列にMapIteratorから.map.filter追加なしハックと。

JavaScriptイテレータを配列に変換する「良い」方法はありますか?Pythonではそれを行うのと同じくらい簡単list(iterator)です...しかしArray(m.entries())、最初の要素としてイテレータを持つ配列を返します!!!

編集

マップが機能する場所で機能する回答を探していることを指定するのを忘れていました。これは、少なくともChromeとFirefoxを意味します(Array.fromはChromeでは機能しません)。

PS。

私は素晴らしいwu.jsがあることを知っていますが、そのtraceurへの依存が私を先延ばしにしています...


回答:


247

任意のイテラブルを配列インスタンスに変換する新しいArray.from関数を探しています:

var arr = Array.from(map.entries());

それは、今されてエッジ、FF、Chromeとノード4+でサポートされています

もちろん、配列や同様のメソッドをイテレータインターフェースで直接定義してmapfilter配列の割り当てを回避できるようにすることもできます。高次関数の代わりにジェネレータ関数を使用することもできます。

function* map(iterable) {
    var i = 0;
    for (var item of iterable)
        yield yourTransformation(item, i++);
}
function* filter(iterable) {
    var i = 0;
    for (var item of iterable)
        if (yourPredicate(item, i++))
             yield item;
}

コールバックが(value, key)ペアではなく(value, index)ペアを受け取ることを期待します。
Aadit M Shah 2015

3
@AaditMShah:イテレータのキーは何ですか?もちろん、マップを反復する場合、次のように定義できますyourTransformation = function([key, value], index) { … }
Bergi

イテレータにはキーはありませんが、Mapキーと値のペアはあります。したがって、私の控えめな意見では、イテレータの一般的な関数mapfilter関数を定義しても意味がありません。代わりに、各反復可能オブジェクトには独自のmapおよびfilter関数が必要です。mapfilterは構造保存操作(おそらくそうではないfilterが、map確かにそうである)であり、したがって、mapおよびfilter関数は、マッピングまたはフィルタリングする反復可能オブジェクトの構造を知っている必要があるため、これは理にかなっています。考えてみてください。Haskellでは、のさまざまなインスタンスを定義していますFunctor。=)
Aadit M Shah 2015

1
@Stefano:簡単にシムできます…
Bergi

1
@Incognitoああ、そうだね。確かにそれは本当だ。でもそれは質問が求めているものに過ぎず、私の答えの問題ではない。
ベルギ

45

[...map.entries()] または Array.from(map.entries())

とても簡単です。

とにかく-イテレータには、reduce、filter、および類似のメソッドがありません。Mapを配列に変換したり、元に戻したりするよりもパフォーマンスが高いため、自分で書く必要があります。ただし、ジャンプを実行しないでください。マップ->配列->マップ->配列->マップ->配列です。パフォーマンスが低下するためです。


1
より実質的なものがない限り、これは本当にコメントになるはずです。さらに、Array.from@ Bergiによってすでにカバーされています。
Aadit M Shah 2015

2
そして、私が最初の質問で書いたように、[iterator]Chromeでは単一のiterator要素を含む配列を作成し、Chromeで[...map.entries()]受け入れられる構文ではないので機能しません
Stefano

2
@Stefano スプレッド演算子がChromeで構文を受け入れるようになりました
Klesun '20

15

変換する必要はありませんMapにはArray。あなたは単にオブジェクトを作成mapしてfilter機能させることができMapます:

function map(functor, object, self) {
    var result = new Map;

    object.forEach(function (value, key, object) {
        result.set(key, functor.call(this, value, key, object));
    }, self);

    return result;
}

function filter(predicate, object, self) {
    var result = new Map;

    object.forEach(function (value, key, object) {
        if (predicate.call(this, value, key, object)) result.set(key, value);
    }, self);

    return result;
}

たとえば!、キーがプリミティブであるマップの各エントリの値にバング(文字)を追加できます。

var object = new Map;

object.set("", "empty string");
object.set(0,  "number zero");
object.set(object, "itself");

var result = map(appendBang, filter(primitive, object));

alert(result.get(""));     // empty string!
alert(result.get(0));      // number zero!
alert(result.get(object)); // undefined

function primitive(value, key) {
    return isPrimitive(key);
}

function appendBang(value) {
    return value + "!";
}

function isPrimitive(value) {
    var type = typeof value;
    return value === null ||
        type !== "object" &&
        type !== "function";
}
<script>
function map(functor, object, self) {
    var result = new Map;

    object.forEach(function (value, key, object) {
        result.set(key, functor.call(this, value, key, object));
    }, self || null);

    return result;
}

function filter(predicate, object, self) {
    var result = new Map;

    object.forEach(function (value, key, object) {
        if (predicate.call(this, value, key, object)) result.set(key, value);
    }, self || null);

    return result;
}
</script>

またmapfilterメソッドを追加しMap.prototypeて読みやすくすることもできます。一般的にはまだ本来のプロトタイプを変更することをお勧めされていないが、私は、例外がある場合に行うことができることを信じているmapfilterのためにMap.prototype

var object = new Map;

object.set("", "empty string");
object.set(0,  "number zero");
object.set(object, "itself");

var result = object.filter(primitive).map(appendBang);

alert(result.get(""));     // empty string!
alert(result.get(0));      // number zero!
alert(result.get(object)); // undefined

function primitive(value, key) {
    return isPrimitive(key);
}

function appendBang(value) {
    return value + "!";
}

function isPrimitive(value) {
    var type = typeof value;
    return value === null ||
        type !== "object" &&
        type !== "function";
}
<script>
Map.prototype.map = function (functor, self) {
    var result = new Map;

    this.forEach(function (value, key, object) {
        result.set(key, functor.call(this, value, key, object));
    }, self || null);

    return result;
};

Map.prototype.filter = function (predicate, self) {
    var result = new Map;

    this.forEach(function (value, key, object) {
        if (predicate.call(this, value, key, object)) result.set(key, value);
    }, self || null);

    return result;
};
</script>


編集: Bergiの回答では、彼はすべての反復可能なオブジェクトのジェネリック関数mapfilterジェネレーター関数を作成しました。それらを使用する利点は、それらがジェネレーター関数であるため、中間の反復可能なオブジェクトを割り当てないことです。

たとえば、上で定義したmy mapおよびfilterfunctions は、新しいMapオブジェクトを作成します。したがって、呼び出しobject.filter(primitive).map(appendBang)により2つの新しいMapオブジェクトが作成されます。

var intermediate = object.filter(primitive);
var result = intermediate.map(appendBang);

中間の反復可能オブジェクトの作成にはコストがかかります。Bergiのジェネレーター関数はこの問題を解決します。これらは中間オブジェクトを割り当てませんが、1つのイテレータがその値を遅延して次のオブジェクトにフィードできるようにします。この種の最適化は、関数型プログラミング言語ではフュージョンまたは森林破壊と呼ばれ、プログラムのパフォーマンスを大幅に向上させることができます。

Bergiのジェネレーター関数で私が持っている唯一の問題は、それらがMapオブジェクトに固有ではないことです。代わりに、すべての反復可能なオブジェクトに対して一般化されます。したがって、コールバック関数を(value, key)ペアで呼び出すのではなく(私がをマッピングするときに予想するようにMap)、(value, index)ペアでコールバック関数を呼び出します。そうでなければ、それは優れたソリューションであり、私が提供したソリューションよりも使用することをお勧めします。

したがって、これらは、Mapオブジェクトのマッピングとフィルタリングに使用する特定のジェネレーター関数です。

function * map(functor, entries, self) {
    var that = self || null;

    for (var entry of entries) {
        var key   = entry[0];
        var value = entry[1];

        yield [key, functor.call(that, value, key, entries)];
    }
}

function * filter(predicate, entries, self) {
    var that = self || null;

    for (var entry of entries) {
        var key    = entry[0];
        var value  = entry[1];

        if (predicate.call(that, value, key, entries)) yield [key, value];
    }
}

function toMap(entries) {
    var result = new Map;

    for (var entry of entries) {
        var key   = entry[0];
        var value = entry[1];

        result.set(key, value);
    }

    return result;
}

function toArray(entries) {
    var array = [];

    for (var entry of entries) {
        array.push(entry[1]);
    }

    return array;
}

次のように使用できます。

var object = new Map;

object.set("", "empty string");
object.set(0,  "number zero");
object.set(object, "itself");

var result = toMap(map(appendBang, filter(primitive, object.entries())));

alert(result.get(""));     // empty string!
alert(result.get(0));      // number zero!
alert(result.get(object)); // undefined

var array  = toArray(map(appendBang, filter(primitive, object.entries())));

alert(JSON.stringify(array, null, 4));

function primitive(value, key) {
    return isPrimitive(key);
}

function appendBang(value) {
    return value + "!";
}

function isPrimitive(value) {
    var type = typeof value;
    return value === null ||
        type !== "object" &&
        type !== "function";
}
<script>
function * map(functor, entries, self) {
    var that = self || null;

    for (var entry of entries) {
        var key   = entry[0];
        var value = entry[1];

        yield [key, functor.call(that, value, key, entries)];
    }
}

function * filter(predicate, entries, self) {
    var that = self || null;

    for (var entry of entries) {
        var key    = entry[0];
        var value  = entry[1];

        if (predicate.call(that, value, key, entries)) yield [key, value];
    }
}

function toMap(entries) {
    var result = new Map;

    for (var entry of entries) {
        var key   = entry[0];
        var value = entry[1];

        result.set(key, value);
    }

    return result;
}

function toArray(entries) {
    var array = [];

    for (var entry of entries) {
        array.push(entry[1]);
    }

    return array;
}
</script>

より流暢なインターフェイスが必要な場合は、次のようにすることができます。

var object = new Map;

object.set("", "empty string");
object.set(0,  "number zero");
object.set(object, "itself");

var result = new MapEntries(object).filter(primitive).map(appendBang).toMap();

alert(result.get(""));     // empty string!
alert(result.get(0));      // number zero!
alert(result.get(object)); // undefined

var array  = new MapEntries(object).filter(primitive).map(appendBang).toArray();

alert(JSON.stringify(array, null, 4));

function primitive(value, key) {
    return isPrimitive(key);
}

function appendBang(value) {
    return value + "!";
}

function isPrimitive(value) {
    var type = typeof value;
    return value === null ||
        type !== "object" &&
        type !== "function";
}
<script>
MapEntries.prototype = {
    constructor: MapEntries,
    map: function (functor, self) {
        return new MapEntries(map(functor, this.entries, self), true);
    },
    filter: function (predicate, self) {
        return new MapEntries(filter(predicate, this.entries, self), true);
    },
    toMap: function () {
        return toMap(this.entries);
    },
    toArray: function () {
        return toArray(this.entries);
    }
};

function MapEntries(map, entries) {
    this.entries = entries ? map : map.entries();
}

function * map(functor, entries, self) {
    var that = self || null;

    for (var entry of entries) {
        var key   = entry[0];
        var value = entry[1];

        yield [key, functor.call(that, value, key, entries)];
    }
}

function * filter(predicate, entries, self) {
    var that = self || null;

    for (var entry of entries) {
        var key    = entry[0];
        var value  = entry[1];

        if (predicate.call(that, value, key, entries)) yield [key, value];
    }
}

function toMap(entries) {
    var result = new Map;

    for (var entry of entries) {
        var key   = entry[0];
        var value = entry[1];

        result.set(key, value);
    }

    return result;
}

function toArray(entries) {
    var array = [];

    for (var entry of entries) {
        array.push(entry[1]);
    }

    return array;
}
</script>

お役に立てば幸いです。


感謝します!私が "Array.from"を知らなかったので、@ Bergiに良い答えのマークを付けました。でもあなたとの非常に興味深い議論!
Stefano

1
@Stefano回答を編集して、ジェネレーターを使用しMapて、特殊な関数mapfilter関数を使用してオブジェクトを正しく変換する方法を示しました。Bergiの答えは、オブジェクトのキーが失われているためにオブジェクトの変換に使用できないすべての反復可能なオブジェクトのジェネリックmapfilter関数の使用を示しています。MapMap
Aadit M Shah 2015

うわー、私はあなたの編集が本当に好きです。私はここで自分の答えをここに書きました:stackoverflow.com/a/28721418/422670(この質問は重複として閉じられたため、そこに追加されましたArray.from)。しかし、私はアプローチが非常に似ているのを見ることができ、あなたはあなたの束に "toArray"関数を追加することができます!
Stefano

1
@Stefano確かに。回答を編集して、toArray関数を追加する方法を示しました。
Aadit M Shah 2015

7

2019年からの小さな更新:

現在、Array.fromは普遍的に利用できるようであり、さらに、2番目の引数mapFnを受け入れます。これにより、中間配列を作成できなくなります。これは基本的に次のようになります。

Array.from(myMap.entries(), entry => {...});

の回答がArray.from既に存在するため、これはその回答に対するコメントまたは編集のリクエストに適しています...しかし、ありがとう!
ステファノ

1

イテラブルの配列のようなメソッドを実装するhttps://www.npmjs.com/package/itiririのようなライブラリを使用できます。

import { query } from 'itiriri';

const map = new Map();
map.set(1, 'Alice');
map.set(2, 'Bob');

const result = query(map)
  .filter([k, v] => v.indexOf('A') >= 0)
  .map([k, v] => `k - ${v.toUpperCase()}`);

for (const r of result) {
  console.log(r); // prints: 1 - ALICE
}

このlibはすばらしく見え、イテラブル@dimadeveatiiにジャンプするためのコンジットがない-書いてくれてありがとう、すぐに試してみる:-)
Angelos Pikoulas

0

配列の配列(キーと値)を取得できます。

[...this.state.selected.entries()]
/**
*(2) [Array(2), Array(2)]
*0: (2) [2, true]
*1: (2) [3, true]
*length: 2
*/

そして、たとえばマップイテレータのキーなど、内部から値を簡単に取得できます。

[...this.state.selected[asd].entries()].map(e=>e[0])
//(2) [2, 3]

0

fluent-iterableを使用して配列に変換することもできます。

const iterable: Iterable<T> = ...;
const arr: T[] = fluent(iterable).toArray();
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.