Javascript mengurangi () pada Objek

182

Ada metode Array yang bagus reduce()untuk mendapatkan satu nilai dari Array. Contoh:

[0,1,2,3,4].reduce(function(previousValue, currentValue, index, array){
  return previousValue + currentValue;
});

Apa cara terbaik untuk mencapai hal yang sama dengan benda? Saya ingin melakukan ini:

{ 
    a: {value:1}, 
    b: {value:2}, 
    c: {value:3} 
}.reduce(function(previous, current, index, array){
  return previous.value + current.value;
});

Namun, Object tampaknya tidak reduce()menerapkan metode apa pun .

Pavel S.
sumber
1
Apakah Anda menggunakan Underscore.js?
Sethen
Nggak. Apakah Underscore menyediakan pengurangan untuk objek?
Pavel S.
Saya tidak ingat. Saya tahu ini memiliki reducemetode. Saya akan memeriksa di sana. Padahal, solusinya sepertinya tidak terlalu sulit.
Sethen
1
@Ethen Maleno, @Pavel: ya _memang ada pengurangan untuk objek. Tidak yakin apakah itu berfungsi secara tidak sengaja atau jika dukungan objek disengaja, tetapi memang Anda dapat melewatkan objek seperti dalam contoh pertanyaan ini, dan itu akan (secara konseptual) for..inmemanggil fungsi iterator Anda dengan nilai-nilai yang ditemukan pada setiap tombol.
Roatin Marth

Jawaban:

55

Apa yang sebenarnya Anda inginkan dalam kasus ini adalah Object.values. Berikut ini adalah implementasi ES6 singkat dengan itu dalam pikiran:

const add = {
  a: {value:1},
  b: {value:2},
  c: {value:3}
}

const total = Object.values(add).reduce((t, {value}) => t + value, 0)

console.log(total) // 6

atau hanya:

const add = {
  a: 1,
  b: 2,
  c: 3
}

const total = Object.values(add).reduce((t, n) => t + n)

console.log(total) // 6
Daniel Bayley
sumber
Itulah Array.prototype.values ​​() yang Anda tautkan - diedit sekarang
Jonathan Wood
281

Salah satu pilihan akan reducedengan keys():

var o = { 
    a: {value:1}, 
    b: {value:2}, 
    c: {value:3} 
};

Object.keys(o).reduce(function (previous, key) {
    return previous + o[key].value;
}, 0);

Dengan ini, Anda ingin menentukan nilai awal atau putaran pertama 'a' + 2.

Jika Anda ingin hasilnya sebagai Obyek ( { value: ... }), Anda harus menginisialisasi dan mengembalikan objek setiap kali:

Object.keys(o).reduce(function (previous, key) {
    previous.value += o[key].value;
    return previous;
}, { value: 0 });
Jonathan Lonowski
sumber
15
Jawaban yang bagus tetapi lebih mudah dibaca untuk menggunakan Object.values ​​daripada Object.keys karena kami khawatir tentang nilai-nilai di sini bukan kunci. Seharusnya seperti ini: Object.values ​​(o) .reduce ((total, current) => total + current.value, 0);
Mina Luke
3
Object.values ​​memiliki dukungan browser yang jauh lebih buruk daripada Object.key, tetapi itu mungkin tidak menjadi masalah jika Anda menggunakan polyfill atau Anda mentransformasikannya dengan Babel
duhaime
tepatnya, saya menggunakan ini di kunci yang bit diambil dari model mongo, dan saya melewati objek kosong sebagai nilai awal ke hasil pengurangan kunci, dan sebanyak saya menggunakan @babel/plugin-proposal-object-rest-spreadplugin, saya menyebarkan nilai akumulator di kembalinya mengurangi dan itu bekerja seperti pesona, tetapi saya bertanya-tanya apakah saya melakukan sesuatu yang salah, karena saya melewati obejct ke nilai awal mengurangi dan jawaban Anda membuktikan kepada saya bahwa saya melakukan hal yang benar!
a_m_dev
55

Implementasi ES6: Object.entries ()

const o = {
  a: {value: 1},
  b: {value: 2},
  c: {value: 3}
};

const total = Object.entries(o).reduce(function (total, pair) {
  const [key, value] = pair;
  return total + value;
}, 0);
faboulaw
sumber
3
Object.entries(o); // returns [['value',1],['value',2],['value',3]]
faboulaw
1
const [key, value] = pair; Saya belum pernah melihat ini!
Martin Meeser
9
@ martin-meeser - ini disebut penghancuran. Kita bahkan dapat menghilangkan baris ini dengan mengubah function (total, pair)kefunction (total, [key, value])
Jakub Zawiślak
Meskipun entriesmerupakan cara yang lebih bersih untuk melakukan hal ini keys, ia kurang berkinerja. hackernoon.com/...
robdonn
@faboulaws Object.entries (o); // mengembalikan [["a", {value: 1}], ["b", {value: 2}], ["c", {value: 3}]]
Thomas
19

Pertama-tama, Anda tidak cukup mengerti apa nilai pengurangan sebelumnya.

Dalam kode pseudo yang Anda miliki return previous.value + current.value, maka previousnilainya akan berupa angka pada panggilan berikutnya, bukan objek.

Kedua, reduceadalah metode Array, bukan metode Object, dan Anda tidak bisa bergantung pada urutan ketika Anda mengulangi properti objek (lihat: https://developer.mozilla.org/en-US/docs/ JavaScript / Referensi / Pernyataan / untuk ... dalam , ini juga berlaku untuk Object.keys ); jadi saya tidak yakin jika mendaftarreduce objek lebih masuk akal.

Namun, jika pesanannya tidak penting, Anda dapat memiliki:

Object.keys(obj).reduce(function(sum, key) {
    return sum + obj[key].value;
}, 0);

Atau Anda bisa memetakan nilai objek:

Object.keys(obj).map(function(key) { return this[key].value }, obj).reduce(function (previous, current) {
    return previous + current;
});

PS dalam ES6 dengan sintaks fungsi tanda panah lemak (sudah ada di Firefox Nightly), Anda dapat sedikit menyusut:

Object.keys(obj).map(key => obj[key].value).reduce((previous, current) => previous + current);
ZER0
sumber
3

1:

[{value:5}, {value:10}].reduce((previousValue, currentValue) => { return {value: previousValue.value + currentValue.value}})

>> Object {value: 15}

2:

[{value:5}, {value:10}].map(item => item.value).reduce((previousValue, currentValue) => {return previousValue + currentValue })

>> 15

3:

[{value:5}, {value:10}].reduce(function (previousValue, currentValue) {
      return {value: previousValue.value + currentValue.value};
})

>> Object {value: 15}
Alair Tavares Jr
sumber
2

Perluas Object.prototype.

Object.prototype.reduce = function( reduceCallback, initialValue ) {
    var obj = this, keys = Object.keys( obj );

    return keys.reduce( function( prevVal, item, idx, arr ) {
        return reduceCallback( prevVal, item, obj[item], obj );
    }, initialValue );
};

Contoh penggunaan.

var dataset = {
    key1 : 'value1',
    key2 : 'value2',
    key3 : 'value3'
};

function reduceFn( prevVal, key, val, obj ) {
    return prevVal + key + ' : ' + val + '; ';
}

console.log( dataset.reduce( reduceFn, 'initialValue' ) );
'Output' == 'initialValue; key1 : value1; key2 : value2; key3 : value3; '.

tidak, wah !! ;-)

pengguna1247458
sumber
-1, sekarang Anda memiliki properti enumerable baru pada semua objek di masa depan: jsfiddle.net/ygonjooh
Johan
1
Tolong jangan modifikasi prototipe dasar seperti ini. Ini dapat menyebabkan banyak masalah bagi pengembang masa depan yang bekerja di basis kode yang sama
adam.k
Ya, ini adalah "tambalan monyet" Solusi ini ditulis 6 tahun yang lalu dan tidak terlalu relevan sekarang, hanya perlu diingat Dan misalnya, akan lebih baik untuk digunakan Object.entries()pada 2021
user1247458
2

Anda dapat menggunakan ekspresi generator (didukung di semua browser selama bertahun-tahun sekarang, dan di Node) untuk mendapatkan pasangan nilai kunci dalam daftar yang dapat Anda kurangi pada:

>>> a = {"b": 3}
Object { b=3}

>>> [[i, a[i]] for (i in a) if (a.hasOwnProperty(i))]
[["b", 3]]
Janus Troelsen
sumber
2

Objek dapat diubah menjadi array dengan: Object.entries () , Object.keys () , Object.values ​​() , dan kemudian direduksi menjadi array. Tetapi Anda juga dapat mengurangi objek tanpa membuat array perantara.

Saya telah membuat sebuah pembantu kecil perpustakaan odict untuk bekerja dengan objek.

npm install --save odict

Ini memiliki reducefungsi yang sangat mirip dengan Array.prototype.reduce () :

export const reduce = (dict, reducer, accumulator) => {
  for (const key in dict)
    accumulator = reducer(accumulator, dict[key], key, dict);
  return accumulator;
};

Anda juga dapat menetapkannya untuk:

Object.reduce = reduce;

karena metode ini sangat berguna!

Jadi jawaban untuk pertanyaan Anda adalah:

const result = Object.reduce(
  {
    a: {value:1},
    b: {value:2},
    c: {value:3},
  },
  (accumulator, current) => (accumulator.value += current.value, accumulator), // reducer function must return accumulator
  {value: 0} // initial accumulator value
);
Igor Sukharev
sumber
1

Jika Anda dapat menggunakan array, gunakan array, panjang dan urutan array adalah setengah nilainya.

function reducer(obj, fun, temp){
    if(typeof fun=== 'function'){
        if(temp== undefined) temp= '';
        for(var p in obj){
            if(obj.hasOwnProperty(p)){
                temp= fun(obj[p], temp, p, obj);
            }
        }
    }
    return temp;
}
var O={a:{value:1},b:{value:2},c:{value:3}}

reducer(O, function(a, b){return a.value+b;},0);

/ * nilai yang dikembalikan: (Nomor) 6 * /

kennebec
sumber
1

Ini tidak terlalu sulit untuk diterapkan sendiri:

function reduceObj(obj, callback, initial) {
    "use strict";
    var key, lastvalue, firstIteration = true;
    if (typeof callback !== 'function') {
        throw new TypeError(callback + 'is not a function');
    }   
    if (arguments.length > 2) {
        // initial value set
        firstIteration = false;
        lastvalue = initial;
    }
    for (key in obj) {
        if (!obj.hasOwnProperty(key)) continue;
        if (firstIteration)
            firstIteration = false;
            lastvalue = obj[key];
            continue;
        }
        lastvalue = callback(lastvalue, obj[key], key, obj);
    }
    if (firstIteration) {
        throw new TypeError('Reduce of empty object with no initial value');
    }
    return lastvalue;
}

Beraksi:

var o = {a: {value:1}, b: {value:2}, c: {value:3}};
reduceObj(o, function(prev, curr) { prev.value += cur.value; return prev;}, {value:0});
reduceObj(o, function(prev, curr) { return {value: prev.value + curr.value};});
// both == { value: 6 };

reduceObj(o, function(prev, curr) { return prev + curr.value; }, 0);
// == 6

Anda juga bisa menambahkannya ke prototipe Obyek:

if (typeof Object.prototype.reduce !== 'function') {
    Object.prototype.reduce = function(callback, initial) {
        "use strict";
        var args = Array.prototype.slice(arguments);
        args.unshift(this);
        return reduceObj.apply(null, args);
    }
}
Francis Avila
sumber
0

Karena belum benar-benar dikonfirmasi dalam jawaban, Underscore reducejuga berfungsi untuk ini.

_.reduce({ 
    a: {value:1}, 
    b: {value:2}, 
    c: {value:3} 
}, function(prev, current){
    //prev is either first object or total value
    var total = prev.value || prev

    return total + current.value
})

Catatan, _.reduceakan mengembalikan satu-satunya nilai (objek atau sebaliknya) jika objek daftar hanya memiliki satu item, tanpa memanggil fungsi iterator.

_.reduce({ 
    a: {value:1} 
}, function(prev, current){
    //not called
})

//returns {value: 1} instead of 1
Chris Dolphin
sumber
"Coba perpustakaan lain ini" tidak berguna
Grunion Shaftoe
Tidak berguna bagi Anda
Chris Dolphin
0

Cobalah fungsi panah satu liner ini

Object.values(o).map(a => a.value, o).reduce((ac, key, index, arr) => ac+=key)
M Thomas
sumber
0

Coba yang ini. Ini akan mengurutkan angka dari variabel lain.

const obj = {
   a: 1,
   b: 2,
   c: 3
};
const result = Object.keys(obj)
.reduce((acc, rec) => typeof obj[rec] === "number" ? acc.concat([obj[rec]]) : acc, [])
.reduce((acc, rec) => acc + rec)
prgv
sumber