วิธีข้ามองค์ประกอบใน. map ()


417

ฉันจะข้ามองค์ประกอบอาร์เรย์ได้.mapอย่างไร

รหัสของฉัน:

var sources = images.map(function (img) {
    if(img.src.split('.').pop() === "json"){ // if extension is .json
        return null; // skip
    }
    else{
        return img.src;
    }
});

สิ่งนี้จะส่งคืน:

["img.png", null, "img.png"]

18
คุณทำไม่ได้ แต่คุณสามารถกรองค่า Null ทั้งหมดได้ในภายหลัง
เฟลิกซ์คลิง

1
ทำไมจะไม่ล่ะ? ฉันรู้ว่าการใช้งานต่อไปไม่ได้ผล แต่มันก็เป็นการดีที่จะรู้ว่าทำไม (เช่นกันจะหลีกเลี่ยงการวนซ้ำสองครั้ง) - แก้ไข - สำหรับกรณีของคุณคุณไม่สามารถสลับเงื่อนไข if และส่งคืนได้ก็ต่อimg.srcเมื่อผลลัพธ์ของป๊อปแยก! = = json
GrayedFox

@GrayedFox แล้วโดยปริยายจะใส่ลงไปในอาร์เรย์แทนundefined nullไม่ว่าดีกว่า ...
FZS

คำตอบ:


637

เพียงแค่.filter()มันเป็นครั้งแรก:

var sources = images.filter(function(img) {
  if (img.src.split('.').pop() === "json") {
    return false; // skip
  }
  return true;
}).map(function(img) { return img.src; });

.reduce()หากคุณไม่ต้องการที่จะทำอย่างนั้นซึ่งเป็นไม่ได้ไม่มีเหตุผลเพราะมันมีค่าใช้จ่ายบางอย่างที่คุณสามารถใช้ทั่วไปมากขึ้น โดยทั่วไปคุณสามารถแสดง.map()ในแง่ของ.reduce:

someArray.map(function(element) {
  return transform(element);
});

สามารถเขียนเป็น

someArray.reduce(function(result, element) {
  result.push(transform(element));
  return result;
}, []);

ดังนั้นหากคุณต้องการข้ามองค์ประกอบคุณสามารถทำได้อย่างง่ายดายด้วย.reduce():

var sources = images.reduce(function(result, img) {
  if (img.src.split('.').pop() !== "json") {
    result.push(img.src);
  }
  return result;
}, []);

ในรุ่นนั้นรหัสใน.filter()ตัวอย่างแรกเป็นส่วนหนึ่งของการ.reduce()ติดต่อกลับ แหล่งรูปภาพจะถูกส่งไปยังอาร์เรย์ผลลัพธ์ในกรณีที่การดำเนินการตัวกรองจะเก็บไว้


21
สิ่งนี้ไม่ต้องการให้คุณวนซ้ำทั้งแถวหรือไม่? มีวิธีใดที่จะหลีกเลี่ยงปัญหานี้ได้หรือไม่?
Alex McMillan

7
@AlexMcMillan คุณสามารถใช้.reduce()และทำมันทั้งหมดในหนึ่งผ่านแม้ว่าประสิทธิภาพฉลาดฉันสงสัยว่ามันจะสร้างความแตกต่างที่สำคัญ
แหลม

9
ด้วยค่าลบ "สไตล์" ว่างเปล่าเหล่านี้ทั้งหมด ( null, undefinedและNaNอื่น ๆ ) มันจะดีถ้าเราสามารถใช้หนึ่งในmap()ตัวบ่งชี้ว่าวัตถุนี้แมปกับสิ่งใดและควรข้าม ฉันมักจะเจออาร์เรย์ที่ฉันต้องการแผนที่ 98% ของ (เช่น: String.split()ปล่อยสตริงเดี่ยวที่ว่างเปล่าในตอนท้ายซึ่งฉันไม่สนใจ) ขอบคุณสำหรับคำตอบของคุณ :)
Alex McMillan

6
@AlexMcMillan well .reduce()เป็นฟังก์ชันเรียงลำดับพื้นฐาน "ทำทุกอย่างที่คุณต้องการ" เพราะคุณสามารถควบคุมค่าตอบแทนได้อย่างสมบูรณ์ คุณอาจสนใจงานที่ยอดเยี่ยมโดย Rich Hickey ใน Clojure เกี่ยวกับแนวคิดของทรานสดิวเซอร์
แหลม

3
@vsync .map()คุณไม่สามารถข้ามองค์ประกอบที่มี อย่างไรก็ตามคุณสามารถใช้.reduce()แทนดังนั้นฉันจะเพิ่มที่
แหลม

25

ฉันคิดว่าวิธีที่ง่ายที่สุดในการข้ามองค์ประกอบบางอย่างจากอาเรย์คือการใช้เมธอดfilter ()

ด้วยการใช้วิธีนี้ ( ES5 ) และไวยากรณ์ES6คุณสามารถเขียนโค้ดของคุณในหนึ่งบรรทัดและสิ่งนี้จะคืนสิ่งที่คุณต้องการ :

let images = [{src: 'img.png'}, {src: 'j1.json'}, {src: 'img.png'}, {src: 'j2.json'}];

let sources = images.filter(img => img.src.slice(-4) != 'json').map(img => img.src);

console.log(sources);


1
นั่นคือสิ่งที่.filter()ถูกสร้างขึ้นมาเพื่อ
avalanche1

2
สิ่งนี้ดีกว่าforEachและทำสำเร็จในหนึ่งรอบแทนที่จะเป็นสองรอบ
wuliwong

1
ตามที่คุณต้องการ @wuliwong แต่โปรดคำนึงว่าสิ่งนี้จะยังคงO(n)ซับซ้อนและโปรดดูอย่างน้อยสองบทความนี้เช่นfrontendcollisionblog.com/javascript/2015/08/15/และcoderwall.com/p/kvzbpa/don-t- use-array-foreach ใช้สำหรับแทน ทั้งหมดที่ดีที่สุด!
simhumileco

1
ขอบคุณ @simhumileco! เพราะอย่างนั้นฉันอยู่ที่นี่ (และอาจเป็นอย่างอื่น) คำถามน่าจะเป็นวิธีรวม. ฟิลเตอร์และ. แมพด้วยการวนซ้ำเพียงครั้งเดียว
แจ็คแบล็ก

21

ตั้งแต่ปี 2019 Array.prototype.flatMapเป็นตัวเลือกที่ดี

images.flatMap(({src}) => src.endsWith('.json') ? [] : src);

จาก MDN :

flatMapสามารถใช้เป็นวิธีเพิ่มและลบรายการ (แก้ไขจำนวนรายการ) ในระหว่างแผนที่ กล่าวอีกนัยหนึ่งมันช่วยให้คุณแมปหลายรายการกับหลาย ๆ รายการ (โดยจัดการแต่ละรายการอินพุตแยกต่างหาก) แทนที่จะเป็นแบบหนึ่งต่อหนึ่งเสมอ ในแง่นี้มันทำงานเหมือนกับตัวกรองที่ตรงกันข้าม เพียงกลับอาร์เรย์ 1 องค์ประกอบเพื่อเก็บรายการอาร์เรย์หลายองค์ประกอบเพื่อเพิ่มรายการหรืออาร์เรย์ 0 องค์ประกอบเพื่อลบรายการ


1
คำตอบที่ดีที่สุดมือลง! ข้อมูลเพิ่มเติมที่นี่: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/
......

1
นี่คือคำตอบจริงๆง่ายและแข็งแรงพอ เราเรียนรู้สิ่งนี้ดีกว่าตัวกรองและลด
ป้องกัน orca

19

TLDR:คุณสามารถกรองอาเรย์ของคุณก่อนแล้วจึงทำการแมปของคุณ แต่การทำเช่นนี้จะต้องผ่านสองครั้งในอาเรย์ (ตัวกรองจะส่งกลับอาเรย์ไปยังแผนที่) เนื่องจากอาเรย์นี้มีขนาดเล็กจึงเป็นต้นทุนประสิทธิภาพที่ต่ำมาก คุณยังสามารถลดได้ง่ายๆ อย่างไรก็ตามหากคุณต้องการจินตนาการอีกครั้งว่าวิธีนี้สามารถทำได้ด้วยการส่งผ่านแถวเดียว (หรือประเภทข้อมูลใด ๆ ) คุณสามารถใช้แนวคิดที่เรียกว่า "transducers" ซึ่งได้รับความนิยมโดย Rich Hickey

ตอบ:

เราไม่ควรต้องการการเพิ่มการโยงแบบจุดและการดำเนินการกับอาร์เรย์[].map(fn1).filter(f2)...เนื่องจากวิธีนี้จะสร้างอาร์เรย์กลางในหน่วยความจำในทุกreducingฟังก์ชั่น

วิธีที่ดีที่สุดทำงานในฟังก์ชั่นลดจริงจึงมีเพียงหนึ่งรอบของข้อมูลและไม่มีอาร์เรย์เพิ่มเติม

ฟังก์ชั่นการลดคือฟังก์ชั่นที่ส่งเข้าไปreduceและรับตัวสะสมและอินพุตจากแหล่งที่มาและส่งคืนสิ่งที่ดูเหมือนตัวสะสม

// 1. create a concat reducing function that can be passed into `reduce`
const concat = (acc, input) => acc.concat([input])

// note that [1,2,3].reduce(concat, []) would return [1,2,3]

// transforming your reducing function by mapping
// 2. create a generic mapping function that can take a reducing function and return another reducing function
const mapping = (changeInput) => (reducing) => (acc, input) => reducing(acc, changeInput(input))

// 3. create your map function that operates on an input
const getSrc = (x) => x.src
const mappingSrc = mapping(getSrc)

// 4. now we can use our `mapSrc` function to transform our original function `concat` to get another reducing function
const inputSources = [{src:'one.html'}, {src:'two.txt'}, {src:'three.json'}]
inputSources.reduce(mappingSrc(concat), [])
// -> ['one.html', 'two.txt', 'three.json']

// remember this is really essentially just
// inputSources.reduce((acc, x) => acc.concat([x.src]), [])


// transforming your reducing function by filtering
// 5. create a generic filtering function that can take a reducing function and return another reducing function
const filtering = (predicate) => (reducing) => (acc, input) => (predicate(input) ? reducing(acc, input): acc)

// 6. create your filter function that operate on an input
const filterJsonAndLoad = (img) => {
  console.log(img)
  if(img.src.split('.').pop() === 'json') {
    // game.loadSprite(...);
    return false;
  } else {
    return true;
  }
}
const filteringJson = filtering(filterJsonAndLoad)

// 7. notice the type of input and output of these functions
// concat is a reducing function,
// mapSrc transforms and returns a reducing function
// filterJsonAndLoad transforms and returns a reducing function
// these functions that transform reducing functions are "transducers", termed by Rich Hickey
// source: http://clojure.com/blog/2012/05/15/anatomy-of-reducer.html
// we can pass this all into reduce! and without any intermediate arrays

const sources = inputSources.reduce(filteringJson(mappingSrc(concat)), []);
// [ 'one.html', 'two.txt' ]

// ==================================
// 8. BONUS: compose all the functions
// You can decide to create a composing function which takes an infinite number of transducers to
// operate on your reducing function to compose a computed accumulator without ever creating that
// intermediate array
const composeAll = (...args) => (x) => {
  const fns = args
  var i = fns.length
  while (i--) {
    x = fns[i].call(this, x);
  }
  return x
}

const doABunchOfStuff = composeAll(
    filtering((x) => x.src.split('.').pop() !== 'json'),
    mapping((x) => x.src),
    mapping((x) => x.toUpperCase()),
    mapping((x) => x + '!!!')
)

const sources2 = inputSources.reduce(doABunchOfStuff(concat), [])
// ['ONE.HTML!!!', 'TWO.TXT!!!']

ทรัพยากร: โพสต์สัญญาณ transducers hickey รวย


17

นี่เป็นวิธีแก้ปัญหาที่สนุก:

/**
 * Filter-map. Like map, but skips undefined values.
 *
 * @param callback
 */
function fmap(callback) {
    return this.reduce((accum, ...args) => {
        let x = callback(...args);
        if(x !== undefined) {
            accum.push(x);
        }
        return accum;
    }, []);
}

ใช้กับตัวดำเนินการเชื่อมโยง :

[1,2,-1,3]::fmap(x => x > 0 ? x * 2 : undefined); // [2,4,6]

1
วิธีนี้บันทึกฉันจากที่มีการใช้งานที่แยกจากกันmap, filterและconcatการโทร
LogicalBranch

11

คำตอบซองขอบฟุ่มเฟือย Sans:

const thingsWithoutNulls = things.reduce((acc, thing) => {
  if (thing !== null) {
    acc.push(thing);
  }
  return acc;
}, [])

10

ทำไมไม่ใช้แค่ forEach loop เท่านั้น?

let arr = ['a', 'b', 'c', 'd', 'e'];
let filtered = [];

arr.forEach(x => {
  if (!x.includes('b')) filtered.push(x);
});

console.log(filtered)   // filtered === ['a','c','d','e'];

หรือแม้แต่ตัวกรองที่ใช้ง่ายกว่า:

const arr = ['a', 'b', 'c', 'd', 'e'];
const filtered = arr.filter(x => !x.includes('b')); // ['a','c','d','e'];

1
ดีที่สุดจะเป็นแบบง่าย ๆ สำหรับลูปที่กรองและสร้างอาร์เรย์ใหม่ แต่สำหรับบริบทของการใช้mapให้เหมือนตอนนี้ (เป็น 4yrs ที่ผ่านมาผมถามคำถามนี้เมื่อฉันรู้อะไรเกี่ยวกับการเขียนโปรแกรม)
อิสมาอิล

ยุติธรรมพอเนื่องจากไม่มีวิธีตรงไปด้านบนพร้อมแผนที่และโซลูชันทั้งหมดใช้วิธีอื่นที่ฉันคิดว่าฉันจะชิปในวิธีที่ง่ายที่สุดที่ฉันสามารถคิดทำแบบเดียวกัน
อเล็กซ์

8
var sources = images.map(function (img) {
    if(img.src.split('.').pop() === "json"){ // if extension is .json
        return null; // skip
    }
    else{
        return img.src;
    }
}).filter(Boolean);

.filter(Boolean)จะกรองค่า falsey ใด ๆ nullในอาร์เรย์ที่กำหนดซึ่งในกรณีของคุณเป็น


3

นี่คือวิธีการใช้งานยูทิลิตี้ (รองรับ ES5) ซึ่งแมปค่าที่ไม่ใช่ค่าว่างเท่านั้น (ซ่อนการโทรเพื่อลด):

function mapNonNull(arr, cb) {
    return arr.reduce(function (accumulator, value, index, arr) {
        var result = cb.call(null, value, index, arr);
        if (result != null) {
            accumulator.push(result);
        }

        return accumulator;
    }, []);
}

var result = mapNonNull(["a", "b", "c"], function (value) {
    return value === "b" ? null : value; // exclude "b"
});

console.log(result); // ["a", "c"]


1

ฉันใช้วน.forEachซ้ำและผลักผลลัพธ์ไปยังresultsอาเรย์แล้วใช้ด้วยวิธีนี้ฉันจะไม่วนซ้ำอาร์เรย์สองครั้ง


1

ในการคาดการณ์ความคิดเห็นของ Felix Klingคุณสามารถใช้.filter()สิ่งนี้:

var sources = images.map(function (img) {
  if(img.src.split('.').pop() === "json") { // if extension is .json
    return null; // skip
  } else {
    return img.src;
  }
}).filter(Boolean);

ที่จะลบค่าเท็จจากอาร์เรย์ที่ส่งคืนโดย .map()

คุณสามารถทำให้มันง่ายขึ้นเช่นนี้:

var sources = images.map(function (img) {
  if(img.src.split('.').pop() !== "json") { // if extension is .json
    return img.src;
  }
}).filter(Boolean);

หรือแม้กระทั่งเป็นสายการบินเดียวที่ใช้ฟังก์ชั่นลูกศรการทำลายวัตถุและ&&ผู้ปฏิบัติงาน:

var sources = images.map(({ src }) => src.split('.').pop() !== "json" && src).filter(Boolean);

0

นี่เป็นรุ่นปรับปรุงของรหัสให้โดย @theprtk เป็นการล้างข้อมูลเล็กน้อยเพื่อแสดงเวอร์ชันทั่วไปขณะที่มีตัวอย่าง

หมายเหตุ: ฉันจะเพิ่มนี่เป็นความคิดเห็นในโพสต์ของเขา แต่ฉันยังไม่มีชื่อเสียงเพียงพอ

/**
 * @see http://clojure.com/blog/2012/05/15/anatomy-of-reducer.html
 * @description functions that transform reducing functions
 */
const transduce = {
  /** a generic map() that can take a reducing() & return another reducing() */
  map: changeInput => reducing => (acc, input) =>
    reducing(acc, changeInput(input)),
  /** a generic filter() that can take a reducing() & return */
  filter: predicate => reducing => (acc, input) =>
    predicate(input) ? reducing(acc, input) : acc,
  /**
   * a composing() that can take an infinite # transducers to operate on
   *  reducing functions to compose a computed accumulator without ever creating
   *  that intermediate array
   */
  compose: (...args) => x => {
    const fns = args;
    var i = fns.length;
    while (i--) x = fns[i].call(this, x);
    return x;
  },
};

const example = {
  data: [{ src: 'file.html' }, { src: 'file.txt' }, { src: 'file.json' }],
  /** note: `[1,2,3].reduce(concat, [])` -> `[1,2,3]` */
  concat: (acc, input) => acc.concat([input]),
  getSrc: x => x.src,
  filterJson: x => x.src.split('.').pop() !== 'json',
};

/** step 1: create a reducing() that can be passed into `reduce` */
const reduceFn = example.concat;
/** step 2: transforming your reducing function by mapping */
const mapFn = transduce.map(example.getSrc);
/** step 3: create your filter() that operates on an input */
const filterFn = transduce.filter(example.filterJson);
/** step 4: aggregate your transformations */
const composeFn = transduce.compose(
  filterFn,
  mapFn,
  transduce.map(x => x.toUpperCase() + '!'), // new mapping()
);

/**
 * Expected example output
 *  Note: each is wrapped in `example.data.reduce(x, [])`
 *  1: ['file.html', 'file.txt', 'file.json']
 *  2:  ['file.html', 'file.txt']
 *  3: ['FILE.HTML!', 'FILE.TXT!']
 */
const exampleFns = {
  transducers: [
    mapFn(reduceFn),
    filterFn(mapFn(reduceFn)),
    composeFn(reduceFn),
  ],
  raw: [
    (acc, x) => acc.concat([x.src]),
    (acc, x) => acc.concat(x.src.split('.').pop() !== 'json' ? [x.src] : []),
    (acc, x) => acc.concat(x.src.split('.').pop() !== 'json' ? [x.src.toUpperCase() + '!'] : []),
  ],
};
const execExample = (currentValue, index) =>
  console.log('Example ' + index, example.data.reduce(currentValue, []));

exampleFns.raw.forEach(execExample);
exampleFns.transducers.forEach(execExample);

0

map()คุณสามารถใช้วิธีการหลังจากที่คุณ ตัวอย่างวิธีการfilter()ในกรณีของคุณ:

var sources = images.map(function (img) {
  if(img.src.split('.').pop() === "json"){ // if extension is .json
    return null; // skip
  }
  else {
    return img.src;
  }
});

วิธีการกรอง:

const sourceFiltered = sources.filter(item => item)

sourceFilteredจากนั้นเฉพาะรายการที่มีอยู่ในแถวใหม่

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