.Map () öğesindeki bir öğeyi atlama


417

Bir dizi öğesini nasıl atlayabilirim .map?

Kodum:

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

Bu geri dönecektir:

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

18
Yapamazsınız, ancak daha sonra tüm boş değerleri filtreleyebilirsiniz.
Felix Kling

1
Neden olmasın? Kullanıyorum biliyorum devam niçin (ayrıca çift döngü önleyeceğini) bilmek işi yok ama iyi olurdu - düzenle - dava için sadece ve eğer durum invert sadece dönemediler img.srcbölünmüş pop sonucu eğer =! = json?
GrayedFox

@GrayedFox Sonra undefineddizi yerine örtük konulur null. Daha iyi değil ...
FZs

Yanıtlar:


638

Sadece .filter()önce:

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

Bunu yapmak istemiyorsanız, bu biraz maliyete sahip olduğu için mantıksız değildir, daha genel olanı kullanabilirsiniz .reduce(). Genellikle şu .map()terimlerle ifade edebilirsiniz .reduce:

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

olarak yazılabilir

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

Yani öğeleri atlamanız gerekiyorsa, bunu aşağıdakilerle kolayca yapabilirsiniz .reduce():

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

Bu sürümde, .filter()ilk örnekten gelen kod .reduce()geri aramanın bir parçasıdır . Görüntü kaynağı, yalnızca filtre işleminin onu tutması durumunda sonuç dizisine itilir.


21
Bu, tüm diziyi iki kez devretmenizi gerektirmez mi? Bundan kaçınmanın bir yolu var mı?
Alex McMillan

7
@AlexMcMillan kullanabilirsiniz .reduce()ve hepsini bir geçişte yapabilirsiniz, ancak performans açısından önemli bir fark yaratacağından şüpheliyim.
Sivri

9
Tüm bu negatif ile, tarzı değerleri ( "boş" null, undefined, NaNbiz içeride birini kullanmak eğer iyi olurdu vs) map()bu nesne hiçbir şey eşler ve atlanması olduğunu bir göstergesi olarak. Sıklıkla% 98'i eşleştirmek istediğim dizilerle karşılaşıyorum (örneğin: String.split()sonunda umursamadığım tek bir boş dize bırakmak). Cevabınız için teşekkürler :)
Alex McMillan

6
@AlexMcMillan iyi bir .reduce()"temel ne istersen yap" fonksiyonu temel, çünkü dönüş değeri üzerinde tam kontrole sahip. Dönüştürücü kavramı ile ilgili olarak Rich Hickey'nin Clojure'daki mükemmel çalışmasıyla ilgilenebilirsiniz .
Sivri

3
@vsync ile bir öğeyi atlayamazsınız .map(). Ancak .reduce()bunun yerine kullanabilirsiniz , bu yüzden onu ekleyeceğim.
Sivri

25

Bir diziden bazı öğeleri atlamak için en basit yolu filter () yöntemini kullanmak olduğunu düşünüyorum.

Bu yöntemi ( ES5 ) ve ES6 sözdizimini kullanarak kodunuzu bir satıra yazabilirsiniz ve bu istediğinizi döndürür :

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
tam olarak bunun .filter()için yapıldı
avalanche1

2
Bu forEach, iki yerine bir geçişte daha iyi ve tamamlanıyor mu?
wuliwong

1
İstediğiniz gibi @wuliwong. Ancak, bunun hala O(n)karmaşıklık ölçümünde olacağını ve lütfen en azından şu iki makaleye de bakın: frontendcollisionblog.com/javascript/2015/08/15/… ve coderwall.com/p/kvzbpa/don-t- use-array-foreach-use-for-bunun yerine En iyisi!
simhumileco

1
Teşekkürler @simhumileco! Tam olarak bu yüzden buradayım (ve muhtemelen başkaları da). Soru, muhtemelen .filter ve .map öğelerini yalnızca bir kez yineleyerek nasıl birleştireceğinizdir.
Jack Black

21

2019'dan beri, Array.prototype.flatMap iyi bir seçenektir.

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

MDN'den :

flatMapbir harita sırasında öğe eklemek ve kaldırmak (öğe sayısını değiştirmek) için bir yol olarak kullanılabilir. Başka bir deyişle, her zaman bire bir değil, birçok öğeyi birçok öğeyle (her bir giriş öğesini ayrı ayrı işleyerek) eşlemenize olanak tanır. Bu anlamda filtrenin tersi gibi çalışır. Öğeyi tutmak için 1 öğeli bir dizi, öğeleri eklemek için çok öğeli bir dizi veya öğeyi kaldırmak için 0 öğeli bir dizi döndürmeniz yeterlidir.


1
En iyi cevap eller aşağı! Daha fazla bilgi için burada: developer.mozilla.org/tr-TR/docs/Web/JavaScript/Reference/…
Dominique PERETTI

1
bu gerçekten cevap, yeterince basit ve yeterince güçlü. bunun filtreden ve azaltmadan daha iyi olduğunu öğreniyoruz.
orca

19

TLDR: Önce dizinizi filtreleyip haritanızı gerçekleştirebilirsiniz, ancak bu dizide iki geçiş gerektirir (filtre eşlemek için bir dizi döndürür). Bu dizi küçük olduğundan, çok düşük bir performans maliyetidir. Ayrıca basit bir azaltma yapabilirsiniz. Bununla birlikte, bunun dizi (veya herhangi bir veri türü) üzerinden tek bir geçişle nasıl yapılabileceğini yeniden hayal etmek istiyorsanız, Rich Hickey tarafından popüler hale getirilen "dönüştürücüler" adlı bir fikir kullanabilirsiniz.

Cevap:

[].map(fn1).filter(f2)...Bu yaklaşım, her reducingişlevde bellekte ara diziler oluşturduğundan dizi üzerinde nokta zincirinin artırılmasını ve dizide çalışmasını gerektirmemeliyiz.

En iyi yaklaşım, gerçek azaltma işlevi üzerinde çalışır, bu nedenle yalnızca bir veri geçişi vardır ve fazladan diziler yoktur.

İndirgeme fonksiyonu, reduceaküye ve kaynaktan bir akümülatör ve giriş alır ve akümülatöre benzeyen bir şey döndüren fonksiyondur.

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

Kaynaklar: rich hickey transducers post


17

İşte eğlenceli bir çözüm:

/**
 * 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;
    }, []);
}

Ciltleme işleci ile kullanın :

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

1
Bu yöntem ayrı kullanmak zorunda kurtardı beni map, filterve concataramaları.
LogicalBranch

11

Sans gereksiz kenar vakalarını cevaplayın:

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

10

Neden sadece bir forEach döngüsü kullanmıyorsunuz?

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

Ya da daha basit kullanım filtresi:

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

1
En iyisi, yeni bir dizi filtreleyen ve oluşturan döngü için basit olurdu, ancak kullanma bağlamı için mapşimdi olduğu gibi kalmasını sağlar. (4 yıl önce kodlama hakkında hiçbir şey bilmediğimde bu soruyu sordum)
İsmail

Harita ile yukarı doğru doğrudan bir yol olmadığı ve tüm çözümlerin alternatif bir yöntem kullandığı göz önüne alındığında, aynı şeyi yapmayı düşündüğüm en basit şekilde çipleyeceğimi düşündüm.
Alex

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

Bu .filter(Boolean), belirli bir dizideki herhangi bir falsey değerini filtreleyecektir null.


3

Aşağıda, yalnızca boş olmayan değerleri eşleyen (azaltmak için çağrıyı gizleyen) bir yardımcı yöntem (ES5 uyumlu) verilmiştir:

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

Ben .forEachtekrarlamak için kullanın ve resultsdizi dizi itmek ve sonra kullanmak, bu çözüm ile ben dizi üzerinde iki kez döngü olmaz


1

Felix Kling'in yorumunu tahmin etmek için şu şekilde kullanabilirsiniz .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);

Bu, tarafından döndürülen diziden falsey değerlerini kaldıracaktır .map()

Bunu daha da basitleştirebilirsiniz:

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

Veya bir ok işlevi, nesne imhası ve &&operatör kullanan tek astar olarak :

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

0

@Theprtk tarafından sağlanan kodun güncellenmiş bir sürümü . Bir örnek alırken genelleştirilmiş sürümü göstermek biraz temizlendi.

Not: Bunu yazısına bir yorum olarak eklerdim ama henüz yeterince itibarım yok

/**
 * @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

Sizden sonra yöntemini kullanabilirsiniz map(). filter()Örneğin sizin durumunuzdaki yöntem :

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

Yöntem filtresi:

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

Ardından, yalnızca varolan öğeler yeni dizide bulunur sourceFiltered.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.