web-dev-qa-db-ja.com

javascriptのforループ内のforループを回避する方法

正常に機能するコードを作成しました。 orderArrで指定された順序でmyArrの要素で構成される新しい配列が必要です。ただし、配列要素と一致させるために、別のforループ内のforループを使用します。

var myArr = ['a', 'b', 'c', 'd', 'e'];
var orderArr = ['e', 'c'];
var reArr = [];

for (var i = 0; i < orderArr.length; i++) {
  for (var k = 0; k < myArr.length; k++) {
    if (myArr[k] == orderArr[i]) {
      reArr.Push(myArr[k]);
    }
  }
}

console.log(reArr);

別のforループ内でforループを使用することは悪い習慣であり、forEachでさえ避けるべきだとよく耳にします。

このコードを他にどのように書き直すことができますか。

6
Stacy J

ループ内でループを使用することが悪い習慣であるとは必ずしも言えません。実際、Ori Droriは、そのような実践の効率は単にデータセットのサイズに依存する可能性があると述べて私を殴りました。

たとえば、 ソートアルゴリズム の実装は多数あり、ループ内にループが見つかることがよくあります。ただし、実装の詳細は、データサイズが変更されるとパフォーマンスに影響します。

私自身の個人的な経験では、ネストされたループを見ると、実行している操作を別のアルゴリズムで最適化する必要があるかどうかをすぐに自問します。多くの場合、JavaScript(ブラウザー)アプリケーションでは、データサイズが影響力のある違いを生むほど大きくなることはめったにないため、答えは「いいえ」です。

5
arthurakay

あなたの場合、ネストされたループを持つことの複雑さは、O(n * m)-nはorderArrの長さ、mはmyArrの長さです。

このソリューションの複雑さはO(n + m)です。これは、O(m)の複雑さで Array#reduce を使用して辞書オブジェクトを作成し、次に filteringorderArrayを使用して辞書オブジェクトを作成しているためです。 O(n)の複雑さ。

注1:小さな配列の場合、これは実際には問題ではありません。したがって、両方の配列に数千がない限り、ネストされたループにとどまることができます。

注2:このコードは、myArrに重複がないことを前提としています。重複がある場合、結果はネストされたループの結果とは異なります。

var myArr = ['a', 'b', 'c', 'd', 'e'];
var orderArr = ['e', 'c'];
var myArrDict = myArr.reduce(function(r, s) {
  r[s] = true;
  return r;
}, Object.create(null));

var reArr = orderArr.filter(function(s) {
  return this[s];
}, myArrDict);

console.log(reArr);
7
Ori Drori

個人的には、この特定のシナリオ専用のcorrelate関数が必要です。

"use strict";

function correlate(
  outer,
  inner, 
  outerKeyOrKeySelector = x => x,
  innerKeyOrKeySelector = x => x
) {
  const outerValues = [...outer];
  const innerValues = [...inner];
  const outerToKey = typeof outerKeyOrKeySelector === 'function'
      ? outerKeyOrKeySelector
      : (x => x[outerKeyOrKeySelector]);

  const innerToKey = typeof innerKeyOrKeySelector === 'function'
      ? innerKeyOrKeySelector
      : (x => x[innerKeyOrKeySelector]);

  const outerKeyed = outerValues.map(value => ({key: outerToKey(value), value});
  const innerKeyed = innerValues.map(value => ({key: innerToKey(value), value});

  return outerKeyed.reduce((results, {key: oKey, value: oValue}) => [
     ...results,
     ...innerKeyed
         .filter(({key: iKey}) => oKey === iKey)
         .map(({value: iValue}) => [oValue, iValue])
    ], []);
}

これは基本的にJOINのように機能し、プロパティの値または任意の射影関数に基づいて2つの配列を相関させることができます。

やり過ぎのように思えるかもしれませんが、YMMVは非常に便利です。

手元の問題に適用:

reArr = correlate(orderArr, myArr).map(([first, second]) => first);

しかし、複雑なシナリオも同じように簡単に処理できます

reArr = correlate(orders, customers, o => o.customer.name, c => c.name)
  .map(([first, second]) => {order: first, customer: second})
  .forEach(({customer, order}) => {
    customer.orders = [...customer.orders, order];
  });
1
Aluan Haddad

ここでの良い答えのいくつか(特にOriの)を補完するために、2つの非常に大きな配列の最適な解決策はマージ結合であると思います(これはリレーショナルデータベースがケースを処理する方法です。ここでの考え方は、値を比較するだけです。私たちはほぼ同じだと知っています。

最初のステップは、両方の配列(O(n log n)+ O(m log m))を並べ替えてから、各配列の最初の値を比較することです。 3つの可能性があります:

A<B
  We discard A and fetch the next value from the same list A came from
A=B
  We add A to the keep list and retrieve the next value from that array
A>B
  We discard B and retrieve the next value from its list

...そしてリストの1つが使い果たされるまでプロセスを繰り返します...

 O(n log n) + O(m log m) + O(least(n,m))
1
symcbean

非常に単純なES5バージョンの1つを次に示します。値がオブジェクトでない場合、内部ループ回避機能です。

var myArr = ['a', 'b', 'c', 'd', 'e'];
var orderArr = ['e', 'c'];
var myArrIndex = {};
var reArr = [];

for (var i = 0; i < myArr.length; i++) {
    myArrIndex[myArr[i]] = myArr[i];
};

for (var i = 0; i < orderArr.length; i++) {
    var orderArrayItem = orderArr[i];
    if (myArrIndex[orderArrayItem]) {
        reArr.Push(orderArrayItem);
    }
}


console.log(reArr);
1
bhantol

あなたができることは、2つの配列を組み合わせて、次のようにreduceを使用して重複以外のすべてを削除することです。

var myArr = ['a', 'b', 'c', 'd', 'e'];
var orderArr = ['e', 'c'];
// Merge the two arrays
var reArr = myArr.concat(orderArr);

let result = reArr.reduce((arr, val) => {
  // return only equal values that are not already in the new array
  // If the value is in the new array 'indexOf()' will be greater than -1
  // we will then not modify the current array
  //
  // If the value is not in the new array and is equal to the current lookup 
  // then add it to the filter. Once filtered, we can check
  // if the filter contains more than one item if it does
  // then we can add it to the new array
  return reArr.filter(v => v == val && arr.indexOf(val) == -1).length > 1 
    // If it is not in the new array add it using 'concat()'
    // otherwise we will return the current array as is
    ? arr.concat(val) : arr
}, [])

console.log(result);
1
Get Off My Lawn

私はあなたのコードに何か問題があるとは思いませんが、本当にしたいのであれば、これで(あなたに見える)ループを取り除くことができます:

var reArr = myArr.filter((n) => orderArr.includes(n));

(古いバージョンのIEでは機能しません)。

0
SBFrancies

myArrをオブジェクトにマップします。 myArrに「a」を追加します。orderArrに「f」を追加します。

var myArr = ['a', 'a', 'b', 'c', 'd', 'e'];
var orderArr = ['e', 'c', 'f']
var myObj = createObjectFrom(myArr);
var reArr = [];

function createObjectFrom(array) {
   var obj = {};
   var arrValue;

   for (var i = 0; i < array.length; i++) {
     arrValue = array[i];

     if (obj[arrValue]) {
       obj[arrValue]++;
     } else {
       obj[arrValue] = 1;
     }
   }

   return obj;  // { 'a': 2, 'b': 1, 'c': 1, 'd': 1, 'e': 1 }
}

var orderValue;

for(var i=0;i<orderArr.length;i++){
  orderValue = orderArr[i];

  if (myObj.hasOwnProperty(orderValue)) {
     for (var j = 0; j < myObj[orderValue]; j++) {
       reArr.Push(orderValue);
     }
   }
}

console.log(reArr);  // [ "c", "e" ]

(6 * 3 =)を18回ループする代わりに、(6 + 3 =)を9回ループするだけで済みます。

重複を考慮に入れるためにコードを少し複雑にしました。したがって、2番目のforループ(j)です。


追加のボーナスとして、これはあなたが見るのに面白いと思うビデオです:

https://www.youtube.com/watch?v=XKu_SEDAykw

0

Map を取り、後で結果セットと連結するために、すべて同じ値を配列に収集することができます。

var array = ['a', 'b', 'c', 'd', 'e'],
    order = ['e', 'c'],
    map = new Map,
    result;
    
array.forEach(v => map.set(v, (map.get(v) || []).concat(v)));

result = order.reduce((r, v) => r.concat(map.get(v)), []);

console.log(result);
0
Nina Scholz