Bagaimana metode early break reduce ()?

94

Bagaimana cara menghentikan pengulangan reduce()metode?

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)
Julio Marins
sumber
Apa yang ada currentdi kode di atas? Saya tidak melihat bagaimana ini dapat melakukan hal yang sama. Dalam setiap kasus ada metode yang melanggar awal seperti some, every,find
elclanrs
somedan everymengembalikan boolean dan findmengembalikan satu catatan, yang saya inginkan adalah menjalankan operasi untuk menghasilkan memo. currentadalah the currentValue. referensi
Julio Marins
Maksud saya, apa yang ada currentdi bagian pertama kode?
elclanrs
diperbarui, terima kasih atas balasannya
Julio Marins
2
Jawabannya adalah Anda tidak bisa memutuskan lebih awal reduce, Anda harus mencari cara lain dengan fungsi bawaan yang keluar lebih awal atau membuat pembantu Anda sendiri, atau menggunakan lodash atau semacamnya. Bisakah Anda memposting contoh lengkap tentang apa yang ingin Anda lakukan?
elclanrs

Jawaban:

94

MEMPERBARUI

Beberapa komentator membuat poin yang baik bahwa array asli sedang dimutasi untuk memutuskan awal di dalam .reduce()logika.

Oleh karena itu, saya telah memodifikasi jawabannya sedikit dengan menambahkan .slice(0)sebelum memanggil langkah lanjutan .reduce(), menghasilkan salinan dari larik asli. CATATAN : Operasi serupa yang menyelesaikan tugas yang sama adalah slice()(kurang eksplisit), dan operator penyebaran [...array]( sedikit kurang berkinerja ). Ingat, semua ini menambahkan faktor konstanta tambahan waktu linier ke runtime keseluruhan + 1 * (O (1)).

Salinan, berfungsi untuk mempertahankan larik asli dari mutasi yang akhirnya menyebabkan pengusiran dari iterasi.

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


TUA

Anda BISA menghentikan iterasi apa pun dari pemanggilan .reduce () dengan memutasikan argumen ke-4 dari fungsi pengurangan: "array". Tidak perlu fungsi pengurangan khusus. Lihat Dokumen untuk daftar lengkap .reduce()parameter.

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

Argumen ke-4 adalah array yang diiterasi.

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

MENGAPA?:

Satu-satunya alasan saya dapat memikirkan untuk menggunakan ini daripada banyak solusi lain yang disajikan adalah jika Anda ingin mempertahankan metodologi pemrograman fungsional untuk algoritme Anda, dan Anda ingin pendekatan yang paling deklaratif untuk mencapai itu. Jika seluruh tujuan Anda adalah untuk benar-benar MENGURANGI array menjadi primitif non-falsey alternatif (string, angka, boolean, Simbol) maka saya akan berpendapat bahwa IS ini sebenarnya, pendekatan terbaik.

KENAPA TIDAK?

Ada seluruh daftar argumen yang harus dibuat untuk TIDAK mengubah parameter fungsi karena ini praktik yang buruk.

Tobiah Rex
sumber
3
+1. Ini harus menjadi jawaban yang diterima. Namun solusi ini tidak boleh digunakan, karena alasan yang dinyatakan di bawah "MENGAPA TIDAK".
johndodo
3
Ini benar-benar SARAN BURUK, karena splicemelakukan mutasi yang terlihat ( array). Menurut paradigma fungsional Anda akan menggunakan pengurangan dalam gaya penerusan lanjutan atau menggunakan evaluasi malas dengan pengurangan asosiatif kanan. Atau, sebagai alternatif yang lebih sederhana, rekursi biasa saja.
Tahan! dengan memutasikan argumen ke-4 dari fungsi pengurangan: "array" bukanlah pernyataan yang benar. Dalam hal ini terjadi (contoh di jawaban) karena memotong array ke array panjang tunggal (elemen pertama) sementara itu sudah mencapai indeks 2 , jelas lain kali, untuk indeks 3 tidak akan mendapatkan item untuk iterasi (seperti Anda memutasikan referensi asli ke larik dengan panjang 1 ). Jika Anda melakukan pop yang juga akan mengubah array sumber juga tetapi tidak berhenti di antaranya (jika Anda tidak berada di indeks terakhir kedua).
Koushik Chatterjee
@KikikteaPernyataan saya benar untuk arti tersirat saya. Itu tidak benar untuk arti eksplisit Anda. Anda harus menawarkan saran untuk mengubah pernyataan untuk memasukkan poin Anda dan saya akan mengeditnya karena itu akan meningkatkan jawaban keseluruhan.
Tobiah Rex
1
Saya lebih suka menjangkau operator penyebaran untuk menghindari mutasi yang tidak diinginkan, [... array] .reduce ()
eballeste
16

Jangan gunakan pengurangan. Lakukan iterasi pada larik dengan iterator normal (untuk, dll) dan hancurkan ketika kondisi Anda terpenuhi.

AndroidDev
sumber
58
dimana kesenangannya? :)
Alexander Mills
2
@AlexanderMills mungkin dia suka menjadi seorang imperator!
dimpiax
3
jawaban ini memiliki nilai 0 di sini
fedeghe
tidak yakin mengapa ini mendapat banyak suara positif ... ini bukan jawaban karena OP bertanya bagaimana cara istirahat lebih awal dari pengurangan () .. Ini seperti pergi ke dokter ketika Anda merasa sakit ketika Anda membungkuk dan dokter memberi tahu Anda tidak membungkuk.
ricosrealm
12

Anda dapat menggunakan fungsi seperti some dan every selama Anda tidak peduli dengan nilai yang dikembalikan. setiap jeda saat callback mengembalikan false, beberapa saat mengembalikan true:

things.every(function(v, i, o) {
  // do stuff 
  if (timeToBreak) {
    return false;
  } else {
    return true;
  }
}, thisArg);
RobG
sumber
25
Tetapi jika ia sedang mencoba untuk melakukan reducemaka dengan definisi dia tidak peduli tentang nilai kembali.
1
@ Torazaburo — tentu, tapi saya tidak melihatnya digunakan di OP dan ada cara lain untuk mendapatkan hasil. ;-)
RobG
6

Tidak ada cara, tentu saja, untuk membuat versi reducebawaan keluar sebelum waktunya.

Tapi Anda bisa menulis versi reduce Anda sendiri yang menggunakan token khusus untuk mengidentifikasi kapan loop harus diputus.

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

Gunakan seperti ini, untuk menjumlahkan larik tetapi keluar saat Anda menekan 99:

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

> 3

sumber
1
Anda dapat menggunakan evaluasi malas atau CPS untuk mencapai perilaku yang diinginkan:
scriptum
Kalimat pertama dari jawaban ini salah. Anda bisa istirahat, lihat jawaban saya di bawah untuk detailnya.
Tobiah Rex
4

Array.every dapat menyediakan mekanisme yang sangat alami untuk memecahkan iterasi orde tinggi.

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

Doug Coburn
sumber
1

Anda dapat memecahkan setiap kode - dan juga setiap build di iterator - dengan membuat pengecualian:

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
}
Koudela
sumber
6
Ini mungkin eksekusi yang paling tidak efisien dari semua jawaban. Try / catch memutus konteks eksekusi yang ada dan kembali ke 'jalur lambat' eksekusi. Ucapkan selamat tinggal pada pengoptimalan apa pun yang dilakukan V8 secara tersembunyi.
Evan Plaice
5
Tidak cukup ekstrim. Bagaimana dengan ini:if (current <= 0) window.top.close()
user56reinstatemonica8
0

Seperti yang promisedimiliki resolvedan rejectargumen callback, saya membuat reducefungsi solusi dengan breakargumen callback. Ini mengambil semua argumen yang sama sebagai reducemetode native , kecuali yang pertama adalah array untuk dikerjakan (hindari monkey patching). initialValueArgumen [2] ketiga adalah opsional. Lihat cuplikan di bawah untuk functionperedam.

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

Dan inilah skrip yang dimodifikasi reducersebagai Array 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);
Paweł
sumber
0

Kurangi versi fungsional dengan jeda dapat diimplementasikan sebagai 'transformasi', mis. di garis bawah.

Saya mencoba mengimplementasikannya dengan config flag untuk menghentikannya sehingga implementasi reduce tidak harus mengubah struktur data yang sedang Anda gunakan.

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, sederhana

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, gunakan config sebagai variabel internal

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, tangkap config sebagai variabel eksternal

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)
windmaomao.dll
sumber
0

Anda tidak bisa keluar dari dalam suatu reducemetode. Bergantung pada apa yang ingin Anda capai, Anda dapat mengubah hasil akhirnya (yang merupakan salah satu alasan Anda mungkin ingin melakukan ini)

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

Ingat: Anda tidak dapat menetapkan ulang parameter array secara langsung

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

Namun (seperti yang ditunjukkan di bawah), Anda DAPAT memengaruhi hasilnya dengan mengubah konten larik:

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

Erik Waters
sumber
1
Re " Anda tidak dapat mengubah nilai argumen secara langsung dengan cara yang mempengaruhi perhitungan selanjutnya ", itu tidak benar. ECMA-262 mengatakan: Jika elemen array yang ada diubah, nilainya saat diteruskan ke callbackfn akan menjadi nilai pada saat mengurangi kunjungan . Contoh Anda tidak berfungsi karena Anda menetapkan nilai baru ke d , tidak mengubah larik asli. Ganti d = [1, 1, 2]dengan d[2] = 6dan lihat apa yang terjadi. ;-)
RobG
-1

Implementasi sederhana lainnya yang saya dapatkan dengan menyelesaikan masalah yang sama:

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
}
alun
sumber
-1

Jika Anda ingin merangkai promise secara berurutan dengan mengurangi menggunakan pola di bawah ini:

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

Tetapi perlu memutuskan sesuai dengan sesuatu yang terjadi di dalam atau di luar janji, hal-hal menjadi sedikit lebih rumit karena pengurangan loop diakhiri sebelum janji pertama dieksekusi, membuat pemotongan array dalam panggilan balik janji tidak berguna, saya berakhir dengan implementasi ini:

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

Kemudian Anda dapat melakukan sesuatu seperti ini:

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);
  });
}
luxigo
sumber
-1

Saya menyelesaikannya seperti berikut, misalnya dalam somemetode di mana korsleting dapat menghemat banyak:

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)

fedeghe.dll
sumber