Jak skorzystać z metody Early Break Redukcja ()?


97

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)

Co zawiera currentpowyższy kod? Nie rozumiem, jak mogą zrobić to samo. W każdym razie istnieją metody, które rozkładają się na początku jak some, every,find
elclanrs

somei everyzwracają wartości logiczne i findzwracają pojedynczy rekord, chcę wykonać operacje w celu wygenerowania notatki. currentjest currentValue. odniesienie
Julio Marins

Mam na myśli to, co jest currentw pierwszym fragmencie kodu?
elclanrs

zaktualizowane, dzięki za odpowiedź
Julio Marins

2
Odpowiedź brzmi: nie możesz zerwać wcześniej 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ć?
elclanrs

Odpowiedzi:


97

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.


3
+1. To powinna być akceptowana odpowiedź. A jednak tego rozwiązania nigdy nie należy stosować, z powodów podanych w sekcji „DLACZEGO NIE”.
johndodo,

3
To naprawdę ZŁA RADA, ponieważ splicewykonuje 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.

Czekaj! poprzez mutację czwartego argumentu funkcji redukuj: „tablica” nie jest poprawną instrukcją. W tym przypadku dzieje się tak (przykład w odpowiedzi), ponieważ przycina tablicę do tablicy o pojedynczej długości (pierwszy element), podczas gdy jej indeks już osiągnął 2 , oczywiście następnym razem, dla indeksu 3 nie otrzyma elementu do iteracji (jak modyfikujesz oryginalne odniesienie do tablicy o długości 1 ). W przypadku, gdy wykonasz pop, który również spowoduje mutację tablicy źródłowej, ale nie zatrzyma się pomiędzy (jeśli nie jesteś na przedostatnim indeksie).
Koushik Chatterjee

@KoushikChatterjee Moje stwierdzenie jest zgodne z moim dorozumianym znaczeniem. To nie jest poprawne dla twojego wyraźnego znaczenia. Powinieneś zasugerować modyfikację oświadczenia, aby zawierało twoje punkty, a ja dokonam zmiany, ponieważ poprawiłaby to ogólną odpowiedź.
Tobiah Rex,

1
Wolę sięgać po operator spreadu, aby uniknąć niepożądanych mutacji, [... array] .reduce ()
eballeste

19

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.


60
gdzie jest w tym zabawa? :)
Alexander Mills,

2
@AlexanderMills prawdopodobnie lubi być imperatorem!
dimpiax

3
ta odpowiedź ma tutaj wartość 0
fedeghe

nie jestem pewien, dlaczego otrzymało to kilka pozytywnych głosów ... to jest brak odpowiedzi, ponieważ OP zapytał, jak wcześnie zerwać z redukcją () .. To tak, jakby iść do lekarza, gdy odczuwasz ból, gdy się pochylasz, a lekarz mówi nie schylać się.
ricosrealm

15

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

26
Ale jeśli on próbuje zrobić reducewtedy z definicji, że nie dbają o wartości zwracanej.

1
@ torazaburo - jasne, ale nie widzę, aby było używane w PO i są inne sposoby uzyskania wyniku. ;-)
RobG

6

Oczywiście nie ma sposobu, aby wbudowana wersja programu reducezakoń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

1
Możesz użyć leniwej oceny lub CPS, aby osiągnąć pożądane zachowanie:
scriptum

Pierwsze zdanie tej odpowiedzi jest nieprawidłowe. Możesz złamać, zobacz moją odpowiedź poniżej, aby uzyskać szczegółowe informacje.
Tobiah Rex

4

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


1

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
}

6
Jest to prawdopodobnie najmniej wydajna odpowiedź ze wszystkich odpowiedzi. Try / catch przerywa istniejący kontekst wykonywania i wraca do „wolnej ścieżki” wykonywania. Pożegnaj się z optymalizacjami, które V8 robi pod osłonami.
Evan Plaice,

5
Nie dość ekstremalne. Co powiesz na to:if (current <= 0) window.top.close()
user56reinstatemonica8

0

Ponieważ argumenty promises mają resolvei rejectcallback, utworzyłem funkcję reduceobejścia z breakargumentem callback. Pobiera wszystkie te same argumenty, co reducemetoda natywna , z wyjątkiem tego, że pierwszy z nich jest tablicą do pracy (unikaj małpiego łatania). Trzeci initialValueargument [2] jest opcjonalny. Zobacz poniższy fragment kodu functionreduktora.

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

0

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)

0

Nie możesz wyrwać się z wnętrza reducemetody. 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);


1
Re: „ Nie można bezpośrednio modyfikować wartości argumentów w sposób, który wpływa na późniejsze obliczenia ”, to nieprawda. ECMA-262 mówi: Jeśli istniejące elementy tablicy zostaną zmienione, ich wartość przekazana do callbackfn będzie wartością w momencie redukcji odwiedzin . Twój przykład nie działa, ponieważ przypisujesz nową wartość do d , nie modyfikując oryginalnej tablicy. Wymień d = [1, 1, 2]się d[2] = 6i zobaczyć, co się dzieje. ;-)
RobG

-1

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
}

-1

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

-1

Rozwiązałem to w następujący sposób, na przykład w somemetodzie, 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)

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.