การแปลงตัววนซ้ำ Javascript เป็นอาร์เรย์


171

ฉันพยายามใช้แผนที่วัตถุใหม่จาก Javascript EC6 เนื่องจากได้รับการสนับสนุนใน Firefox และ Chrome เวอร์ชันล่าสุดแล้ว

แต่ฉันพบว่ามันมีข้อ จำกัด มากในการเขียนโปรแกรม "ฟังก์ชั่น" เพราะมันขาดแผนที่แบบคลาสสิกตัวกรองและอื่น ๆ ที่ใช้งานได้ดีกับ[key, value]คู่ มันมี forEach แต่ว่าจะไม่ส่งกลับผลลัพธ์การโทรกลับ

ถ้าฉันจะเปลี่ยนมันmap.entries()จาก MapIterator ลงในอาร์เรย์ที่เรียบง่ายจากนั้นผมก็สามารถใช้มาตรฐาน.map, .filterไม่มี hacks เพิ่มเติม

มีวิธี "ดี" ในการแปลง Javascript Iterator เป็น Array หรือไม่? ในไพ ธ อนมันง่ายเหมือนทำlist(iterator)... แต่Array(m.entries())คืนอาเรย์ด้วย Iterator เป็นองค์ประกอบแรก !!!

แก้ไข

ฉันลืมระบุว่าฉันกำลังมองหาคำตอบที่ทำงานได้ทุกที่บนแผนที่ซึ่งหมายความว่าอย่างน้อย Chrome และ Firefox (Array.from ไม่ทำงานใน Chrome)

PS

ฉันรู้ว่ามีwu.js ที่ยอดเยี่ยมแต่การพึ่งพา traceur ทำให้ฉันออกไป ...


ดูเพิ่มเติมstackoverflow.com/q/27612713/1460043
1460043

คำตอบ:


247

คุณกำลังมองหาArray.fromฟังก์ชั่นใหม่ที่จะแปลง iterables ให้เป็นอาเรย์

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

คือตอนนี้ได้รับการสนับสนุนในขอบ FF, Chrome และโหนด 4+

แน่นอนว่ามันอาจจะมีมูลค่าในการกำหนดmap, filterและวิธีการที่คล้ายกันโดยตรงบนอินเตอร์เฟซ iterator เพื่อให้คุณสามารถหลีกเลี่ยงการจัดสรรอาร์เรย์ คุณอาจต้องการใช้ฟังก์ชันตัวสร้างแทนฟังก์ชันลำดับสูงกว่า:

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

3
@AaditMShah: กุญแจสำคัญของตัววนซ้ำคืออะไร แน่นอนถ้าคุณทำซ้ำแผนที่คุณสามารถกำหนดyourTransformation = function([key, value], index) { … }
Bergi

ตัววนซ้ำไม่มีคีย์ แต่ a Mapมีคู่ของค่าคีย์ ดังนั้นในความเห็นที่ต่ำต้อยของฉันมันไม่สมเหตุสมผลเลยที่จะนิยามทั่วไปmapและfilterฟังก์ชั่นสำหรับตัววนซ้ำ แต่ละวัตถุที่ทำซ้ำได้ควรมีของตัวเองmapและfilterฟังก์ชั่น สิ่งนี้สมเหตุสมผลmapและfilterมีโครงสร้างที่รักษาการปฏิบัติงาน (อาจไม่ใช่filterแต่mapแน่นอน) และด้วยเหตุนี้mapและfilterฟังก์ชั่นควรรู้โครงสร้างของวัตถุ iterable ที่พวกเขากำลังทำแผนที่หรือการกรอง คิดเกี่ยวกับมันใน Haskell Functorเรากำหนดกรณีที่แตกต่างกันของ =)
Aadit M Shah

1
@Stefano: คุณสามารถShim มันได้อย่างง่ายดาย ...
Bergi

1
@Incognito อ่าโอเคแน่นอนว่ามันเป็นความจริง แต่มันก็เป็นคำถามที่ถามไม่ใช่ปัญหากับคำตอบของฉัน
Bergi

45

[...map.entries()] หรือ Array.from(map.entries())

มันง่ายมาก

อย่างไรก็ตามตัววนซ้ำขาดการลดตัวกรองและวิธีการที่คล้ายกัน คุณต้องเขียนมันด้วยตัวเองเพราะมันสมบูรณ์แบบกว่าการแปลง Map เป็น array และ back แต่อย่ากระโดด Map -> Array -> Map -> Array -> Map -> Array เพราะมันจะฆ่าประสิทธิภาพ


1
เว้นแต่คุณจะมีสิ่งที่สำคัญกว่านี้ควรเป็นความคิดเห็น นอกจากนี้Array.fromยังได้รับการคุ้มครองโดย @Bergi
Aadit M Shah

2
และอย่างที่ฉันเขียนไว้ในคำถามเดิมของฉัน[iterator]ไม่ทำงานเพราะใน Chrome มันจะสร้างอาร์เรย์ที่มีiteratorองค์ประกอบเดียวในนั้นและ[...map.entries()]ไม่ใช่ไวยากรณ์ที่ยอมรับใน Chrome
Stefano

2
ขณะนี้ผู้ดำเนินการ @Stefano spreadsheet ยอมรับไวยากรณ์ใน Chrome แล้ว
Klesun

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>

คุณสามารถเพิ่มmapและfilterวิธีการMap.prototypeเพื่อให้อ่านดีขึ้น แม้ว่าโดยทั่วไปจะไม่ได้รับการแนะนำให้ปรับเปลี่ยนต้นแบบต้นแบบ แต่ฉันเชื่อว่าอาจมีข้อยกเว้นในกรณีของmapและfilterสำหรับ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 เขาได้สร้างฟังก์ชั่นทั่วไปmapและตัวfilterสร้างสำหรับวัตถุที่ทำซ้ำได้ทั้งหมด ข้อได้เปรียบของการใช้พวกมันคือเพราะมันเป็นฟังก์ชั่นเครื่องกำเนิดไฟฟ้าพวกมันไม่ได้จัดสรรวัตถุที่ทำซ้ำได้ระดับกลาง

ตัวอย่างเช่นฉันmapและfilterฟังก์ชั่นที่กำหนดไว้ข้างต้นสร้างMapวัตถุใหม่ ดังนั้นการเรียกobject.filter(primitive).map(appendBang)จะสร้างMapวัตถุใหม่สองรายการ:

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

การสร้างออบเจกต์แบบ iterable ระดับกลางนั้นมีราคาแพง ฟังก์ชั่นเครื่องกำเนิดของ Bergi แก้ปัญหานี้ได้ พวกเขาไม่ได้จัดสรรวัตถุกลาง แต่อนุญาตให้ตัววนซ้ำหนึ่งตัวป้อนค่าของมันไปอย่างเกียจคร้านต่อไป การเพิ่มประสิทธิภาพแบบนี้เรียกว่าการรวมหรือการตัดไม้ทำลายป่าในภาษาการเขียนโปรแกรมการทำงานและสามารถปรับปรุงประสิทธิภาพของโปรแกรมอย่างมีนัยสำคัญ

ปัญหาเดียวที่ฉันมีกับฟังก์ชั่นเครื่องกำเนิดของ 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>

หวังว่าจะช่วย


มันไม่ขอบคุณ! ให้เครื่องหมายคำตอบที่ดีแก่ @Bergi แต่เพราะฉันไม่รู้จัก "Array.from" และนั่นเป็นคำตอบที่ตรงประเด็นที่สุด การสนทนาที่น่าสนใจระหว่างคุณเช่นกัน!
Stefano

1
@Stefano ฉันแก้ไขคำตอบของฉันเพื่อแสดงว่าเครื่องกำเนิดไฟฟ้าสามารถใช้ในการแปลงMapวัตถุอย่างถูกต้องโดยใช้ผู้เชี่ยวชาญmapและfilterฟังก์ชั่น คำตอบของ Bergi แสดงให้เห็นถึงการใช้งานทั่วไปmapและfilterฟังก์ชั่นสำหรับวัตถุที่สามารถทำซ้ำได้ทั้งหมดซึ่งไม่สามารถใช้สำหรับการเปลี่ยนMapวัตถุได้เพราะกุญแจของMapวัตถุนั้นหายไป
Aadit M Shah

ว้าวฉันชอบการแก้ไขของคุณจริงๆ ฉันลงเอยด้วยการเขียนคำตอบของฉันเองที่นี่: stackoverflow.com/a/28721418/422670 (เพิ่มไปที่นั่นเนื่องจากคำถามนี้ถูกปิดเพราะซ้ำซ้อน) เพราะArray.fromไม่ทำงานใน Chrome (ในขณะที่ Map และ iterators ทำ!) แต่ฉันเห็นว่าวิธีการนี้คล้ายกันมากและคุณสามารถเพิ่มฟังก์ชั่น "toArray" เข้าในเครือของคุณได้!
Stefano

1
@tefano แน่นอน ฉันแก้ไขคำตอบเพื่อแสดงวิธีเพิ่มtoArrayฟังก์ชั่น
Aadit M Shah

7

อัพเดตเล็กน้อยจากปี 2019:

ตอนนี้ Array.from ดูเหมือนว่าจะมีอยู่ในระดับสากลและยิ่งไปกว่านั้นมันก็รับอาร์กิวเมนต์mapFnตัวที่สองซึ่งป้องกันไม่ให้มันสร้างอาร์เรย์กลางขึ้นมา โดยทั่วไปจะมีลักษณะเช่นนี้:

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

เนื่องจากมีคำตอบArray.fromอยู่แล้วนี่เหมาะกว่าที่จะแสดงความคิดเห็นหรือขอแก้ไขคำตอบนั้น ... แต่ขอบคุณ!
Stefano

1

คุณสามารถใช้ห้องสมุดเช่นhttps://www.npmjs.com/package/itiririซึ่งใช้วิธีการแบบอาเรย์เหมือนสำหรับ iterables:

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 นี้ดูน่าอัศจรรย์ & ท่อที่หายไปเพื่อข้ามไปยัง iterables @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]

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.