TLDR: Możesz najpierw przefiltrować tablicę, a następnie wykonać mapę, ale wymagałoby to dwóch przejść w tablicy (filtr zwraca tablicę do mapy). Ponieważ ta tablica jest niewielka, jest to bardzo mały koszt wydajności. Możesz także zrobić proste zmniejszenie. Jeśli jednak chcesz sobie wyobrazić, jak można to zrobić za pomocą pojedynczego przejścia przez tablicę (lub dowolny typ danych), możesz skorzystać z pomysłu zwanego „przetwornikami”, który stał się popularny przez Richa Hickeya.
Odpowiedź:
Nie powinniśmy wymagać zwiększania łańcucha i operowania na [].map(fn1).filter(f2)...macierzach, ponieważ takie podejście tworzy tablice pośrednie w pamięci dla każdej reducingfunkcji.
Najlepsze podejście działa na rzeczywistej funkcji redukcji, więc jest tylko jedno przejście danych i żadnych dodatkowych tablic.
Funkcja redukująca jest funkcją przekazaną do reducei pobiera akumulator i wkład ze źródła i zwraca coś, co wygląda jak akumulator
// 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!!!']
Zasoby: post: przetworniki Rich Hickey