¿Cómo romper temprano el método reduce ()?


94

¿Cómo puedo romper la iteración del reduce()método?

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)

¿Qué hay currenten el código de arriba? No veo cómo estos pueden hacer lo mismo. En cualquier caso, hay métodos que rompen principios como some, every,find
elclanrs

somey everydevolver booleanos y finddevolver un solo registro, lo que quiero es ejecutar operaciones para generar una nota. currentes el valor actual. referencia
Julio Marins

Quiero decir, ¿qué hay currenten la primera parte del código?
elclanrs

actualizado, gracias por la respuesta
Julio Marins

2
La respuesta es que no puede romper antes de tiempo reduce, tendrá que encontrar otra forma con funciones integradas que salgan temprano o creen su propio ayudante, o usen lodash o algo así. ¿Puedes publicar un ejemplo completo de lo que quieres hacer?
elclanrs

Respuestas:


94

ACTUALIZAR

Algunos de los comentaristas hacen un buen punto de que la matriz original está siendo mutada para romper temprano dentro de la .reduce()lógica.

Por lo tanto, modifiqué ligeramente la respuesta agregando un .slice(0)antes de llamar a un .reduce()paso de seguimiento , obteniendo una copia de la matriz original. NOTA : Operaciones similares que logran la misma tarea son slice()(menos explícitas) y operador de propagación [...array](un poco menos de rendimiento ). Tenga en cuenta que todos estos agregan un factor constante adicional de tiempo lineal al tiempo de ejecución general + 1 * (O (1)).

La copia sirve para preservar la matriz original de la eventual mutación que causa la expulsión de la iteración.

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


ANTIGUO

PUEDE interrumpir en cualquier iteración de una invocación .reduce () mutando el cuarto argumento de la función de reducción: "matriz". No se necesita una función de reducción personalizada. Consulte Documentos para obtener una lista completa de .reduce()parámetros.

Array.prototype.reduce ((acc, curr, i, array))

El cuarto argumento es la matriz sobre la que se itera.

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

¿POR QUÉ?:

La única razón por la que puedo pensar en usar esto en lugar de las muchas otras soluciones presentadas es si desea mantener una metodología de programación funcional para su algoritmo y desea el enfoque más declarativo posible para lograrlo. Si todo su objetivo es REDUCIR literalmente una matriz a una primitiva alternativa que no sea falsey (cadena, número, booleano, símbolo), entonces yo diría que esto ES, de hecho, el mejor enfoque.

¿POR QUÉ NO?

Hay una lista completa de argumentos para NO modificar los parámetros de la función, ya que es una mala práctica.


3
+1. Esta debería ser la respuesta aceptada. Y, sin embargo, esta solución nunca debe utilizarse, por las razones indicadas en "POR QUÉ NO".
johndodo

3
Este es realmente un MAL CONSEJO, porque splicerealiza una mutación visible ( array). De acuerdo con el paradigma funcional, usaría una reducción en el estilo de aprobación de continuación o utilizaría una evaluación perezosa con una reducción asociativa a la derecha. O, como alternativa más simple, simplemente recursividad.

¡Espere! mutando el cuarto argumento de la función de reducción: "matriz" no es una declaración correcta. En este caso, está sucediendo (el ejemplo en la respuesta) porque está cortando la matriz a una matriz de longitud única (primer elemento) mientras que ya alcanzó el índice 2 , obviamente la próxima vez, para el índice 3 no obtendrá un elemento para iterar (como está mutando la referencia original a la matriz de longitud 1 ). En caso de que realice un pop que también mutará la matriz de origen pero no se detendrá en el medio (si no está en el penúltimo índice).
Koushik Chatterjee

@KoushikChatterjee Mi declaración es correcta para mi significado implícito. No es correcto para su significado explícito. Debe ofrecer una sugerencia sobre la modificación de la declaración para incluir sus puntos y haré la edición ya que mejoraría la respuesta general.
Tobiah Rex

1
Prefiero utilizar el operador de propagación para evitar mutaciones no deseadas, [... array] .reduce ()
eballeste

16

No use reducir. Simplemente repita la matriz con iteradores normales (para, etc.) y salga cuando se cumpla su condición.


58
¿Dónde está la diversión en esto? :)
Alexander Mills

2
@AlexanderMills probablemente le guste ser un imperator!
dimpiax

3
esta respuesta tiene valor 0 aquí
fedeghe

no estoy seguro de por qué esto obtuvo tantos votos a favor ... esto no es una respuesta, ya que el OP preguntó cómo salir temprano de una reducción (). que no te inclines.
ricosrealm

12

Puede utilizar funciones como cierta y cada todo el tiempo que no se preocupan por el valor de retorno. todos se rompen cuando la devolución de llamada devuelve falso, algunos cuando devuelve verdadero:

things.every(function(v, i, o) {
  // do stuff 
  if (timeToBreak) {
    return false;
  } else {
    return true;
  }
}, thisArg);

25
Pero si él está tratando de hacer reduceentonces, por definición, que no se preocupan por el valor de retorno.

1
@ torazaburo: claro, pero no veo que se use en el OP y hay otras formas de obtener un resultado. ;-)
RobG

6

Por supuesto, no hay forma de que la versión incorporada de reducesalga antes de tiempo.

Pero puede escribir su propia versión de reduce, que usa un token especial para identificar cuándo debe romperse el bucle.

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

Úselo así, para sumar una matriz pero salga cuando llegue a 99:

reduce([1, 2, 99, 3], (a, b) => b === 99 ? EXIT_REDUCE : a + b, 0);

> 3

1
Puede utilizar la evaluación perezosa o CPS para lograr el comportamiento deseado:
scriptum

La primera oración de esta respuesta es incorrecta. Puede romper, vea mi respuesta a continuación para obtener más detalles.
Tobiah Rex

4

Array.every puede proporcionar un mecanismo muy natural para romper la iteración de alto orden.

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

Puede romper cada código, y por lo tanto cada compilación en el iterador, lanzando una excepción:

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
Esta es probablemente la ejecución menos eficiente de todas las respuestas. Try / catch rompe el contexto de ejecución existente y vuelve a la 'ruta lenta' de ejecución. Diga adiós a las optimizaciones que V8 hace bajo las sábanas.
Evan Plaice

5
No lo suficientemente extremo. ¿Qué tal esto:if (current <= 0) window.top.close()
user56reinstatemonica8

0

Como los argumentos promisetienen resolvey de rejectdevolución de llamada, creé la reducefunción de solución alternativa con el breakargumento de devolución de llamada. Toma todos los mismos argumentos que el reducemétodo nativo , excepto que el primero es una matriz en la que trabajar (evite parches de mono). El tercer initialValueargumento [2] es opcional. Consulte el fragmento a continuación para ver el functionreductor.

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

Y aquí está el script modificado reducercomo una matriz 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

Reducir la versión funcional con ruptura se puede implementar como 'transformar', ej. en subrayado.

Intenté implementarlo con un indicador de configuración para detenerlo, de modo que la implementación reduce no tenga que cambiar la estructura de datos que está utilizando actualmente.

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

Usage1, simple

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

Usage2, use config como variable interna

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

Usage3, captura de configuración como variable externa

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

No se puede romper desde el interior de un reducemétodo. Dependiendo de lo que esté tratando de lograr, podría alterar el resultado final (que es una de las razones por las que puede querer hacer esto)

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

Tenga en cuenta: no puede reasignar el parámetro de matriz directamente

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

Sin embargo (como se señala a continuación), PUEDE afectar el resultado cambiando el contenido de la matriz:

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 " No puede mutar los valores del argumento directamente de una manera que afecte los cálculos posteriores ", eso no es cierto. ECMA-262 dice: Si se cambian los elementos existentes de la matriz, su valor tal como se pasa a callbackfn será el valor en el momento en que reduzca las visitas . Su ejemplo no funciona porque está asignando un nuevo valor ad , sin modificar la matriz original. Reemplazar d = [1, 1, 2]con d[2] = 6y ver qué pasa. ;-)
RobG

-1

Otra implementación simple con la que vine resolviendo el mismo problema:

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

Si desea encadenar promesas secuencialmente con reducir utilizando el patrón a continuación:

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

Pero si es necesario romper de acuerdo con algo que sucede dentro o fuera de una promesa, las cosas se vuelven un poco más complicadas porque el ciclo de reducción se termina antes de que se ejecute la primera promesa, lo que hace que truncar la matriz en las devoluciones de llamada de promesa sea inútil, terminé con esta implementación:

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

Entonces puedes hacer algo como esto:

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

Lo resolví de la siguiente manera, por ejemplo, en el somemétodo donde el cortocircuito puede ahorrar mucho:

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)

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.