Как преждевременно прервать метод reduce ()?


94

Как мне прервать итерацию reduce()метода?

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)

Что currentв коде выше? Я не понимаю, как они могут делать то же самое. В любом случае , существуют методы , которые ломаются рано , как some, every,find
elclanrs

someи everyвозвращать логические значения и findвозвращать единственную запись, я хочу выполнить операции для создания памятки. currentэто текущее значение. ссылка
Хулио Маринс

Я имею в виду, что находится currentв первом фрагменте кода?
elclanrs

обновлено, спасибо за ответ
Julio Marins

2
Ответ заключается в том, что вы не можете преждевременно прервать работу reduce, вам придется найти другой способ со встроенными функциями, которые выходят раньше, или создать свой собственный помощник, или использовать lodash или что-то в этом роде. Можете ли вы опубликовать полный пример того, что вы хотите сделать?
elclanrs

Ответы:


94

ОБНОВИТЬ

Некоторые комментаторы отмечают, что исходный массив видоизменяется с целью раннего нарушения .reduce()логики.

Поэтому я немного изменил ответ , добавив .slice(0)перед вызовом следующего .reduce()шага, получив копию исходного массива. ПРИМЕЧАНИЕ . Аналогичные операции, которые выполняют одну и ту же задачу, - это slice()(менее явный) и оператор распространения [...array]( немного менее эффективный ). Имейте в виду, что все это добавляет дополнительный постоянный коэффициент линейного времени к общему времени выполнения + 1 * (O (1)).

Копия служит для защиты исходного массива от возможной мутации, вызывающей исключение из итерации.

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


Старый

Вы МОЖЕТЕ прервать выполнение любой итерации вызова .reduce (), изменив 4-й аргумент функции сокращения: «массив». Нет необходимости в специальной функции уменьшения. См Docs для полного списка .reduce()параметров.

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

Четвертый аргумент - это повторяемый массив .

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

ЗАЧЕМ?:

Единственная причина, по которой я могу использовать это вместо многих других представленных решений, - это если вы хотите сохранить методологию функционального программирования в своем алгоритме и хотите максимально декларативный подход для этого. Если вся ваша цель состоит в том, чтобы буквально УМЕНЬШИТЬ массив до альтернативного примитива, не являющегося ложным (строка, число, логическое значение, символ), то я бы сказал, что это, по сути, лучший подход.

ПОЧЕМУ НЕТ?

Существует целый список аргументов, чтобы НЕ изменять параметры функции, поскольку это плохая практика.


3
+1. Это должен быть принятый ответ. И все же это решение никогда не следует использовать по причинам, указанным в разделе «ПОЧЕМУ НЕТ».
johndodo

3
Это действительно ПЛОХОЙ СОВЕТ, потому что splice выполняет видимую мутацию ( array). Согласно функциональной парадигме вы должны использовать либо стиль передачи с сокращением продолжения, либо ленивую оценку с правоассоциативным сокращением. Или, как более простая альтернатива, просто рекурсия.

Оставайтесь на линии! путем изменения 4-го аргумента функции уменьшения: «массив» не является правильным утверждением. В этом случае это происходит (пример в ответе), потому что он сокращает массив до массива одной длины (первый элемент), в то время как он уже достиг индекса 2 , очевидно, в следующий раз для индекса 3 он не получит элемент для итерации (как вы изменяете исходную ссылку на массив длиной 1 ). В случае, если вы выполните всплывающее сообщение, которое также изменит исходный массив, но не остановится между ними (если вы не находитесь во втором последнем индексе).
Кушик Чаттерджи

@KoushikChatterjee Мое утверждение верно для моего неявного смысла. Это не соответствует вашему явному значению. Вы должны предложить предложение по изменению утверждения, чтобы включить в него ваши замечания, и я внесу правку, так как это улучшит общий ответ.
Товия Рекс

1
Я предпочитаю обращаться к оператору распространения, чтобы избежать нежелательных мутаций, [... array] .reduce ()
eballeste

16

Не используйте сокращение. Просто выполните итерацию по массиву с помощью обычных итераторов (для и т. Д.) И завершите работу, когда ваше условие будет выполнено.


58
где в этом веселье? :)
Александр Миллс

2
@AlexanderMills, наверное, любит быть императором!
dimpiax

3
у этого ответа здесь 0 значение
fedeghe

не уверен, почему это вызвало много положительных отзывов ... это не ответ, так как OP спросил, как рано прервать сокращение (). Это как пойти к врачу, когда у вас немного болит, когда вы наклоняетесь, и врач говорит не наклоняться.
ricosrealm

12

Вы можете использовать такие функции, как some и every, если вам не важно возвращаемое значение. каждый прерывается, когда обратный вызов возвращает false, некоторые, когда он возвращает true:

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

25
Но если он пытается сделать reduceто , по определению , он делает заботиться о возвращаемом значении.

1
@ torazaburo - конечно, но я не вижу, чтобы он использовался в OP, и есть другие способы получить результат. ;-)
RobG

6

Конечно, невозможно добиться reduceпреждевременного выхода встроенной версии .

Но вы можете написать свою собственную версию сокращения, в которой используется специальный токен для определения момента, когда цикл должен быть прерван.

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

Используйте это так, чтобы суммировать массив, но выходите, когда вы нажимаете 99:

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

> 3

1
Вы можете использовать ленивую оценку или CPS для достижения желаемого поведения:
scriptum

Первое предложение этого ответа неверно. Вы можете сломаться, подробности см. В моем ответе ниже.
Товия Рекс

4

Array.every может предоставить очень естественный механизм для выхода из итерации высокого порядка.

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

Вы можете сломать каждый код - и, следовательно, каждую сборку в итераторе - с помощью исключения:

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
Вероятно, это наименее эффективный с точки зрения выполнения из всех ответов. Try / catch прерывает существующий контекст выполнения и возвращается к «медленному пути» выполнения. Попрощайтесь с любыми оптимизациями, которые V8 делает незаметно.
Эван Плейс

5
Не достаточно экстремально. Как насчет этого:if (current <= 0) window.top.close()
user56reinstatemonica8

0

Поскольку аргументы promises имеют resolveи rejectобратного вызова, я создал reduceфункцию обхода с breakаргументом обратного вызова. Он принимает все те же аргументы, что и собственный reduceметод, за исключением того, что первый - это массив для работы (избегайте исправлений обезьяны). Третий [2] initialValueаргумент не обязателен. См. Фрагмент functionредуктора ниже.

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

А вот модифицированный скрипт reducerв виде массива 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

Уменьшенная функциональная версия с разрывом может быть реализована как «преобразование», например. в подчеркивании.

Я попытался реализовать его с помощью флага конфигурации, чтобы остановить его, чтобы сокращение реализации не изменяло структуру данных, которую вы сейчас используете.

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, простой

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, используйте config как внутреннюю переменную

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, захват конфигурации как внешней переменной

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

Вы не можете вырваться изнутри reduceметода. В зависимости от того, чего вы пытаетесь достичь, вы можете изменить окончательный результат (это одна из причин, по которой вы можете захотеть это сделать)

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

Имейте в виду: вы не можете напрямую переназначить параметр массива

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

Однако (как указано ниже) вы МОЖЕТЕ повлиять на результат, изменив содержимое массива:

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: « Вы не можете напрямую изменять значения аргументов таким образом, чтобы это повлияло на последующие вычисления », это не так. ECMA-262 говорит: Если существующие элементы массива изменены, их значение, переданное в callbackfn, будет значением на момент их посещений с помощью сокращения . Ваш пример не работает, потому что вы присваиваете новое значение d , а не изменяете исходный массив. Замените d = [1, 1, 2]на d[2] = 6и посмотрите, что произойдет. ;-)
RobG 03

-1

Еще одна простая реализация, с которой я пришел, для решения той же проблемы:

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

Если вы хотите связать обещания последовательно с сокращением, используя шаблон ниже:

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

Но при необходимости прерывания в зависимости от того, что происходит внутри или вне обещания, все становится немного сложнее, потому что цикл уменьшения завершается до выполнения первого обещания, что делает усечение массива в обратных вызовах обещания бесполезным, я закончил с этой реализацией:

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

Тогда вы можете сделать что-то вроде этого:

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

Я решил это следующим образом, например, в someметоде, где короткое замыкание может значительно сэкономить:

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)

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.