Как пропустить элемент в.map()?

Как я могу пропустить элемент массива в .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"]

Ответ 1

Просто .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(). Источник изображения помещается в массив результатов только в том случае, если операция фильтра сохранила бы его.

Ответ 2

TL;DR: вы можете сначала отфильтровать массив, а затем выполнить карту, но для этого потребуется два прохода в массиве (фильтр возвращает массив в карту). Поскольку этот массив небольшой, это очень низкая стоимость производительности. Однако, если вы хотите представить, как это можно сделать за один проход по массиву, вы можете использовать идею под названием "преобразователи", ставшую популярной благодаря Ричу Хикки.

Ответ:

Мы не должны требовать увеличения цепочки точек и работы с массивом [].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!!!']

Ресурсы: богатый пост преобразователей хикки

Ответ 3

Здесь веселое решение:

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

Ответ 4

TL; TR;

Я думаю, что самый простой способ пропустить некоторые элементы из массива - это использовать метод filter().

Используя этот метод и синтаксис ES6, вы можете написать свой код в одну строку:

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

и это вернет то, что вы хотите:

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

Ответ 5

Ответьте без лишних краевых случаев:

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

Ответ 6

Почему бы просто не использовать цикл forEach?

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

Ответ 7

Здесь служебный метод (совместимый с 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"]

Ответ 8

Array.prototype.flatMap является еще одним вариантом.

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

От MDN:

flatMap можно использовать как способ добавления и удаления элементов (изменения количества элементов) во время карты. Другими словами, он позволяет сопоставить множество элементов множеству элементов (обрабатывая каждый элемент ввода отдельно), а не всегда один к одному. В этом смысле он работает как противоположность фильтра. Просто верните массив из 1 элемента, чтобы сохранить элемент, массив из нескольких элементов, чтобы добавить элементы, или массив из 0 элементов, чтобы удалить элемент.

Ответ 9

Я использую .forEach чтобы .forEach и переместить результат в массив results затем использовать его, с этим решением я не буду дважды перебирать массив

Ответ 10

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.

Ответ 11

Вот обновленная версия кода , предоставленного @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);