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

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

ただいまの
回答率

89.86%

配列内の数字を項目を分けて足したい

解決済

回答 3

投稿

  • 評価
  • クリップ 0
  • VIEW 487

jsrookie

score 18

発生している問題、目指している形

初心者なためかなり冗長なコードになっていると思いますがご了承ください。
ソースコードの様なtxtを読み込むと以下の様な配列になるプログラムを作ったのですが、各配列内でx,yを分けて数字を足したいです。
result5[a]=[[x, 1000, a], [y, 1000, a], [y, 1, a], [x, 1000, a]]
result5[b]=[[x, 100, b], [x, 10, b], [x, 100, b]]
result5[c]=[[y, 100, c], [y, 10, c], [x, 1, c]]
result5[d]=[[x, 1, d], [x, 10, d]]

最終的には配列内のa,b,c,dは消し、以下の様な形にしたいです。
result5[a]=[[x, 2000], [y, 1001]]
result5[b]=[x, 210]
result5[c]=[[x, 1], [y, 110]]
result5[d]=[x, 11]

result5[k]のkの値が同じ時に何度も数字を足してしまったり、xとyを分けて数字を足すことができませんでした。
どうかご教授お願いします。

該当のソースコード

<body>
<form name="test">
<input type="file" id="selfile"><br>
<textarea name="txt" rows="10" cols="60" readonly></textarea>
</form>

<script>

var obj1 = document.getElementById("selfile");

obj1.addEventListener("change",function(evt){

  var file = evt.target.files;
  var reader = new FileReader();
  reader.readAsText(file[0]);

  reader.onload = function(ev){

    //テキストエリアに表示
    document.test.txt.value = reader.result.toLowerCase();
    var result2=reader.result.toLowerCase().split("\n");

    var result3=[];
    for (var i = 0; i <result2.length; i++) {         
        result3.push(result2[i].split(/\s+/));
    }

    for (var i = 0; i<result3.length; i++) {  
        if(result3[i].length>=6){
          while (result3[i].length>=6){
            result3[i].pop();
          }
        }
    }

    Array.prototype.divide = function(n){
    var ary = this;
    var idx = 0;
    var results = [];
    var length = ary.length;

    while (idx + n < length){
        var result = ary.slice(idx,idx+n)
        results.push(result);
        idx = idx + n
    }
    var rest = ary.slice(idx,length+1)
    results.push(rest)
    return results;
    }

    result4=[];
    for (var i = 0; i<result3.length; i++) {  
      result4.push(result3[i].divide(1));
    }

    for (var i = 0; i<result4.length; i++) { 
    if(result4[i][4][0].indexOf("x") >= 0){ 
          result4[i][0].unshift(1000); 
          result4[i][0].unshift("x");
          result4[i][1].unshift(100);
          result4[i][1].unshift("x"); 
          result4[i][2].unshift(10);
          result4[i][2].unshift("x"); 
          result4[i][3].unshift(1); 
          result4[i][3].unshift("x"); 
    } else if (result4[i][4][0].indexOf("y") >= 0){
          result4[i][0].unshift(1000); 
          result4[i][0].unshift("y");
          result4[i][1].unshift(100);
          result4[i][1].unshift("y"); 
          result4[i][2].unshift(10);
          result4[i][2].unshift("y"); 
          result4[i][3].unshift(1);
          result4[i][3].unshift("y");  
    }
    }
console.log(result4);

var result5 = [];
for (var i = 0; i < result4.length; i++) {         
    for (var j = 0; j < result4[i].length; j++) {         
        k = result4[i][j][2];
        if (!result5[k]) {
            result5[k] = [];
        }
        result5[k].push(result4[i][j]);
    }
}
console.log(result5);
  }
},false);
</script>
</body>
a b b d X
a c c a Y
a b d c X
  • 気になる質問をクリップする

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

回答 3

+3

スマートではないですが、下記で実現できました。

var result5 = new Map(
    [
      ['a', [['x', 1000, 'a'], ['y', 1000, 'a'], ['y', 1, 'a'], ['x', 1000, 'a']]],
      ['b', [['x', 100, 'b'], ['x', 10, 'b'], ['x', 100, 'b']]],
      ['c', [['y', 100, 'c'], ['y', 10, 'c'], ['x', 1, 'c']]],
      ['d', [['x', 1, 'd'], ['x', 10, 'd']]]
    ]
);

var result = new Map();
for (const [key, value] of result5.entries()) {
  const o = new Map();
  for(let l of value) {
    const v = o.get(l[0]) || 0
    o.set(l[0], l[1] + v);
  }
  const r = [];
  for( const [k, v] of o.entries()){
    r.push([k, v])
  }
  result.set(key, r);
}
console.log(result);

xとyはキーとなるので、配列ではなくMapを使用した方が良いかと思います。

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2019/08/10 08:03

    回答ありがとうございます!
    yamap_55さんのプログラムで動作確認できました。
    しかし、私の考えるresult5と形が違ったためか私のプログラム上だと上手くいきませんでした。
    result5の形を変えたりすることで私のプログラムでも動くようにしたいと思います。
    配列のMapはまだあまり扱ったことがないため勉強頑張ります。
    ありがとうございました。

    キャンセル

checkベストアンサー

+1

こんにちは

ご質問を拝読しまして、 result5 は以下のようなオブジェクトであると把握しました。

{
  a: [['x', 1000, 'a'], ['y', 1000, 'a'], ['y', 1, 'a'], ['x', 1000, 'a']],
  b: [['x', 100, 'b'], ['x', 10, 'b'], ['x', 100, 'b']],
  c: [['y', 100, 'c'], ['y', 10, 'c'], ['x', 1, 'c']],
  d: [['x', 1, 'd'], ['x', 10, 'd']]
}


この回答では、上記のオブジェクト result5 に含まれる配列に対して集計処理を行って、ご質問にある

最終的には配列内のa,b,c,dは消し、以下の様な形

の内容を持つオブジェクトを、 result6 という変数に得るためのコードを示します。

まず、以下のような関数summarize(ary) を作成します。

const summarize = (ary) => {    
  const map = ary.reduce((m, e) => m.set(e[0],  (m.get(e[0]) || 0) + e[1]), new Map());
  return [...map].sort( (e1,e2) => e1[0].localeCompare(e2[0]));
}

上記の関数summarize(ary)は、引数 ary にたとえば

[['x', 1000, 'a'], ['y', 1000, 'a'], ['y', 1, 'a'], ['x', 1000, 'a']]


が与えられると、ご質問の要件に沿って、各要素の配列の先頭の文字ごとに、2番目の要素である数値を足し上げた配列

[['x', 2000], ['y', 1001]]


を返します。また、引数 ary に

[['y', 100, 'c'], ['y', 10, 'c'], ['x', 1, 'c']]


が与えられた場合は、足し上げたうえで、 先頭要素のアルファベット順に並び替えて、

[['x', 1], ['y', 110]]


を返します。

上記の summarize(ary)を使うと、冒頭に書いた result5 から  result6 を得るには、以下のようにすればよいです。

const result6 = Object.entries(result5).reduce((obj, [k,v]) => ({...obj, [k]: summarize(v)}), {});

上記により、result6 には各配列が集計されているオブジェクトが入ります。

以下は、上記を動作確認するために jsFiddle に上げたものです。

以上、参考になれば幸いです。

追記1

補足ですが、上記に挙げたコードの中で、 result5 から result6 を得る以下の行

const result6 = Object.entries(result5).reduce((obj, [k,v]) => ({...obj, [k]: summarize(v)}), {});


については、lodash の _.mapValues を使うと、以下のように少し短く書けます。

const result6 = _.mapValues(result5, summarize);

lodash はオブジェクトや配列の操作で便利なメソッドを提供しており、用途にうまく合ったメソッドがみつかると、コードを短くできることがあります。

追記2

result5 からの合計処理だけではなく、ご質問に挙げられているソースコード全体について、リファクタ案を書いてみましたので、参考までに提示します。作成したコードは以下に上げています。

最終的な修正後のJavascriptのコードは以下です。

以下、各コミットを順を追って説明します。

  • initial commit では、ご質問に挙げられているコードをそのままコピペしたうえで、HTML と JavaScript のファイルを分けました。
     

  • 末尾に空文字列がある場合、これを除去 :result2 を作るときに、元のコードだと、末尾に空文字列ができてしまうことがあるので、これを除く filter を追加しています。
     

  • result3 の作成にmapとsliceを使用: result2 から result3 を作る際に、map と slice を使うと短くできます。
     

  • 以下は、 result3 から result4 を作る処理を3段階でリファクタしました。
    (1) result4 の作成部分を修正(その1): 元のコードにある、 divide はかなり力作という感じで恐縮ですが、 .divide(1) と、要素が1個の配列に区切るならば、修正後のように短く書けます。
    (2) result4 の作成部分を修正(その2) : result4 を作る2段階目です。map を二重にして、unshift を使わずに長さが 3 の配列を作ります。

  • (3) result4 の作成部分を修正(その3) :上記(1)と(2)の処理をひとつにまとめました。
     

  • result5 の作成にreduceとfilterを使用 :result4 から result5 を作る処理を reduce と filter を使ってみました。元のコードでは、result5 は配列ですが、 ab などがキーになるのでオブジェクトが得られるようにしています。
     

  • 以下の2つは、先の回答に書いたコードを追加したものです。
    (1) result6 の作成を追加
    (2) lodash の利用

配列のメソッド、map や reducefilter とスプレッド構文は、配列やオブジェクトを処理するプログラムを書くときに、いろいろな場面で重宝します。

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2019/08/10 08:03

    前回の質問に引き続き回答ありがとうございます!
    まさに求めていた結果が得られて感謝しかないです。
    プログラムがどう動いているのか説明して下さったのでとても分かりやすかったです。
    本当にありがとうございました。

    キャンセル

  • 2019/08/10 08:36

    また、コード全体の修正までしていただいてありがとうございます。
    独学なため誰かに修正していただく機会は皆無だったためとても勉強になりました。
    私の冗長なコードがこんなに短くなるとは驚きです。
    本当にありがとうございました。
    因みにdivideは自作ではなく、調べたら要素4つ毎に区切るコードが出てきたのでそれを1個で区切るよう変えたものです。

    キャンセル

  • 2019/08/10 21:36 編集

    どういたしまして。

    > 独学なため誰かに修正していただく機会は皆無だったためとても勉強になりました。

    ありがとうございます。そう仰って頂けますと回答者冥利に尽きます。

    > 因みにdivideは自作ではなく、調べたら要素4つ毎に区切るコードが出てきたのでそれを1個で区切るよう変えたものです。

    なるほどですね。
    そのように、すでに誰かが作ってくれたコードを、自分が書こうとしているコードに利用できるか見極めて、必要ならちょっと手を加えて、利用させてもらう(または、検証した結果、利用しないという判断をする。)ということも、何かひとまとまりのものを作ろうというときに、とても大事になってきます。

    jsrookieさんはJQuery のご質問もあるようですが、JQuery だったりReactだったりのライブラリを使ったフロントエンド開発をやろうとするときに、こういったライブラリの使い方というのは、公式ドキュメントのサンプルを写経すれば、だいたいお決まりの書き方が分かってきますが、どのライブラリを使うにせよ、自分の作ろうとしているWEBアプリなりシステムの対象領域("ドメイン"といったりします。)における、個別具体的な問題解決をするコードというのは、配列やオブジェクトの操作や値の置き換え、条件に合致する要素の検索あるいは除外といったコードを、(ライブラリの使い方とは違って)お手本がない中で書くことになります。
    今回のご質問でいえば、

    与えられたテキストファイルから、['x', 1000, 'a'] といった、 'X' と 10のN乗 と 'a' という3つの組を作る。そしてそれらを集計する。

    というのが個別具体的な問題です。

    そのときに、少ないコード量で大きな仕事(または、いい仕事)をしてくれるコードを思いついて実際に書くには、配列操作でいうと、 map, reduce, filter、find といったような、関数を引数に取る関数("高階関数" といいます。)を使ったコードがスラスラ出てくるか、というのが、初心者からワンステップ上に登るポイントのひとつと思います。

    以下にまとまった記事をご紹介します。

    Qiita: JavaScript 高階関数を説明するよ-代表的な高階関数(may88seijiさん)
    https://bit.ly/2TkMecE

    for 文を使ったほうが明らかに分かり易いコードになる場合は別として、今後しばらくは、 配列に対して for ループのコードを書こうとしたときに、いったん手を留めて、
    「これを map やforEach, reduce などで書けないか? 」
    と、ご自身に制約を課してみるのも面白いかもしれません。

    それと、これは今すぐ始めなくてもいいですが、for ループを書く前に map などの高階関数を使ったコードが出てくるようになるために、今後のどこかで以下も取り組むとよいかもしれません。

    ・ Lisp にちょっと入門してみる。

    ・ JavaScript による関数型プログラミングを勉強してみる。具体的には  
    https://amzn.to/2OLg2jU
     といったような本を読む。

    また上記のような、javascript によるプログラミング力を高めるときに、基礎の点検というのが外せない学習アイテムになってきますが、そのときに以下の本をおすすめします。(もう読んでいたらすみません)

    ・開眼! JavaScript ―言語仕様から学ぶJavaScriptの本質
     https://amzn.to/2H0Rtcr

     手前味噌ですが、上記の本のサンプルコード集を以下にリストしています。
     https://bit.ly/31x7pLr


    今後の学習が捗ることをお祈り申し上げます。

    キャンセル

+1

こういうことでしょうか?

var result5={};
result5['a']=[['x', 1000, 'a'], ['y', 1000, 'a'], ['y', 1, 'a'], ['x', 1000, 'a']];
result5['b']=[['x', 100, 'b'], ['x', 10, 'b'], ['x', 100, 'b']];
result5['c']=[['y', 100, 'c'], ['y', 10, 'c'], ['x', 1, 'c']];
result5['d']=[['x', 1, 'd'], ['x', 10, 'd']];

Object.keys(result5).map(function(row){
  var obj = result5[row].reduce(function(obj, arr){
    if ( ! (arr[0] in obj) ) obj[arr[0]] = 0;
    obj[arr[0]] += arr[1];
    return obj;
  },{});
  return Object.keys(obj).map(function(e){
    return [e, obj[e]]
  })
}).map(function(e){
  if (e.length == 1) return e[0];
  return e;  
});



/*
[
  [
    [
      "x",
      2000
    ],
    [
      "y",
      1001
    ]
  ],
  [
    "x",
    210
  ],
  [
    [
      "y",
      110
    ],
    [
      "x",
      1
    ]
  ],
  [
    "x",
    11
  ]
]
*/

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2019/08/10 08:07

    回答ありがとうございます!
    まさに私の求めていたものです。
    初心者の私でも理解しやすそうで、こんな方法があるのかと勉強になりました。
    本当にありがとうございました。

    キャンセル

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

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

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