我如何在。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"]

先.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()。你通常可以用.reduce来表示.map():

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()回调的一部分。只有在过滤操作保留结果数组的情况下,才会将图像源推入结果数组。

更新-这个问题得到了很多关注,我想补充以下澄清意见。作为一个概念,.map()的目的正是“map”的意思:根据某些规则将一个值列表转换为另一个值列表。就像某些国家的纸质地图如果完全缺少几个城市就会显得很奇怪一样,从一个列表映射到另一个列表只有在有1对1的结果值集时才有意义。

我并不是说,从一个旧列表中创建一个新列表并排除一些值是没有意义的。我只是试图说明.map()只有一个简单的意图,即创建一个与旧数组长度相同的新数组,只是使用由旧值转换形成的值。


TLDR:你可以先过滤你的数组,然后执行你的映射,但这将需要对数组进行两次传递(过滤器返回一个数组映射)。因为这个数组很小,所以它的性能代价非常小。你也可以做简单的减法。然而,如果你想重新想象如何通过对数组(或任何数据类型)的一次传递来实现这一点,你可以使用Rich Hickey流行的“传感器”思想。

答:

我们不应该要求增加点链并对数组[]进行操作。map(fn1).filter(f2)…因为这种方法在内存中为每个约简函数创建中间数组。

最好的方法是对实际的约简函数进行操作,因此只有一次数据传递,没有额外的数组。

reduce函数是传递给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!!!']

资源:丰富的吻痕传感器后


这里有一个有趣的解决方案:

/**
 * Filter-map. Like map, but skips undefined values.
 *
 * @param callback
 */
function fmap(callback) {
    return this.reduce((accum, ...args) => {
        const 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]

下面是一个实用工具方法(ES5兼容),它只映射非空值(隐藏reduce调用):

函数mapNonNull(arr, cb) { 加勒比海盗。Reduce(函数(累加器,值,索引,arr) { Var结果= cb。调用(null, value, index, arr); If (result != null) { accumulator.push(结果); } 返回蓄电池; },[]); } var result = mapNonNull(["a", "b", "c"],函数(值){ 返回值=== "b" ?Null:值;//排除"b" }); console.log(结果);// ["a", "c"]


回答无多余的边缘情况:

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

为什么不直接使用forEach循环?

Let arr = ['a', 'b', 'c', 'd', 'e']; Let filtered = []; 加勒比海盗。forEach(x => { If (!x.includes('b')) filter .push(x); }); console.log(过滤)/ /过滤= = = [' a ', ' c ', ' d ', ' e '];

或者更简单的使用过滤器:

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

我认为从数组中跳过一些元素的最简单方法是使用filter()方法。

通过使用这个方法(ES5)和ES6语法,你可以在一行中编写你的代码,这将返回你想要的:

Let images = [{src: 'img.png'}, {src: 'j1. png'}。Json '}, {src: 'img.png'}, {src: ' Json . Json '}]; Let sources = images。过滤(img => img.src.slice(-4) != 'json')。Map (img => img.src); console.log(来源);


从2019年开始,Array.prototype.flatMap是一个很好的选择。

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

中数:

flatMap可以作为一种添加和删除项目的方法(修改 项目数目)在一个地图。换句话说,它允许您进行映射 多项对多项(通过分别处理每个输入项), 而不是总是一对一。在这个意义上,它就像 filter的反义词。只需返回一个1元素的数组来保存该项, 用于添加项的多元素数组,或用于删除项的0元素数组 的项目。


我使用.forEach来迭代,并将结果推到结果数组,然后使用它,有了这个解决方案,我将不会在数组上循环两次


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(布尔值)将过滤掉给定数组中的任何错误值,在您的例子中是null。


下面是@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);

你可以使用map()方法的after。例如在你的例子中,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中。


为了推断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);

如果它在一行ES5/ES6中为空或未定义

//will return array of src 
images.filter(p=>!p.src).map(p=>p.src);//p = property


//in your condition
images.filter(p=>p.src.split('.').pop() !== "json").map(p=>p.src);

你可以这样做

Var sources = []; 图像。映射(函数(img) { 如果(img.src.split (' . ') .pop () ! = = json){/ /如果扩展不是. json sources.push (img.src);//只推有效值 } });


我使用foreach():

var sources = [];

images.forEach(function (img) {
    if(img.src.split('.').pop() !== "json"){ // if extension is .json
        sources.push(img);
    }
});

注:我否定了你的逻辑。


你可以像这样使用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;
    }})?.filter(x => x !== null);

const arr = [0,1, ", undefined, false, 2, undefined, null,, 3, NaN]; const filtered = arr.filter(Boolean); console.log(过滤); /* 输出:[1,2,3] * /