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

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

新規登録して質問してみよう
ただいま回答率
85.48%
ECMAScript

ECMAScriptとは、JavaScript類の標準を定めるために作られたスクリプト言語です。

Lodash

Lodashは、JavaScriptのユーティリティライブラリ。Underscoreの派生ライブラリで、配列・オブジェクトの操作に便利です。また、コードの可読性も高めることができます。

JavaScript

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

Q&A

解決済

1回答

1789閲覧

よりスマートかつ高パフォーマンスに期待値を得たい

退会済みユーザー

退会済みユーザー

総合スコア0

ECMAScript

ECMAScriptとは、JavaScript類の標準を定めるために作られたスクリプト言語です。

Lodash

Lodashは、JavaScriptのユーティリティライブラリ。Underscoreの派生ライブラリで、配列・オブジェクトの操作に便利です。また、コードの可読性も高めることができます。

JavaScript

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

0グッド

0クリップ

投稿2018/07/19 10:10

前提・実現したいこと

※初心者ながら自分なりにコードを書いて目的(やりたいこと)は達成できています。

具体的なモデルがあったほうがわかりやすいと思うので、まずそちらを共有させていただきます。
以下サロン予約サイトです。
https://beauty.hotpepper.jp/slnH000314178/coupon/

サロン予約サイトに掲載されているサロンは複数のメニュー(子)を持っています。
さらに、そのメニューは、複数のオプション(孫)を持っています。このことから
サロンのメニューは複数のオプション(子)を持っている = サロンは複数のオプション(孫)を持っている
と定義出来るかと思います。
システム的なマスタデータは後者のサロンのオプションとなります。
データストアはRDBでなくNoSQLなので、冗長(非正規)な構成となり、参照カウント(何個のメニューに参照されているか)をマスタデータに持っています。
これがシステム上で扱うデータモデルの前提となります。

ここでイメージ図です。

  • サロンのメニューは複数のオプション(子)を持っている
サロン  ├ メニューA  │  ├ オプションA  │  └ オプションB  ├ メニューB  │  ├ オプションA  │  ├ オプションB  │  └ オプションC  ︙
  • サロンは複数のオプション(孫)を持っている
サロン  ├ オプションA、参照カウント=2(2つのメニューに参照されている状態)  ├ オプションB、参照カウント=2(2つのメニューに参照されている状態)  └ オプションC、参照カウント=1(1つのメニューに参照されている状態)

オプションの具体的な例としては、カラー(髪を染める)というメニューであれば、金髪、茶髪等の選択肢が該当します。

そして、上記データモデルを前提とした**システム的な主要件は「メニュー登録・編集画面でオプションを追加・更新・削除できて、それがマスタデータと同期されていること」**です。
つまり、メニュー登録・編集画面で以下のようなことを実現したいです。

  • サロンのオプションからオプションを選択して、メニューのオプションに追加できる

その際、マスタデータの参照カウントをインクリメントする。
※今回こちらに該当するコードは除外しています

  • サロンのオプションに希望のオプションがなければ、メニューのオプションとして新規作成・追加できる。

その際、新規作成したオプションを参照カウント=1としてマスタデータにも反映する。

  • メニューのオプションを更新できる。

その際、マスタデータにその更新後の値が存在しなければ、メニューのオプションとして新規作成・追加し参照カウント=1としてマスタデータにも反映する。

  • メニューのオプションを削除できる。

その際、他メニューから参照されていなければ、マスタデータからも削除する。他メニューから参照されていれば、マスタデータの参照カウントをデクリメントする。

あとは、ソースコード見ながらのほうが説明しやすいので、ところどころソースコード中にコメントを入れてます。

発生している問題・エラーメッセージ

以下期待値を得るため、ライブラリ等使わずJavascriptのみでコーディングを試みましたが、悪戦苦闘した結果、ライブラリ(Lodash)を使うことにしました。

なんとか自力で書いた(自分の中で最善)コードを書きましたが、もっとスマートかつ高パフォーマンスに以下の期待値を得たいです。

周りにレビュアーがおらず、Teratailだけが頼りです。

Javascript

1// 期待値 2const expectedSalon = { 3 name: 'い~サロン', 4 menus: [ 5 { 6 name: 'カット', 7 // サロンのメニューは複数のオプション(子)を持っている 8 options: [ 9 {name: 'オプション1', price: 100}, 10 {name: 'オプション2', price: 200}, 11 {name: 'オプション3', price: 300} 12 ] 13 // ︙(省略) 14 }, 15 { 16 name: 'カラー', 17 // サロンのメニューは複数のオプション(子)を持っている 18 options: [ 19 {name: 'オプション1', price: 100}, 20 {name: 'オプション2', price: 222}, 21 {name: 'オプション3', price: 300}, 22 {name: 'オプション5', price: 500} 23 ] 24 // ︙(省略) 25 } 26 ], 27 // サロンは複数のオプション(孫)を持っている 28 menuOptions: [ 29 {name: 'オプション1', price: 100, refCount: 2}, 30 {name: 'オプション2', price: 200, refCount: 1}, 31 {name: 'オプション2', price: 222, refCount: 1}, 32 {name: 'オプション3', price: 300, refCount: 1}, 33 {name: 'オプション5', price: 500, refCount: 1}, 34 ] 35 // ︙(省略) 36};

該当のソースコード

Javascript

1// 初期(更新前)データ 2const salon = { 3 name: 'い~サロン', 4 menus: [ 5 { 6 name: 'カット', 7 // サロンのメニューは複数のオプション(子)を持っている 8 options: [ 9 {name: 'オプション1', price: 100}, 10 {name: 'オプション2', price: 200}, 11 {name: 'オプション3', price: 300} 12 ] 13 // ︙(省略) 14 }, 15 { 16 name: 'カラー', 17 // サロンのメニューは複数のオプション(子)を持っている 18 options: [ 19 {name: 'オプション1', price: 100}, 20 {name: 'オプション2', price: 200}, 21 {name: 'オプション3', price: 300}, 22 {name: 'オプション4', price: 400} 23 ] 24 // ︙(省略) 25 } 26 ], 27 // サロンは複数のオプション(孫)を持っている 28 menuOptions: [ 29 {name: 'オプション1', price: 100, refCount: 2}, 30 {name: 'オプション2', price: 200, refCount: 2}, 31 {name: 'オプション3', price: 300, refCount: 2}, 32 {name: 'オプション4', price: 400, refCount: 1} 33 ] 34 // ︙(省略) 35}; 36 37// カラーメニューを画面から更新(=Postされたデータ) 38// 更新内容: オプション1はそのまま、オプション2を更新、オプション3とオプション4を削除、オプション5が2つ追加 39const updatedColorMenu = { 40 name:'カラー', 41 options: [ 42 {name: 'オプション1', price: 100}, 43 {name: 'オプション2', price: 222}, 44 {name: 'オプション5', price: 500}, 45 {name: 'オプション5', price: 500} 46 ] 47 // ︙(省略) 48} 49 50// ↓↓↓↓↓以降がPost後の処理↓↓↓↓↓ 51// 追加されたオプションを抽出 52const addedOptions = _.uniqWith( 53 _.differenceWith(updatedColorMenu.options, salon.menuOptions, (a, b) => { 54 return _.isEqual( 55 a, 56 _.omit(b, ['refCount']) 57 ); 58 } 59), _.isEqual); 60console.log(addedOptions); 61 62// 削除されたオプションを抽出 63const removedOptions = _.differenceWith(salon.menuOptions, updatedColorMenu.options, (a, b) => { 64 return _.isEqual( 65 _.omit(a, ['refCount']), 66 b 67 ); 68 } 69); 70console.log(removedOptions); 71 72// マスターとなるオプションを生成 73let masterOptions = []; 74// 削除されたオプションをマスターから削除 75salon.menuOptions.forEach(x => { 76 if (_.some(removedOptions, x)) { 77 if (x.refCount > 1) { 78 x.refCount = x.refCount - 1; 79 masterOptions.push(x); 80 } 81 } else { 82 masterOptions.push(x); 83 } 84}); 85// 追加されたオプションをマスターに追加 86addedOptions.forEach(x => { 87 x.refCount = 1; 88 masterOptions.push(x); 89}); 90 91console.log(masterOptions);

試したこと

上記の通り、何とか自力で期待値と一致させることができました。

補足情報(FW/ツールのバージョンなど)

Lodashを使っています。
余分なプロパティは可読性の観点から省略しています。
データストアはRDBでなくNoSQLです

気になる質問をクリップする

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

m.ts10806

2018/07/19 11:02

達成基準が不明瞭なので回答も付きにくいと思いますよ。要件によってコード丸々書き直す必要が出てくるでしょうし。リファクタリング依頼となると「作業依頼」となり非推奨です。特に気になる箇所だけ抜き出してそこだけにフォーカスした質問にするとか、質問の仕方を工夫されたほうが良いです。
guest

回答1

0

ベストアンサー

まずは概念的な所に触れます。
パフォーマンスだけ見るのであれば、Lodashは投げ捨ててfor...of文を回しましょう。

しかし、質問文と与えられた初期値を読む限り、初期のデータが変というか駄目なんじゃないですかね?
初期データが複雑で変な形式をしているから、
Lodashをこねくり回してアレやらコレやらのデータを突合させる必要がある。

変なデータをこねくり回すより、シンプルなデータを使って小学生でも出来るような計算でさっさと解決する。
こういうアプローチもとても重要ですので一度見直してみてください。

また、インクリメントといったワードが質問文に入ってきている所を見るに、
質問者さんのやりたいことはjQueryではなく、Vue.jsなんかのJSフレームワークを覚えれば一気に解決する可能性があります。
JSフレームワークを使っていれば表示の為に数値のインクリメントといった発想に至る事は殆どありません。


さて、質問文自体の回答に入っていきます。

わかり易さを考慮するのであればチェーン記法で書き直せる箇所は多いのでは?
参考サイト: Lodashのチェーン記法について - Qiita

differenceWithをチェーン記法に持ってこれるかどうかはちょっと怪しいのですが、
サンプルとしてaddedOptionsをチェーン記法にしてみました。
動作検証してみてください。

JavaScript

1const addedOptions = _.chain(updatedColorMenu.options) 2 .differenceWith(salon.menuOptions, (a, b) => _.chain(b) 3 .omit(['refCount']) 4 .isEqual(a) 5 .value() 6 ) 7 .uniqWith(_.isEqual) 8 .value() 9console.log(addedOptions);

※データが歯抜け過ぎるしネストされすぎで、回答文のコードは検証出来てません。
もし回答文のコードが誤っており、正確なコードが欲しい場合、A -> Bを記述するように質問文を修正してください。


質問文のコードはやりたいことが右に左に行ってしまう上、
カッコのネストで凄まじく読みづらいですが、
チェーン記法であればやりたい事が直列繋ぎにして書けるので、慣れればかなり読みやすくなります。
チェーン記法はLodashの存在意義と言えるでしょう、是非覚えて使いこなしましょう。

このメソッドチェーンで繋ぎまくるプログラミングは途中の計算結果が吸い出せず、
これホンマに合ってるんかな???でも検証し辛いし困った…となるかと思います。
その時はtapthruのメソッドが役に立つはずです。

JavaScript

1const users = [ 2 {name: 'taro', score: 80}, 3 {name: 'jiro', score: 85}, 4 {name: 'saburo', score: 90}, 5]; 6const eighty = _.chain(users) 7 .filter(user => user.score >= 80) 8 // この時点の計算結果をconsole.logに吐き出して確認したいんだけど… 9 .tap(console.log) 10 .filter(user => user.score < 90) 11 .value(); 12console.log(middle);

console.logは引数をコンソールに書き出してundefinedを返すメソッドです。
なので、メソッドチェーンで繋ぐプログラミングとはすこぶる相性が悪いのですが、
.tapという関数の中に自分を渡して起動させますが、戻り値は捨ててそのまま次で行くという投げっぱなしの処理を行う為のメソッドが存在します。
まさに計算途中の値をconsole.logalertで確認する為に作られたメソッドと言っても過言ではないでしょう。

.thru.tapの様に関数を発火させますが、変更後の戻り値を使って次の処理に進む関数です。
計算結果をちょっと補正したい時に威力を発揮するので使って見てくださいね。

投稿2018/07/19 16:32

編集2018/07/19 17:05
miyabi-sun

総合スコア21158

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2018/07/31 08:01

ありがとうございます! データ設計を見直しつつ、教えていただいた記法を参考にさせていただきます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問