人力検索はてな
モバイル版を表示しています。PC版はこちら
i-mobile

連想配列の数字の平均値の求め方について教えて下さい。こちらの続きになります。
http://q.hatena.ne.jp/1448507045


このような連想配列でキーごとに4番目のスコア(rec)の部分の平均値を求めてキーごとに表示させたいのですが方法がわかりません。
例)
UNIT1の平均値は@@点です。
UNIT2の平均値は@@点です。
連想配列に入れた後で、キーごとにスコアの数を数えて、その回数で割る方法を考えましたが合計点を求めるところがわかりません。また連想配列のデータのスコアの部分の合計点を求める場合、やはり、連想配列に入れたあとで、処理するのでしょうか?あるいは、入れながら足していくのか? 調べてみても1つのキーに対して数が1つの例しか見つからず、上のように1つのキーに得点が複数ある場合の処理の方法が理解できずにいます。アドバイスいたたければ幸いです。


●質問者: javelover
●カテゴリ:コンピュータ
○ 状態 :終了
└ 回答数 : 2/2件

▽最新の回答へ

1 ● Lhankor_Mhy
var tableMap = {
UNIT1:["80", "40"],
UNIT2:["50", "60", "90"]
};

var averageMap = Object.keys(tableMap).reduce(function(map, key){
 var row = tableMap[key];
 map[key] = row.reduce(function(sum, value){return sum += parseFloat(value)},0) / row.length
 return map;
},{});

//{UNIT1:60, UNIT2:66.66666666666667}

無理に副作用を嫌ってかえって分かりにくいコードになる図。

分かりやすい回答はa-kuma3さんお願いします。


javeloverさんのコメント
Lhankor_Mhyさん さっそくありがとうございます。 お返事遅れて申し訳ありませんでした。 なかなかLhankor_Mhyさんのコードを理解するのは難しいですがとてもシンプルにまとまるのですね じっくり勉強します。 ありがとうございました。

2 ● a-kuma3

ベタに書くとこんな感じになります。

var tableMap = {
 UNIT1:["80", "40"],
 UNIT2:["50", "60", "90"]
};

var averageMap = {};

for (var key in tableMap) {
 var data = tableMap[key];
 var sum = 0;
 for (var i = 0 ; i < data.length ; ++i) {
 sum += parseFloat(data[i]);
 }
 averageMap[key] = sum / data.length;
}

console.log(averageMap); // Object { UNIT1: 60, UNIT2: 66.66666666666667 }

やってることは id:Lhankor_Mhy さんが書いたのと同じです。

Array#reduce って、使いどころが難しいメソッドだと思います。
メソッド名を素直に受け取れば、Array#filter メソッドで代替できるし、合計を求めるような N → 1 の「削減する」だったら「集約する」とかの方がメソッド名としてはピンとくるような感じがします。

ランカーさんの回答例にならったコードを回答には書きましたが、前の質問であったようなケースに組み込むのであれば、こんな感じかと。
https://jsfiddle.net/a_kuma3/yecrp2c1/1/

点数の表にくっつけるなら、こんな感じです。
https://jsfiddle.net/a_kuma3/k15k6k2w/2/

# 平均を求める所よりも、見た目を作るところの方が面倒です :-)


Lhankor_Mhyさんのコメント
質問からずれますが。 reduceって他言語ではどうだったかな、と思って見ると、rubyではinjectという関数なんですね。ググってみたところ、LISPがreduceで、Smalltalkがinjectなのが始まりだとか。

javeloverさんのコメント
a-kuma3さん どうもありがとうございました。 for (var key in tableMap) { var data = tableMap[key]; キーごとにデータを取り出すやり方がよくわからず困っておりましたが 上のようなやり方でできるのですね。 とても勉強になりました。 さっそくこれで進めていけそうです。 ランカーさんにもアドバイス頂きまして、いろいろとありがとうございました。

a-kuma3さんのコメント
>> reduceって他言語ではどうだったかな、と思って見ると、 << こんなのを読みました。 http://magazine.rubyist.net/?0038-MapAndCollect 関数型言語って不案内なのですが、ループがないから reduce みたいなのがあるという感じなんですかね。 >> for (var key in tableMap) { var data = tableMap[key]; << 前の質問で結果の表を作るところが、実は for ? in だったのですが、DOM の操作が目立ち過ぎてて埋もれちゃってますね <nobr>(´・ω・`)</nobr>

javeloverさんのコメント
また1つ教えて下さい。 こちらにコードを書きました。 https://jsfiddle.net/kajironpu/vqr6vq50/ for (var key in tableMap) { このループの中に averagedata.push(key) averagedata.push(average) このコードを追加しました。 新しい配列を作り、その中にキーごとに、計算された平均値を入れてみたのですが とても不思議な現象に遭遇してしまいました。 ループが終わったあとに alert(averagedata[0][0]) 試しに配列の0個目の0番めのデータ(タイトル名)を表示させてみたのですが、 なぜか、タイトルの1文字しか表示されません。 UNIT1の「U」が表示されます。 配列の中にタイトルの文字1個1個がバラバラに入っているような感じです。 なぜなのでしょうか??

a-kuma3さんのコメント
平均点を求める for ループで、key は、UNIT1、UNIT2 とループが回りますから、以下のようにデータが変わります。 >|javascript| for (var key in tableMap) { // 一周目 key = "UNIT1" ... averagedata.push(key); // averagedata = ["UNIT1"] averagedata.push(average); // averagedata = ["UNIT1", 60] for (var key in tableMap) { // 二周目 key = "UNIT2" ... averagedata.push(key); // averagedata = ["UNIT1", 60, "UNIT2"] averagedata.push(average); // averagedata = ["UNIT1", 60, "UNIT2", 66.7] ||< 一次元配列に、キーと平均点を順番に突っ込んでいるので、 averagedata[0] は、"UNIT1" になります。 UNIT1 の平均点は averagedata[1] に入っています。 文字列のデータに対して、配列の要素にアクセスするような大括弧と整数を指定すると、位置指定で文字を取り出すことになります(知らなかった)。 https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String#Character_access averagedata[0][0] は、"UNIT1" のゼロ文字目になるので "U" となります。 平均点を求めた結果を二次元配列(正確には、配列の配列)にするには、こんな感じです。 >|javascript| for (var key in tableMap) { ... var pair = []; // 空の配列 pair.push(key); pair.push(average); averagedata.push(pair); // キーと平均値の二つの要素を持った配列を追加 } alert(averagedata[0][0]); ||< tableMap に、各人の得点を貯めていったときと考え方は一緒です。

javeloverさんのコメント
ありがとうございます。なるほど、そうだったんですね 原因がわからずはまっておりました。なんと一次元配列に、キーと平均点を入れていたのは 気づきませんでした。。。。 文字の先頭が配列に入っていて、かなり焦りました。。。。 pushで2つのデータを合わせてから、まとめてpushすると二次元配列になるんですね。 とても理解できました。ありがとうございました。

a-kuma3さんのコメント
未実施を処理するループのところなんですが、i = 2 の三回目のループのときに、 averagedata[i] つまり、averagedata[2] が未定義なので、averagedata[i][0] を参照しようとしてエラーになってます(そこで、ループが止まる)。 ここでやりたいのは、averagedata に listdata[i] が、同じ順番のところではなく、どこかにあるかどうかを探さなくちゃいけません。 元になる得点データ dataArray4 を UNIT1 と UNIT2 ではなく、UNIT3 と UNIT4 に変えて試してみれば分かると思います。 こうしてみると、考えやすいと思います。 >|javascript| function searchAverageData(data, name) { // 配列の配列 data から、data[i][0] == name を探す ... return 平均得点; // もし、見つからなかったら -1 を返す } result1 =[]; result =[]; for (var i = 0 ; i < listdata.length ; ++i) { var average = searchAverageData(averagedata, listdata[i]); if (average != -1){ // 見つからなかったら -1 が返る alert("○:リストにありました " + i + " : " + listdata[i]) result1.push(averagedata[i][0]) //keyを入れる result1.push(averagedata[i][1]) //平均値を入れる result.push(result1) } else{ alert("×:リストにありません " + i + " : " + listdata[i]) result1.push(averagedata[i][0]) //keyを入れる result1.push("未実施") //未実施を入れる result.push(result1) } alert(result[i]) } ||< # ループの部分は、if の判定のところしか変えてません 「averagedata から listdata のそれぞれの名前を探す」というところが解決できれば、 - 配列に入れるよりも、連想配列に入れた方が簡単だったんじゃないか - listdata にある全員のデータを別の配列に入れる処理にもミスがある ということに、気付くと思います。 >> a-kuma3 さん、何度も恐縮です。 << 面倒になったら応えなくなるだけなので、あまり気にしなくて良いですよ。 そういうサイトなわけだし <tt>:-)</tt>

javeloverさんのコメント
a-kuma3さん、ありがとうございます。 https://jsfiddle.net/kajironpu/vqr6vq50/2/ なるほど、理解できました。 for (var i = 0 ; i < listdata.length ; ++i) { if (averagedata[i][0] == listdata[i]){ ここで、リストの数だけ順番に回して探そうとしたのですが、averageデータのほうは実際は 少ない場合はデータがないのでエラーになっていたんですね。 firefoxでundefinedというのはそういうことだったのですね function searchAverageData(data, name) { // 配列の配列 data から、data[i][0] == name を探す ... return 平均得点; // もし、見つからなかったら -1 を返す } 上の関数で、データを見つけて取ってくるやり方(引数というやつでしょうか) を是非トライしてみます。 ありがとうございます。
関連質問

●質問をもっと探す●



0.人力検索はてなトップ
8.このページを友達に紹介
9.このページの先頭へ
対応機種一覧
お問い合わせ
ヘルプ/お知らせ
ログイン
無料ユーザー登録
はてなトップ