Làm thế nào để ngắt sớm phương thức Reduce ()?


94

Làm thế nào tôi có thể phá vỡ sự lặp lại của reduce()phương thức?

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ạn currentmã trên là gì? Tôi không biết làm thế nào những cái này có thể làm điều tương tự. Trong mọi trường hợp có những phương pháp mà phá vỡ đầu như some, every,find
elclanrs

someeverytrả về boolean và findtrả về một bản ghi duy nhất, những gì tôi muốn là chạy các hoạt động để tạo một bản ghi nhớ. currentlà Giá trị hiện tại. tham khảo
Julio Marins

Ý tôi là những gì currenttrong đoạn mã đầu tiên?
elclanrs

cập nhật, nhờ trả lời
Julio Marins

2
Câu trả lời là bạn không thể phá vỡ sớm reduce, bạn sẽ phải tìm một cách khác với các hàm nội trang thoát sớm hoặc tạo trình trợ giúp của riêng bạn, hoặc sử dụng lodash hoặc thứ gì đó. Bạn có thể đăng một ví dụ đầy đủ về những gì bạn muốn làm không?
elclanrs

Câu trả lời:


94

CẬP NHẬT

Một số nhà bình luận đưa ra quan điểm tốt rằng mảng ban đầu đang bị đột biến để phá vỡ .reduce()logic sớm bên trong .

Do đó, tôi đã sửa đổi câu trả lời một chút bằng cách thêm một .slice(0)trước khi gọi một bước tiếp theo .reduce(), tạo ra một bản sao của mảng ban đầu. LƯU Ý : Các hoạt động tương tự hoàn thành cùng một nhiệm vụ là slice()(ít rõ ràng hơn) và toán tử dàn trải [...array]( hiệu suất kém hơn một chút ). Hãy nhớ rằng, tất cả những điều này đều thêm một hệ số không đổi bổ sung của thời gian tuyến tính vào thời gian chạy tổng thể + 1 * (O (1)).

Bản sao, phục vụ để bảo toàn mảng ban đầu khỏi đột biến cuối cùng gây ra loại bỏ do lặp lại.

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


Bạn CÓ THỂ ngắt trên bất kỳ lần lặp nào của một lệnh gọi .reduce () bằng cách thay đổi đối số thứ 4 của hàm giảm: "array". Không cần chức năng giảm tùy chỉnh. Xem Tài liệu để biết danh sách đầy đủ các .reduce()thông số.

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

Đối số thứ 4 là mảng đang được lặp lại.

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

TẠI SAO?:

Lý do duy nhất mà tôi có thể nghĩ ra để sử dụng giải pháp này thay vì nhiều giải pháp khác đã trình bày là nếu bạn muốn duy trì một phương pháp lập trình chức năng cho thuật toán của mình và bạn muốn có cách tiếp cận khai báo nhất có thể để thực hiện điều đó. Nếu toàn bộ mục tiêu của bạn là GIẢM một mảng theo nghĩa đen thành một nguyên thủy không sai thay thế (chuỗi, số, boolean, Biểu tượng) thì trên thực tế, tôi cho rằng đây là IS, cách tiếp cận tốt nhất.

TẠI SAO KHÔNG?

Có một danh sách toàn bộ các đối số để KHÔNG thay đổi các tham số của hàm vì đó là một phương pháp không tốt.


3
+1. Đây phải là câu trả lời được chấp nhận. Tuy nhiên, giải pháp này không bao giờ được sử dụng, vì những lý do được nêu trong "TẠI SAO KHÔNG".
johndodo

3
Đây thực sự là LỜI KHUYÊN XẤU vì splicethực hiện một đột biến có thể nhìn thấy ( array). Theo mô hình chức năng, bạn sẽ sử dụng kiểu giảm trong kiểu chuyển tiếp tiếp tục hoặc sử dụng đánh giá lười biếng với kiểu giảm kết hợp phải. Hoặc, như một giải pháp thay thế đơn giản hơn, chỉ đơn giản là đệ quy.

Giữ lấy! bằng cách thay đổi đối số thứ 4 của hàm giảm: "array" không phải là một câu lệnh đúng. Trong trường hợp này, nó đang xảy ra (ví dụ trong câu trả lời) vì nó cắt mảng thành mảng có độ dài đơn (phần tử đầu tiên) trong khi nó đã đạt đến chỉ số 2 , rõ ràng là lần sau, đối với chỉ số 3, nó sẽ không nhận được một mục để lặp (như bạn đang thay đổi tham chiếu ban đầu thành mảng có độ dài 1 ). Trong trường hợp bạn thực hiện một cửa sổ bật lên cũng sẽ làm thay đổi mảng nguồn nhưng không dừng lại ở giữa (nếu bạn không ở chỉ mục cuối cùng thứ hai).
Koushik Chatterjee

@KoushikChatterjee Tuyên bố của tôi đúng với ý nghĩa ẩn của tôi. Nó không đúng với ý nghĩa rõ ràng của bạn. Bạn nên đưa ra một đề xuất về việc sửa đổi tuyên bố để bao gồm các điểm của bạn và tôi sẽ thực hiện chỉnh sửa vì nó sẽ cải thiện câu trả lời tổng thể.
Tobiah Rex

1
Tôi thích đạt cho các nhà điều hành lây lan để tránh bất kỳ đột biến không mong muốn, [... mảng] .reduce ()
eballeste

16

Không sử dụng giảm. Chỉ cần lặp lại trên mảng với các trình vòng lặp bình thường (cho, v.v.) và thoát ra khi điều kiện của bạn được đáp ứng.


58
niềm vui trong này ở đâu? :)
Alexander Mills

2
@AlexanderMills có lẽ anh ấy thích trở thành người xâm nhập!
dimpiax

3
câu trả lời này có giá trị 0 ở đây
liên kết

không chắc tại sao điều này lại nhận được nhiều ủng hộ ... đây không phải là câu trả lời vì OP đã hỏi làm thế nào để phá vỡ sớm từ giảm () .. Nó giống như đi khám bác sĩ khi bạn bị đau khi cúi xuống và bác sĩ nói bạn không cúi xuống.
ricosrealm

12

Bạn có thể sử dụng các hàm như một sốmọi hàm miễn là bạn không quan tâm đến giá trị trả về. mọi ngắt khi gọi lại trả về false, một số ngắt khi nó trả về true:

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

25
Nhưng nếu anh ta đang cố gắng làm reducethì theo định nghĩa, anh ta quan tâm đến giá trị trả về.

1
@ torazaburo — chắc chắn, nhưng tôi không thấy nó được sử dụng trong OP và có những cách khác để nhận được kết quả. ;-)
RobG

6

Tất nhiên, không có cách nào để khiến phiên bản tích hợp của reducethoát sớm.

Nhưng bạn có thể viết phiên bản giảm của riêng mình, sử dụng mã thông báo đặc biệt để xác định khi nào vòng lặp sẽ bị phá vỡ.

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

Sử dụng nó như thế này, để tính tổng một mảng nhưng thoát ra khi bạn đạt 99:

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

> 3

1
Bạn có thể sử dụng đánh giá lười biếng hoặc CPS để đạt được hành vi mong muốn:
scriptum

Câu đầu tiên của câu trả lời này không chính xác. Bạn có thể phá vỡ, xem câu trả lời của tôi dưới đây để biết chi tiết.
Tobiah Rex

4

Array.every có thể cung cấp một cơ chế rất tự nhiên để thoát khỏi sự lặp lại thứ tự cao.

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

Bạn có thể phá vỡ mọi mã - và do đó mọi bản dựng trong trình lặp - bằng cách đưa ra một ngoại lệ:

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
Đây có lẽ là cách thực thi kém hiệu quả nhất trong tất cả các câu trả lời. Thử / bắt phá vỡ bối cảnh thực thi hiện có và quay trở lại 'đường dẫn chậm' của quá trình thực thi. Nói lời tạm biệt với bất kỳ tối ưu hóa nào mà V8 thực hiện dưới vỏ bọc.
Evan Plaice

5
Không đủ cực đoan. Làm thế nào về vấn đề này:if (current <= 0) window.top.close()
user56reinstatemonica8

0

Vì các đối số promiseresolverejectgọi lại của s , tôi đã tạo reducehàm giải pháp thay thế với breakđối số gọi lại. Nó nhận tất cả các đối số giống như reducephương thức gốc , ngoại trừ đối số đầu tiên là một mảng để làm việc (tránh việc vá lỗi khỉ). Đối số [2] thứ ba initialValuelà tùy chọn. Xem đoạn mã dưới đây để biết bộ functiongiảm tốc.

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

Và đây là tập lệnh được sửa đổi reducerdưới dạng 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

Giảm phiên bản chức năng với ngắt có thể được thực hiện dưới dạng 'chuyển đổi', ví dụ: trong gạch dưới.

Tôi đã cố gắng triển khai nó bằng cờ cấu hình để dừng nó lại để quá trình giảm triển khai không phải thay đổi cấu trúc dữ liệu mà bạn hiện đang sử dụng.

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

Cách sử dụng 1, đơn giản

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

Use2, sử dụng cấu hình làm biến nội bộ

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

Sử dụng 3, chụp cấu hình làm biến bên ngoài

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

Bạn không thể đột nhập từ bên trong reduce phương thức. Tùy thuộc vào những gì bạn đang cố gắng hoàn thành, bạn có thể thay đổi kết quả cuối cùng (đó là một lý do bạn có thể muốn làm điều này)

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

Lưu ý: bạn không thể gán lại tham số mảng trực tiếp

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

Tuy nhiên (như được chỉ ra bên dưới), bạn CÓ THỂ ảnh hưởng đến kết quả bằng cách thay đổi nội dung của mảng:

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 " Bạn không thể thay đổi các giá trị đối số trực tiếp theo cách ảnh hưởng đến các phép tính tiếp theo ", điều đó không đúng. ECMA-262 cho biết: Nếu các phần tử hiện có của mảng bị thay đổi, giá trị của chúng khi được chuyển đến callbackfn sẽ là giá trị tại thời điểm giảm lượt truy cập chúng . Ví dụ của bạn không hoạt động vì bạn đang gán một giá trị mới cho d , không phải sửa đổi mảng ban đầu. Thay thế d = [1, 1, 2]bằng d[2] = 6và xem điều gì sẽ xảy ra. ;-)
RobG

-1

Một cách triển khai đơn giản khác mà tôi đã giải quyết cùng một vấn đề:

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

Nếu bạn muốn xâu chuỗi các lời hứa một cách tuần tự bằng cách sử dụng mẫu bên dưới:

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

Nhưng cần phải phá vỡ theo một cái gì đó xảy ra bên trong hoặc bên ngoài một lời hứa, mọi thứ trở nên phức tạp hơn một chút vì vòng lặp giảm được kết thúc trước khi lời hứa đầu tiên được thực thi, làm cho việc cắt ngắn mảng trong lệnh gọi lại lời hứa trở nên vô ích, tôi đã kết thúc với việc triển khai này:

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

Sau đó, bạn có thể làm điều gì đó như sau:

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

Tôi đã giải quyết nó như sau, ví dụ trong somephương pháp mà hiện tượng đoản mạch có thể tiết kiệm rất nhiều:

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)

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.