2014-07-17 10 views
146

Come posso saltare un elemento di matrice in .map?javascript salta elemento su .map()

il mio codice:

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

questo tornerà

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

Non è possibile, ma in seguito puoi filtrare tutti i valori nulli. –

risposta

215

Basta .filter() per primo:

var sources = json.images.filter(function(img) { 
    if (img.src.split('.').pop() === "json") { 
    game.loadSprite(img.src,false,function(){ 
     console.log("sprite loaded!"); 
    }); 
    return false; // skip 
    } 
    return true; 
}).map(function(img) { return img.src; }); 

Se non si vuole fare questo, che non è irragionevole dal momento che ha un costo, è possibile utilizzare il più generale .reduce(). Si può generalmente esprimere .map() in termini di .reduce:

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

può essere scritta come

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

Quindi se avete bisogno di saltare elementi, si può fare facilmente con .reduce():

var sources = json.images.reduce(function(result, img) { 
    if (img.src.split('.').pop() === "json") { 
    game.loadSprite(img.src, false, function() { 
     console.log("Sprite loaded!"); 
    }); 
    } 
    else { 
    result.push(img.src); 
    } 
    return result; 
}, []); 

In questa versione, il codice nello .filter() del primo campione fa parte della richiamata .reduce() . L'origine dell'immagine viene solo spinto sull'array dei risultati nel caso in cui l'operazione di filtro l'avrebbe mantenuta.

+7

Questo non richiede il loop dell'intero array per due volte? C'è un modo per evitarlo? –

+2

@AlexMcMillan si potrebbe usare '.riduci() 'e fai tutto in un solo passaggio, anche se in termini di prestazioni dubito che farebbe una differenza significativa. – Pointy

+4

Con tutti questi valori negativi, "empty" -stile ('null',' indefinito', 'NaN' ecc.) Sarebbe bene se potessimo utilizzarne uno all'interno di' map() 'come indicatore che questo oggetto mappa a niente e dovrebbe essere saltato. Spesso mi imbatto in array che voglio mappare al 98% di (ad esempio: 'String.split()' lasciando una singola stringa vuota alla fine, che non mi interessa). Grazie per la tua risposta :) –

15

Aggiornamento: tutto quello che ho pensato che sapevo di questa risposta era sbagliata

Non dovremmo richiedere l'aumento di punti concatenamento e operando sulla matrice [].map(fn1).filter(f2)... dal momento che questo approccio crea gli array intermedi in memoria su ogni funzione reducing.

L'approccio migliore funziona sulla funzione di riduzione effettiva in modo che sia presente un solo passaggio di dati e nessun array aggiuntivo.

La funzione di riduzione è la funzione passata in reduce e prende un accumulatore e ingresso dalla sorgente e restituisce qualcosa che assomiglia l'accumulatore

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

Ecco una soluzione divertente:

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

Usa con la bind operator:

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

Ecco un metodo di utilità che mappa solo i valori non nulli (nasconde la chiamata per ridurre):

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"]

3

risposte sans casi limite superflue:

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

Il ritorno della funzione push è l'elemento non l'accumulatore. Dovresti separare i comandi push e return. –

+0

Grazie Thiago! Vedo come funziona ora. Non sei sicuro del motivo per cui gli altri hanno rifiutato la tua modifica _correct_. A volte questo sito è folle. – corysimmons