วิธีการลด () ในช่วงต้นต้องทำอย่างไร?


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คือ currentValue อ้างอิง
Julio Marins

ฉันหมายถึงอะไร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 ของฟังก์ชันลด: "array" ไม่จำเป็นต้องมีฟังก์ชันลดขนาดที่กำหนดเอง ดูเอกสารสำหรับรายการ.reduce()พารามิเตอร์ทั้งหมด

Array.prototype.reduce ((acc, curr, i, อาร์เรย์))

อาร์กิวเมนต์ที่ 4 คืออาร์เรย์ที่ถูกทำซ้ำ

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 เขาอาจจะชอบเป็น imperator!
dimpiax

3
คำตอบนี้มีค่า 0 ที่นี่
fedeghe

ไม่แน่ใจว่าทำไมถึงได้รับการโหวตเพิ่มขึ้นมากมาย ... นี่ไม่ใช่คำตอบเนื่องจาก OP ถามถึงวิธีการลดต้นจากการลด () .. เหมือนไปหาหมอเมื่อคุณมีอาการปวดเมื่อคุณก้มลงและแพทย์บอก คุณจะไม่งอ
ricosrealm

12

คุณสามารถใช้ฟังก์ชั่นเช่นบางคนและทุกคนตราบใดที่คุณไม่สนใจเกี่ยวกับค่าตอบแทน ทุกครั้งที่หยุดเมื่อการโทรกลับส่งคืนเท็จบางครั้งเมื่อส่งคืนจริง:

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

ประโยคแรกของคำตอบนี้ไม่ถูกต้อง คุณสามารถทำลายดูคำตอบของฉันด้านล่างสำหรับรายละเอียด
Tobiah Rex

4

Array ทุกคนสามารถให้กลไกที่เป็นธรรมชาติในการทำลายการทำซ้ำที่มีลำดับสูง

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
นี่อาจเป็นการดำเนินการที่มีประสิทธิภาพน้อยที่สุดในบรรดาคำตอบทั้งหมด ลอง / จับแบ่งบริบทการดำเนินการที่มีอยู่และกลับไปที่ 'เส้นทางช้า' ของการดำเนินการ บอกลาการเพิ่มประสิทธิภาพใด ๆ ที่ V8 ทำภายใต้ฝาครอบ
Evan Plaice

5
ไม่มากพอ เกี่ยวกับเรื่องนี้if (current <= 0) window.top.close()
user56reinstatemonica8

0

เนื่องจากpromiseมีอาร์กิวเมนต์การเรียกกลับ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เป็น 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);

0

ลดเวอร์ชันที่ใช้งานได้ด้วยการหยุดพักสามารถใช้เป็น 'transform' ได้เช่น ในขีดล่าง

ฉันพยายามใช้มันด้วยค่าสถานะการกำหนดค่าเพื่อหยุดการใช้งานเพื่อให้การลดการใช้งานไม่จำเป็นต้องเปลี่ยนโครงสร้างข้อมูลที่คุณกำลังใช้อยู่

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

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

การใช้งาน 3 จับ config เป็นตัวแปรภายนอก

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)

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.