tag:crieit.net,2005:https://crieit.net/tags/%E9%85%8D%E5%88%97/feed 「配列」の記事 - Crieit Crieitでタグ「配列」に投稿された最近の記事 2022-07-14T23:39:50+09:00 https://crieit.net/tags/%E9%85%8D%E5%88%97/feed tag:crieit.net,2005:PublicArticle/18239 2022-07-14T23:39:50+09:00 2022-07-14T23:39:50+09:00 https://crieit.net/posts/extract-array-of-diff-exclude-duplicated-between-two-arraies-by-javascript-20220715 JavaScript で互いにオブジェクトを要素に持つ配列2つを比較して、重複していない要素のみを取り出す <h2 id="経緯"><a href="#%E7%B5%8C%E7%B7%AF">経緯</a></h2> <p>JavaScript で、互いにオブジェクトを要素に持つ配列2つを比較して、重複していない要素のみを取り出す処理を試みたのでメモしておきます。</p> <h2 id="前提"><a href="#%E5%89%8D%E6%8F%90">前提</a></h2> <p>前提として、次のような2つの配列があったとします。</p> <ul> <li>互いにオブジェクトを要素として持つ配列</li> <li>配列2は配列1の子集合(サブセット)</li> </ul> <p>この2つの配列を比較して、配列1の中から<strong>配列2に含まれない要素のみ</strong>の配列を作りたい、と考えました。</p> <p>想定する最終結果も併せて付記しておきます。</p> <h3 id="配列1"><a href="#%E9%85%8D%E5%88%971">配列1</a></h3> <pre><code class="json">[ { "value": "value-0", "label": "Château d'If" }, { "value": "value-1", "label": "Marseille" }, { "value": "value-2", "label": "France" }, { "value": "value-3", "label": "Le Comte de Monte-Cristo" }, { "value": "value-4", "label": "Alexandre Dumas" }, { "value": "value-5", "label": "Rhino" }, { "value": "value-6", "label": "prison" }, { "value": "value-7", "label": "François I" }, { "value": "value-8", "label": "Jean-Baptiste Kléber" } ] </code></pre> <h3 id="配列2"><a href="#%E9%85%8D%E5%88%972">配列2</a></h3> <pre><code class="json">[ { "value": "value-0", "label": "Château d'If" }, { "value": "value-1", "label": "Le Comte de Monte-Cristo" }, { "value": "value-2", "label": "Alexandre Dumas" } ] </code></pre> <h3 id="得たい配列"><a href="#%E5%BE%97%E3%81%9F%E3%81%84%E9%85%8D%E5%88%97">得たい配列</a></h3> <pre><code class="json">[ { "value": "value-1", "label": "Marseille" }, { "value": "value-2", "label": "France" }, { "value": "value-5", "label": "Rhino" }, { "value": "value-6", "label": "prison" }, { "value": "value-7", "label": "François I" }, { "value": "value-8", "label": "Jean-Baptiste Kléber" } ] </code></pre> <p>「2つの配列で重複した要素を除去した配列を生成する」というのはいくつか記事を見かけました。</p> <p>しかし、それらは1次元配列のサンプルばかりだった上に、今回はさらに「要素の中の <code>label</code>キー の値で比較したい」という内容だったので、さらにハードルが上がりました。</p> <h2 id="コード"><a href="#%E3%82%B3%E3%83%BC%E3%83%89">コード</a></h2> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://www.nxworld.net/js-array-filter-snippets.html">JavaScript:filter()を使って配列内の重複要素を削除・取得したり、2つの配列から共通要素を取得する方法 - NxWorld</a></li> </ul> <p>最終的にはこちらのコードをベースにして作りました。</p> <p>サンプルは以下。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://arm-band.github.io/test-l-enfer-de-chateau-d-if/">Home - L'enfer de Chateau d'If</a></li> </ul> <pre><code class="javascript">// 配列 const Ar1 = [ // 略 ]; const Ar2 = [ // 略 ]; // 重複要素を除去した配列を返す関数 const getArraysDiff = (array1, array2) => { // 引数の各々の配列から label のみの配列を生成 const array1LabelArray = array1.map((itm) => { return itm.label; }); const array2LabelArray = array2.map((itm) => { return itm.label; }); // label のみの配列で比較 const arr1 = [...new Set(array1LabelArray)]; const arr2 = [...new Set(array2LabelArray)]; return [...arr1, ...arr2].filter((val) => { return !arr1.includes(val) || !arr2.includes(val); }); }; // 上述関数で重複除去した label の配列を得る const ChateuDiff = getArraysDiff(Ar1, Ar2); // filter メソッドで、元配列 から該当する label が存在する要素を除去する const enferChateuDiff = Ar1.filter((item) => { return ChateuDiff.includes(item.label); }); </code></pre> <p>ざっくりこのような処理で想定していた結果を得られました。</p> <h2 id="余談"><a href="#%E4%BD%99%E8%AB%87">余談</a></h2> <p>何故このようなことをしようかと思ったかというと、 <a target="_blank" rel="nofollow noopener" href="https://react-select.com/home">React Select</a> で <code>defaultCalue</code> で指定した選択済みの項目が選択候補にも上がってしまっていたので、除外しようとしたためでした。</p> <p>が、そもそも React Select 側は選択済み項目を除外する機能を元々持っていたので上述の処理は不要ということが分かったため、今回のコードは未使用となりました。</p> <p>ちなみに、この機能が働かなかった原因は API で取得した 値から 上述のような <code>label</code> と <code>value</code> のオブジェクトを生成するループ処理の際に、 <code>value</code> に ID の数値を振り方を間違えていたため、 React Select から「異なる値」として認識されてしまっていたためでした。</p> <p>また、仮に今回の処理をかけたとしても、初期表示では上手く選択済み項目が除外されますが、選択済み項目を削除した場合は選択可能な項目として再度選択肢に復活させる必要があるため、かなり手間がかかることが想定されたためオミットしていたと思います。</p> <p>……本当、 React Select 側に標準搭載されていて良かったです。</p> <h2 id="参考"><a href="#%E5%8F%82%E8%80%83">参考</a></h2> <h3 id="本題"><a href="#%E6%9C%AC%E9%A1%8C">本題</a></h3> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://www.nxworld.net/js-array-filter-snippets.html">JavaScript:filter()を使って配列内の重複要素を削除・取得したり、2つの配列から共通要素を取得する方法 - NxWorld</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://www.dkrk-blog.net/javascript/duplicate_an_array">配列同士で重複する値があるか確認する | grgr-dkrkのブログ</a></li> </ul> <h3 id="label のみの配列を作る"><a href="#label+%E3%81%AE%E3%81%BF%E3%81%AE%E9%85%8D%E5%88%97%E3%82%92%E4%BD%9C%E3%82%8B">label のみの配列を作る</a></h3> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/tsu_eng/items/57f9d3919bf175188033">JavaScriptで連想配列から特定のキーだけ抽出 - Qiita</a></li> </ul> <h3 id="配列操作"><a href="#%E9%85%8D%E5%88%97%E6%93%8D%E4%BD%9C">配列操作</a></h3> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/takeharu/items/d75f96f81ff83680013f">JavaScriptの配列の使い方まとめ。要素の追加,結合,取得,削除。 - Qiita</a></li> </ul> <h3 id="JSON のフォーマット"><a href="#JSON+%E3%81%AE%E3%83%95%E3%82%A9%E3%83%BC%E3%83%9E%E3%83%83%E3%83%88">JSON のフォーマット</a></h3> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/unsoluble_sugar/items/7df08527215ea92831a6">JSON.stringifyの出力結果を整形して可読性を向上させる - Qiita</a></li> </ul> arm-band tag:crieit.net,2005:PublicArticle/15566 2019-11-29T22:48:04+09:00 2019-11-29T22:48:04+09:00 https://crieit.net/posts/JavaScript JavaScript:オブジェクトが中に入った配列をコピーする <h2 id="はじめに"><a href="#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB">はじめに</a></h2> <p>こんなの、絶対みんな直面していると思うんだけど、調べても出てこないのは、ググり方が悪いのか、需要がないのか、みんな余裕でやってしまうのか、なんなんだ?</p> <h2 id="想定読者"><a href="#%E6%83%B3%E5%AE%9A%E8%AA%AD%E8%80%85">想定読者</a></h2> <p>下記の挙動を理解している人</p> <pre><code class="JavaScript">const array = [1,2,3]; const array2 = array; console.log(array); // → [1,2,3] array2[1] = 4; console.log(array); // → [1,4,3] </code></pre> <h2 id="配列をディープコピーしたい"><a href="#%E9%85%8D%E5%88%97%E3%82%92%E3%83%87%E3%82%A3%E3%83%BC%E3%83%97%E3%82%B3%E3%83%94%E3%83%BC%E3%81%97%E3%81%9F%E3%81%84">配列をディープコピーしたい</a></h2> <p>配列をコピーして別物として扱いたいとき、 = で代入してはいけないのは流石に分かるのだけど、殊更JavaScriptになると「あれ、どうやるんだっけ?」と毎回調べるハメになる。<br /> しかも、やれ<a target="_blank" rel="nofollow noopener" href="https://qiita.com/takahiro_itazuri/items/882d019f1d8215d1cb67">sliceを使え、concatを使え</a>だの、やれ<a target="_blank" rel="nofollow noopener" href="https://pisuke-code.com/js-correct-way-of-array-copy/">Array.fromを使え</a>だの、様々な方法があって、かつ、そいつら、ディープコピーのメソッドじゃないよね? <a target="_blank" rel="nofollow noopener" href="https://www.php.net/manual/ja/language.oop5.cloning.php">clone</a>とかないの?<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> という状況なのですが、文句を言っても仕方がないので、愚直にやるわけです。</p> <h2 id="配列の中身がオブジェクトだとうまくいかない"><a href="#%E9%85%8D%E5%88%97%E3%81%AE%E4%B8%AD%E8%BA%AB%E3%81%8C%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%A0%E3%81%A8%E3%81%86%E3%81%BE%E3%81%8F%E3%81%84%E3%81%8B%E3%81%AA%E3%81%84">配列の中身がオブジェクトだとうまくいかない</a></h2> <p>で、こんなコードを書いたわけですが、</p> <pre><code class="JavaScript">const array = [{a:1},{a:2}]; const array2 = array.slice(); console.log(JSON.parse(JSON.stringify(array))); // Array(2) // 0: {a: 1} // 1: {a: 2} array2[1].a = 3; console.log(JSON.parse(JSON.stringify(array))); // ??? // Array(2) // 0: {a: 1} // 1: {a: 3} </code></pre> <p>目を疑った。あれ、実はsliceはダメなのかな? とか思ってconcatもArray.fromも試したけどダメ。<br /> 1時間くらい悩んで、「ああ、配列はディープコピーされたけど、中のオブジェクトが参照のままなのか」と気がついた。</p> <pre><code class="JavaScript">const array = [{a:1},{a:2}]; const array2 = array.slice(); console.log(JSON.parse(JSON.stringify(array))); // Array(2) // 0: {a: 1} // 1: {a: 2} array2.push({a:3}); console.log(JSON.parse(JSON.stringify(array))); // 配列はディープコピーされている // Array(2) // 0: {a: 1} // 1: {a: 2} console.log(JSON.parse(JSON.stringify(array2))); // Array(3) // 0: {a: 1} // 1: {a: 2} // 2: {a: 3} </code></pre> <h2 id="解決策"><a href="#%E8%A7%A3%E6%B1%BA%E7%AD%96">解決策</a></h2> <p>え、これどーすんの? と少し悩んで、下記の様にした。</p> <pre><code class="JavaScript">const array = [{a:1},{a:2}]; const array2 = array.map((obj) => Object.assign({},obj)); console.log(JSON.parse(JSON.stringify(array))); // Array(2) // 0: {a: 1} // 1: {a: 2} array2.push({a:3}); console.log(JSON.parse(JSON.stringify(array))); // 配列はOK // Array(2) // 0: {a: 1} // 1: {a: 2} array2[1].a = 4; console.log(JSON.parse(JSON.stringify(array))); // 中のオブジェクトもディープコピーしている // Array(2) // 0: {a: 1} // 1: {a: 2} console.log(JSON.parse(JSON.stringify(array2))); // Array(3) // 0: {a: 1} // 1: {a: 4} // 2: {a: 3} </code></pre> <p>Array.map()の中でオブジェクトをObject.assign()でディープコピーして返している。mapは新しい配列を返すので、配列のコピーもOK。</p> <h2 id="ところで、オブジェクトのディープコピーって"><a href="#%E3%81%A8%E3%81%93%E3%82%8D%E3%81%A7%E3%80%81%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%AE%E3%83%87%E3%82%A3%E3%83%BC%E3%83%97%E3%82%B3%E3%83%94%E3%83%BC%E3%81%A3%E3%81%A6">ところで、オブジェクトのディープコピーって</a></h2> <p>オブジェクトのディープコピーも調べるといくつか出てくる。<br /> <a target="_blank" rel="nofollow noopener" href="https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/assign#Examples">Object.assign</a>ってのがあるんだけど、<a target="_blank" rel="nofollow noopener" href="https://kuroeveryday.blogspot.com/2017/05/deep-clone-object-in-javascript.html">Object.assignはシャローコピーなのでJSON.parse/JSON.stringify</a>って人もいれば、<a target="_blank" rel="nofollow noopener" href="https://qiita.com/seihmd/items/74fa9792d05278a2e898">JSON.parse/JSON.stringifyは安易に使うな</a>、<a target="_blank" rel="nofollow noopener" href="https://qiita.com/seihmd/items/74fa9792d05278a2e898#comment-7b97f37d119fb69712f2">Object.assignがディープコピーだからそっちを使え</a>っていう堂々巡りで、というか読んでいるとどっちもどっち感が強くて、状況に合わせて自力でどうにかするしかないじゃんっていう……。</p> <h2 id="おわりに"><a href="#%E3%81%8A%E3%82%8F%E3%82%8A%E3%81%AB">おわりに</a></h2> <p>もっと正しいスマートなやり方を教えてください。</p> <div class="footnotes" role="doc-endnotes"> <hr /> <ol> <li id="fn:1" role="doc-endnote"> <p>phpのcloneが配列に使えない(そして使う必要がない)のは知っているのだけれど、良い例が思い浮かばなかったのです。 <a href="#fnref:1" class="footnote-backref" role="doc-backlink">↩︎</a></p> </li> </ol> </div> hammhiko