Como interromper o método de redução () de quebra antecipada?


94

Como posso interromper a iteração do 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)

O que está currentno código acima? Não vejo como eles podem fazer a mesma coisa. Em qualquer caso, existem métodos que quebram cedo some, como every,find
elclanrs

somee everyretornar booleanos e findretornar um único registro, o que eu quero é executar operações para gerar um memo. currenté o valor atual. referência
Julio Marins

Quero dizer, o que está currentna primeira parte do código?
elclanrs

atualizado, obrigado pela resposta
Julio Marins

2
A resposta é que você não pode interromper cedo reduce, você terá que encontrar outra maneira com funções embutidas que saem mais cedo ou criam seu próprio ajudante, ou usam lodash ou algo assim. Você pode postar um exemplo completo do que deseja fazer?
elclanrs

Respostas:


94

ATUALIZAR

Alguns dos comentaristas afirmam que o array original está sofrendo mutação para quebrar logo no início da .reduce()lógica.

Portanto, modifiquei ligeiramente a resposta adicionando um .slice(0)antes de chamar uma .reduce()etapa de acompanhamento , produzindo uma cópia do array original. NOTA : Operações semelhantes que realizam a mesma tarefa são slice()(menos explícitas) e operador de propagação [...array]( desempenho ligeiramente inferior ). Lembre-se de que tudo isso adiciona um fator constante adicional de tempo linear ao tempo de execução geral + 1 * (O (1)).

A cópia serve para preservar a matriz original da eventual mutação que causa a ejeção da iteração.

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


VELHO

Você PODE interromper qualquer iteração de uma invocação .reduce () alterando o 4º argumento da função de redução: "array". Não há necessidade de uma função de redução personalizada. Veja Docs para uma lista completa de .reduce()parâmetros.

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

O quarto argumento é a matriz que está sendo iterada.

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

PORQUE?:

A única razão pela qual posso pensar em usar isso em vez de muitas outras soluções apresentadas é se você deseja manter uma metodologia de programação funcional para seu algoritmo e deseja a abordagem mais declarativa possível para fazer isso. Se todo o seu objetivo é literalmente REDUZIR um array a um primitivo não falsey alternativo (string, número, booleano, símbolo), então eu diria que isso é, de fato, a melhor abordagem.

POR QUE NÃO?

Há uma lista completa de argumentos para NÃO alterar os parâmetros da função, pois é uma prática ruim.


3
+1. Esta deve ser a resposta aceita. E, no entanto, esta solução nunca deve ser usada, pelos motivos indicados em "POR QUE NÃO"
johndodo de

3
Este é um conselho realmente RUIM, porque splicerealiza uma mutação visível ( array). De acordo com o paradigma funcional, você usaria uma redução no estilo de aprovação de continuação ou utilizaria a avaliação preguiçosa com uma redução associativa à direita. Ou, como uma alternativa mais simples, simplesmente recursão.

Aguente! alterando o quarto argumento da função de redução: "array" não é uma declaração correta. Neste caso, está acontecendo (o exemplo da resposta) porque está cortando a matriz em uma matriz de comprimento único (primeiro elemento) enquanto já atingiu o índice 2 , obviamente da próxima vez, para o índice 3 não obterá um item para iterar (como você está alterando a referência original para a matriz de comprimento 1 ). No caso de você executar um pop que também irá alterar a matriz de origem, mas não para no meio (se você não estiver no penúltimo índice).
Koushik Chatterjee

@KoushikChatterjee Minha declaração está correta para o meu significado implícito. Não é correto para o seu significado explícito. Você deve oferecer uma sugestão sobre a modificação da declaração para incluir seus pontos e eu farei a edição, pois isso melhoraria a resposta geral.
Tobiah Rex

1
Eu prefiro alcançar o operador de propagação para evitar quaisquer mutações indesejadas, [... array] .reduce ()
eballeste

16

Não use reduzir. Apenas itere na matriz com iteradores normais (para, etc) e interrompa quando sua condição for atendida.


58
onde está a diversão nisso? :)
Alexander Mills

2
@AlexanderMills provavelmente ele gosta de ser um imperador!
dimpiax

3
esta resposta tem valor 0 aqui
fedeghe

não sei por que isso teve tantos votos positivos ... isso não é uma resposta, já que o OP perguntou como interromper precocemente uma redução () .. É como ir ao médico quando você tem alguma dor quando se inclina e o médico diz você para não se curvar.
ricosrealm

12

Você pode usar funções como some e every , desde que não se importe com o valor de retorno. todos são interrompidos quando o retorno de chamada retorna falso, alguns quando retorna verdadeiro:

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

25
Mas se ele está tentando fazer reduceisso, por definição, ele se preocupa com o valor de retorno.

1
@ torazaburo — claro, mas não o vejo sendo usado no OP e há outras maneiras de obter um resultado. ;-)
RobG

6

Obviamente, não há como obter a versão integrada do reduce saia prematuramente.

Mas você pode escrever sua própria versão de reduzir, que usa um token especial para identificar quando o loop deve ser interrompido.

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

Use-o assim, para somar uma matriz, mas saia quando chegar a 99:

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

> 3

1
Você pode usar a avaliação preguiçosa ou CPS para atingir o comportamento desejado:
scriptum

A primeira frase desta resposta está incorreta. Você pode quebrar, veja minha resposta abaixo para detalhes.
Tobiah Rex

4

Array.every pode fornecer um mecanismo muito natural para interromper a iteração de alta ordem.

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

Você pode quebrar cada código - e, portanto, cada compilação no iterador - lançando uma exceção:

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 é provavelmente a execução menos eficiente de todas as respostas. Try / catch quebra o contexto de execução existente e retorna ao 'caminho lento' de execução. Diga adeus a todas as otimizações que o V8 faz nos bastidores.
Evan Plaice

5
Não extremo o suficiente. Que tal:if (current <= 0) window.top.close()
user56reinstatemonica8

0

Como os promisetêm resolvee os rejectargumentos de retorno de chamada, criei a reducefunção de solução alternativa com o breakargumento de retorno de chamada. Leva os mesmos argumentos que o reducemétodo nativo , exceto o primeiro é um array para trabalhar (evite o patching do macaco). O terceiro [2] initialValueargumento é opcional. Veja o snippet abaixo para o functionredutor.

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

E aqui está o script reducercomo um Array methodmodificado:

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

A versão funcional de redução com quebra pode ser implementada como 'transformar', ex. em sublinhado.

Tentei implementá-lo com um sinalizador de configuração para interrompê-lo, de modo que a redução da implementação não precise alterar a estrutura de dados que você está usando no momento.

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

Uso1, simples

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 variável 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 configuração como variável 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

Você não pode quebrar de dentro de um reducemétodo. Dependendo do que você está tentando realizar, você pode alterar o resultado final (que é uma razão pela qual você pode querer fazer isso)

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

Lembre-se: você não pode reatribuir o parâmetro de matriz diretamente

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

No entanto (conforme indicado abaixo), você PODE afetar o resultado alterando o conteúdo da 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 " Você não pode alterar os valores do argumento diretamente de uma forma que afete cálculos subsequentes ", isso não é verdade. ECMA-262 diz: Se os elementos existentes da matriz forem alterados, seu valor conforme passado para callbackfn será o valor no momento em que a redução os visita . Seu exemplo não funciona porque você está atribuindo um novo valor a d , não modificando o array original. Substitua d = [1, 1, 2]por d[2] = 6e veja o que acontece. ;-)
RobG

-1

Outra implementação simples que recebi para resolver o mesmo 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

Se você deseja encadear promessas sequencialmente com redução usando o padrão abaixo:

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

Mas precisa quebrar de acordo com algo acontecendo dentro ou fora de uma promessa, as coisas se tornam um pouco mais complicadas porque o loop de redução é encerrado antes que a primeira promessa seja executada, tornando inútil o truncamento da matriz nos retornos de chamada da promessa. Acabei com esta implementação:

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

Então você pode fazer algo assim:

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

Resolvi da seguinte maneira, por exemplo, no somemétodo em que um curto-circuito pode economizar muito:

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)

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.