質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.40%

  • JavaScript

    21476questions

    JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

map,reduce,filterで集計処理を簡潔に書きたい

解決済

回答 2

投稿

  • 評価
  • クリップ 2
  • VIEW 3,917

stakezaki

score 32

前提・実現したいこと

JavaScriptで以下のようなmap,reduce,filterを使って集計するコードを書いています。
name単位でvalueを合算するものです。
もっと簡潔にエレガントに書けないものでしょうか?

自分ならこう書くといった例を示していただけると幸いです。

ソースコード

var src = [{'name':'abc','value':100},{'name':'def','value':50},{'name':'abc','value':123},{'name':'hij','value':789},{'name':'def','value':001}];

var sorted = src.sort(function(a, b) {
  if (a.name > b.name){
    return 1;
  }else{
    return -1;
  }
});

var result = sorted.map(function(entry){ return entry.name; })           // 同じnameの重複をなくす
                          .filter(function (x, i, self) {
                            return self.indexOf(x) === i;
                            })
                      .map(function(name) {                // 重複を排除した配列で繰り返し 
                          return sorted.reduce(function(prev,current) {   // ソートした配列(重複を含む)を元に集計
                                if (current.name===name){
                                  if (prev.name===current.name) {           // 前回のものと今回のものが同じnameであれば合算
                                    var entry = {};                         // 元の配列を壊さないように新しくentryを生成する
                                    entry.name = name;
                                    entry.value = current.value + prev.value;
                                    return entry;
                                  }
                                  return current;

                                }
                                return prev;
                              },{})
                        });


console.log(result);

[Object { name="abc",  value=223}, Object { name="def",  value=51}, Object { name="hij",  value=789}]
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • think49

    2016/12/27 23:14

    name は String 型である事が確定しているのでしょうか。Object 型が来るケースは想定されますか。

    キャンセル

  • stakezaki

    2016/12/27 23:34

    String型確定です

    キャンセル

  • think49

    2016/12/27 23:47

    であれば、オブジェクト初期化子を利用できますね。回答にてコードを書きました。

    キャンセル

回答 2

+3

var src = [{'name':'def','value':50},{'name':'abc','value':100},{'name':'abc','value':123},{'name':'hij','value':789},{'name':'def','value':001}];
var sum = {};
src.forEach(o => sum[o.name] = (sum[o.name] || 0) + o.value);
var res = Object.keys(sum).sort((a, b) => a > b ? 1 : -1).map(name => ({name, value: sum[name]}));
console.log(res);


こんな感じとか。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/12/27 23:44

    エレガントですね〜

    キャンセル

checkベストアンサー

+2

 Array.prototype.reduce 単体

要件を達成する方法としてはおそらく最速。
(Array.prototype.reduce を使わなければもっと速いですが、reduce を使う条件なのでそこは置いておきます。)

'use strict';
var src = [{'name':'abc','value':100},{'name':'def','value':50},{'name':'abc','value':123},{'name':'hij','value':789},{'name':'def','value':1}];

var results = src.reduce(function (results, current) {
  for (var i = 0, l = results.length, name = current.name, object; i < l; ++i) {
    object = results[i];

    if (object.name === name) {
      object.value += current.value;
      return results;
    }
  }

  results.push(current);

  return results;
}, []);

console.log(JSON.stringify(results)); // [{"name":"abc","value":223},{"name":"def","value":51},{"name":"hij","value":789}]

 オブジェクト初期化子

このコードは name が String 型であることが確定している必要があります。

'use strict';
var src = [{'name':'abc','value':100},{'name':'def','value':50},{'name':'abc','value':123},{'name':'hij','value':789},{'name':'def','value':1}];

var object = src.reduce(function (object, current) {
  var name = current.name;

  if (object.hasOwnProperty(name)) {
    object[name] += current.value;
  } else {
    object[name] = current.value;
  }

  return object;
}, {});

var results = Object.keys(object).map(function (key) {
  return {name: key, value: object[key]};
});

console.log(JSON.stringify(results)); // [{"name":"abc","value":223},{"name":"def","value":51},{"name":"hij","value":789}]

 オブジェクト初期化子 + Object.entries (ES8)

先行仕様(ES8/ES2017)ですが、Object.entries を利用するとオブジェクト初期化子のコードを更にエレガントに書けます。
Object.entries は Polyfill コードがあるので、現行でも使おうと思えば使えます。

'use strict';
var src = [{'name':'abc','value':100},{'name':'def','value':50},{'name':'abc','value':123},{'name':'hij','value':789},{'name':'def','value':1}];

var results = Object.entries(src.reduce((object, current) => {
  var name = current.name;

  if (object.hasOwnProperty(name)) {
    object[name] += current.value;
  } else {
    object[name] = current.value;
  }

  return object;
}, {})).map(entry=>{
  return {name: entry[0], value: entry[1]};
});

console.log(JSON.stringify(results)); // [[{"name":"abc","value":223},{"name":"def","value":51},{"name":"hij","value":789}]

 new Map (ES6)

オブジェクト初期化子で「name が String 型でなければならない」欠点を new Map で克服しており、可搬性が最も高いと思われます。
構造を変えていいのなら、Map.prototype.entries の出力値で十分かもしれません。

'use strict';
var src = [{'name':'abc','value':100},{'name':'def','value':50},{'name':'abc','value':123},{'name':'hij','value':789},{'name':'def','value':1}],
    map = new Map,
    results = [];

src.reduce(function (map, current) {
  var name = current.name;

  map.set(name, map.has(name) ? map.get(name) + current.value : current.value);

  return map;
}, map).forEach(function (value, key, map) {
  this.push({name: key, value: value});
}, results);

console.log(JSON.stringify(results)); // [[{"name":"abc","value":223},{"name":"def","value":51},{"name":"hij","value":789}]
console.log(JSON.stringify([...map.entries()])); // [["abc",223],["def",51],["hij",789]]

 更新履歴

  • 2016/12/28 00:08 new Mapコードを Object.keys -> Map#forEach
  • 2016/12/28 00:32 new Mapコードを三項演算子を使ったコードに修正
  • 2016/12/28 01:38 オブジェクト初期化子 + Object.entriesのコード追記

Re: stakezaki さん

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/12/28 00:00

    オブジェクト初期化子の例が私が一番イメージしていたものに近いです。
    また、ES6の例も示してくださり、大変勉強になります。
    reduce縛りのような質問になっていましたが、実は短くて高速でエレガントであれば何でもよかったのです。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.40%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る

  • JavaScript

    21476questions

    JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。