Як достроково скоротити метод зменшення ()?


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

оновлено, дякую за відповідь
Хуліо Марінс

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

Відповіді:


93

ОНОВЛЕННЯ

Деякі коментатори твердо переконують, що початковий масив мутується, щоб рано пробитися до .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-й аргумент функції зменшення: "масив". Не потрібно користувацької функції зменшення. Повний список параметрів див. У Документах.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 ). У випадку, якщо ви виконуєте сплеск, який також буде мутувати вихідний масив, але не зупинятиметься між ними (якщо ви не в другому останньому індексі).
Koushik Chatterjee

@KoushikChatterjee Моє твердження відповідає моєму неявному значенню. Це неправильно для вашого явного значення. Ви повинні запропонувати пропозицію щодо модифікації висловлення, щоб включити ваші пункти, і я внесу редакцію, оскільки це покращить загальну відповідь.
Tobiah Rex

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

16

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


58
де в цьому веселощі? :)
Alexander Mills

2
@AlexanderMills, мабуть, йому подобається бути імператором!
dimpiax

3
ця відповідь має значення 0
fedeghe

не впевнений, чому це отримало багато голосів "за" ... це не відповідь, оскільки ОП запитав, як достроково перерватися від зменшення () .. Це все одно, що звернутися до лікаря, коли у вас болить, коли ви нахиляєтесь, і лікар каже щоб ти не нахилявся.
ricosrealm

12

Ви можете використовувати такі функції , як деякі і кожен до тих пір , поки ви не дбаєте про возвращаемом значенні. кожна перерва, коли зворотний виклик повертає 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метод, за винятком того, що перший - це масив, над яким працює (уникайте виправлення мавп). Третій initialValueаргумент [2] необов’язковий. Дивіться фрагмент 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

Функція зменшення функціональної версії з перервою може бути реалізована як "перетворення", напр. підкреслення.

Я спробував реалізувати його за допомогою прапорця config, щоб зупинити його, щоб зменшення реалізації не повинно змінювати структуру даних, яку ви зараз використовуєте.

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

Використання1, просте

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

Використання2, використовуйте 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')
})

Використання3, захоплення конфігурації як зовнішньої змінної

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

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