Jak mogę przerwać iterację reduce()
metody?
for
:
for (var i = Things.length - 1; i >= 0; i--) {
if(Things[i] <= 0){
break;
}
};
reduce()
Things.reduce(function(memo, current){
if(current <= 0){
//break ???
//return; <-- this will return undefined to memo, which is not what I want
}
}, 0)
javascript
loops
reduce
Julio Marins
źródło
źródło
current
powyższy kod? Nie rozumiem, jak mogą zrobić to samo. W każdym razie istnieją metody, które rozkładają się na początku jaksome
,every
,find
some
ievery
zwracają wartości logiczne ifind
zwracają pojedynczy rekord, chcę wykonać operacje w celu wygenerowania notatki.current
jest currentValue. odniesieniecurrent
w pierwszym fragmencie kodu?reduce
, będziesz musiał znaleźć inny sposób z wbudowanymi funkcjami, które wcześnie kończą pracę lub stworzyć własnego pomocnika, lub użyć lodash lub czegoś podobnego. Czy możesz zamieścić pełny przykład tego, co chcesz zrobić?Odpowiedzi:
AKTUALIZACJA
Niektórzy komentatorzy słusznie zauważają, że oryginalna tablica jest modyfikowana w celu wczesnego złamania
.reduce()
logiki.Dlatego nieznacznie zmodyfikowałem odpowiedź , dodając
.slice(0)
przed wywołaniem kolejnego.reduce()
kroku, uzyskując kopię oryginalnej tablicy. UWAGA : Podobnymi operacjami, które wykonują to samo zadanie, sąslice()
(mniej wyraźne) i operator rozproszenia[...array]
( nieco mniej wydajne ). Należy pamiętać, że wszystkie one dodają dodatkowy stały współczynnik czasu liniowego do całkowitego czasu działania + 1 * (O (1)).Kopia służy do zachowania oryginalnej tablicy przed ewentualną mutacją, która powoduje wyrzucenie z iteracji.
const array = ['9', '91', '95', '96', '99']; const x = array .slice(0) // create copy of "array" for iterating .reduce((acc, curr, i, arr) => { if (i === 2) arr.splice(1); // eject early by mutating iterated copy return (acc += curr); }, ''); console.log("x: ", x, "\noriginal Arr: ", array); // x: 99195 // original Arr: [ '9', '91', '95', '96', '99' ]
STARY
MOŻESZ przerwać dowolną iterację wywołania .reduce (), modyfikując czwarty argument funkcji redukuj: "tablica". Nie ma potrzeby korzystania z niestandardowej funkcji redukcji. Pełną listę parametrów znajdziesz w Dokumentach
.reduce()
.Array.prototype.reduce ((acc, curr, i, array))
Czwarty argument to tablica podlegająca iteracji.
const array = ['9', '91', '95', '96', '99']; const x = array .reduce((acc, curr, i, arr) => { if(i === 2) arr.splice(1); // eject early return acc += curr; }, ''); console.log('x: ', x); // x: 99195
CZEMU?:
Jedynym powodem, dla którego mogę wymyślić, aby użyć tego zamiast wielu innych przedstawionych rozwiązań, jest to, że chcesz zachować metodologię programowania funkcjonalnego w swoim algorytmie i chcesz, aby to osiągnąć najbardziej deklaratywne podejście. Jeśli twoim celem jest dosłownie ZMNIEJSZENIE tablicy do alternatywnego prymitywu niefalsey (ciąg, liczba, wartość logiczna, symbol), to uważam, że jest to w rzeczywistości najlepsze podejście.
DLACZEGO NIE?
Istnieje cała lista argumentów, aby NIE mutować parametrów funkcji, ponieważ jest to zła praktyka.
źródło
splice
wykonuje widoczną mutację (array
). Zgodnie z paradygmatem funkcjonalnym, użyłbyś albo redukcji w stylu przekazywania kontynuacji, albo skorzystałbyś z leniwej oceny z prawostronną redukcją. Lub, jako prostsza alternatywa, po prostu zwykła rekurencja.Nie używaj redukcji. Po prostu wykonaj iterację tablicy za pomocą normalnych iteratorów (for itp.) I przerwij, gdy twój warunek zostanie spełniony.
źródło
Możesz używać funkcji takich jak some i every, o ile nie zależy Ci na wartości zwracanej. co się psuje, gdy wywołanie zwrotne zwraca false, a niektóre, gdy zwraca true:
things.every(function(v, i, o) { // do stuff if (timeToBreak) { return false; } else { return true; } }, thisArg);
źródło
reduce
wtedy z definicji, że nie dbają o wartości zwracanej.Oczywiście nie ma sposobu, aby wbudowana wersja programu
reduce
zakończyła działanie przedwcześnie.Ale możesz napisać własną wersję reduktora, która używa specjalnego tokena do identyfikacji, kiedy pętla powinna zostać przerwana.
var EXIT_REDUCE = {}; function reduce(a, f, result) { for (let i = 0; i < a.length; i++) { let val = f(result, a[i], i, a); if (val === EXIT_REDUCE) break; result = val; } return result; }
Użyj tego w ten sposób, aby zsumować tablicę, ale wyjdź, gdy trafisz 99:
reduce([1, 2, 99, 3], (a, b) => b === 99 ? EXIT_REDUCE : a + b, 0); > 3
źródło
Array.every może zapewnić bardzo naturalny mechanizm wyjścia z iteracji wyższego rzędu.
const product = function(array) { let accumulator = 1; array.every( factor => { accumulator *= factor; return !!factor; }); return accumulator; } console.log(product([2,2,2,0,2,2])); // 0
źródło
Możesz złamać każdy kod - a tym samym każdą kompilację w iteratorze - rzucając wyjątek:
function breakReduceException(value) { this.value = value } try { Things.reduce(function(memo, current) { ... if (current <= 0) throw new breakReduceException(memo) ... }, 0) } catch (e) { if (e instanceof breakReduceException) var memo = e.value else throw e }
źródło
if (current <= 0) window.top.close()
Ponieważ argumenty
promise
s mająresolve
ireject
callback, utworzyłem funkcjęreduce
obejścia zbreak
argumentem callback. Pobiera wszystkie te same argumenty, coreduce
metoda natywna , z wyjątkiem tego, że pierwszy z nich jest tablicą do pracy (unikaj małpiego łatania). TrzeciinitialValue
argument [2] jest opcjonalny. Zobacz poniższy fragment kodufunction
reduktora.var list = ["w","o","r","l","d"," ","p","i","e","r","o","g","i"]; var result = reducer(list,(total,current,index,arr,stop)=>{ if(current === " ") stop(); //when called, the loop breaks return total + current; },'hello '); console.log(result); //hello world function reducer(arr, callback, initial) { var hasInitial = arguments.length >= 3; var total = hasInitial ? initial : arr[0]; var breakNow = false; for (var i = hasInitial ? 0 : 1; i < arr.length; i++) { var currentValue = arr[i]; var currentIndex = i; var newTotal = callback(total, currentValue, currentIndex, arr, () => breakNow = true); if (breakNow) break; total = newTotal; } return total; }
A oto skrypt zmodyfikowany przez
reducer
tablicęmethod
:Array.prototype.reducer = function(callback,initial){ var hasInitial = arguments.length >= 2; var total = hasInitial ? initial : this[0]; var breakNow = false; for (var i = hasInitial ? 0 : 1; i < this.length; i++) { var currentValue = this[i]; var currentIndex = i; var newTotal = callback(total, currentValue, currentIndex, this, () => breakNow = true); if (breakNow) break; total = newTotal; } return total; }; var list = ["w","o","r","l","d"," ","p","i","e","r","o","g","i"]; var result = list.reducer((total,current,index,arr,stop)=>{ if(current === " ") stop(); //when called, the loop breaks return total + current; },'hello '); console.log(result);
źródło
Wersja funkcjonalna Reduce with break może być zaimplementowana jako „transform”, np. w podkreśleniu.
Próbowałem zaimplementować go z flagą config, aby go zatrzymać, aby implementacja nie musiała zmieniać struktury danych, z której obecnie korzystasz.
const transform = (arr, reduce, init, config = {}) => { const result = arr.reduce((acc, item, i, arr) => { if (acc.found) return acc acc.value = reduce(config, acc.value, item, i, arr) if (config.stop) { acc.found = true } return acc }, { value: init, found: false }) return result.value } module.exports = transform
Użycie 1, proste
const a = [0, 1, 1, 3, 1] console.log(transform(a, (config, acc, v) => { if (v === 3) { config.stop = true } if (v === 1) return ++acc return acc }, 0))
Użycie2, użyj config jako zmiennej wewnętrznej
const pixes = Array(size).fill(0) const pixProcessed = pixes.map((_, pixId) => { return transform(pics, (config, _, pic) => { if (pic[pixId] !== '2') config.stop = true return pic[pixId] }, '0') })
Użycie3, przechwyć konfigurację jako zmienną zewnętrzną
const thrusts2 = permute([9, 8, 7, 6, 5]).map(signals => { const datas = new Array(5).fill(_data()) const ps = new Array(5).fill(0) let thrust = 0, config do { config = {} thrust = transform(signals, (_config, acc, signal, i) => { const res = intcode( datas[i], signal, { once: true, i: ps[i], prev: acc } ) if (res) { [ps[i], acc] = res } else { _config.stop = true } return acc }, thrust, config) } while (!config.stop) return thrust }, 0)
źródło
Nie możesz wyrwać się z wnętrza
reduce
metody. W zależności od tego, co próbujesz osiągnąć, możesz zmienić końcowy wynik (co jest jednym z powodów, dla których możesz chcieć to zrobić)const result = [1, 1, 1].reduce((a, b) => a + b, 0); // returns 3 console.log(result);
const result = [1, 1, 1].reduce((a, b, c, d) => { if (c === 1 && b < 3) { return a + b + 1; } return a + b; }, 0); // now returns 4 console.log(result);
Pamiętaj: nie możesz bezpośrednio zmienić przypisania parametru tablicy
const result = [1, 1, 1].reduce( (a, b, c, d) => { if (c === 0) { d = [1, 1, 2]; } return a + b; }, 0); // still returns 3 console.log(result);
Jednak (jak wskazano poniżej) MOŻESZ wpłynąć na wynik, zmieniając zawartość tablicy:
const result = [1, 1, 1].reduce( (a, b, c, d) => { if (c === 0) { d[2] = 100; } return a + b; }, 0); // now returns 102 console.log(result);
źródło
d = [1, 1, 2]
sięd[2] = 6
i zobaczyć, co się dzieje. ;-)Kolejna prosta implementacja, którą przyszedłem z rozwiązaniem tego samego problemu:
function reduce(array, reducer, first) { let result = first || array.shift() while (array.length > 0) { result = reducer(result, array.shift()) if (result && result.reduced) { return result.reduced } } return result }
źródło
Jeśli chcesz łączyć obietnice sekwencyjnie z redukcją, korzystając z poniższego wzoru:
return [1,2,3,4].reduce(function(promise,n,i,arr){ return promise.then(function(){ // this code is executed when the reduce loop is terminated, // so truncating arr here or in the call below does not works return somethingReturningAPromise(n); }); }, Promise.resolve());
Ale potrzeba przerwania w zależności od czegoś, co dzieje się wewnątrz lub na zewnątrz obietnicy, sprawy stają się nieco bardziej skomplikowane, ponieważ pętla redukuj jest przerywana przed wykonaniem pierwszej obietnicy, co sprawia, że obcinanie tablicy w wywołaniach zwrotnych obietnicy jest bezużyteczne, skończyłem z następującą implementacją:
function reduce(array, promise, fn, i) { i=i||0; return promise .then(function(){ return fn(promise,array[i]); }) .then(function(result){ if (!promise.break && ++i<array.length) { return reduce(array,promise,fn,i); } else { return result; } }) }
Następnie możesz zrobić coś takiego:
var promise=Promise.resolve(); reduce([1,2,3,4],promise,function(promise,val){ return iter(promise, val); }).catch(console.error); function iter(promise, val) { return new Promise(function(resolve, reject){ setTimeout(function(){ if (promise.break) return reject('break'); console.log(val); if (val==3) {promise.break=true;} resolve(val); }, 4000-1000*val); }); }
źródło
Rozwiązałem to w następujący sposób, na przykład w
some
metodzie, w której zwarcie może dużo zaoszczędzić:const someShort = (list, fn) => { let t; try { return list.reduce((acc, el) => { t = fn(el); console.log('found ?', el, t) if (t) { throw '' } return t }, false) } catch (e) { return t } } const someEven = someShort([1, 2, 3, 1, 5], el => el % 2 === 0) console.log(someEven)
źródło