web-dev-qa-db-ja.com

JavaScriptのreduce関数を使用した配列の並べ替え

多くの場合、いくつかのJavaScriptインタビューの質問を勉強しますが、突然reduceをソートするためのArray関数の使用法に関する質問を見ました。それについては [〜# 〜] mdn [〜#〜] およびいくつかのmedium記事での使用法ですが、Arrayのソートは非常に革新的です。

const arr = [91,4,6,24,8,7,59,3,13,0,11,98,54,23,52,87,4];

私はたくさん考えましたが、この質問にどのように答えるか、reducecall back関数はどうあるべきかわかりません。 initialValue関数のreduceとは何ですか? accumulatorcall back関数のcurrentValuereduceは何ですか?

そして最後に、この方法には他のソートアルゴリズムよりもいくつかの利点がありますか?または、他のアルゴリズムを改善することは有用ですか?

19
AmerllicA

ここでreduceを使用する意味はありませんが、新しい配列をアキュムレーターとして使用し、すべての要素で挿入ソートを実行できます。

array.reduce((sorted, el) => {
  let index = 0;
  while(index < array.length && el < array[index]) index++;
  sorted.splice(index, 0, el);
  return sorted;
}, []);

バージョンは次のとおりですwithout reduce:

array.sort((a, b) => a - b);

レデューサーを作成するための一般的なヒント:

コールバックの削減機能はどうすればよいですか?

アキュムレータでアプローチするか、レデューサーが現在の要素に基づいてアキュムレータに変更を適用し、それを返す必要があります。

(acc, el) => acc

または、アキュムレーターと要素が同じタイプで論理的に等しい場合、それらを区別する必要はありません。

 (a, b) => a + b

reduce関数のinitialValueは何ですか?

自問する必要があります"空の配列に適用された場合のリターンを減らすには何が必要ですか?"

今最も重要なのは、reduceを使用するタイミングですか? (IMO)

配列の値を1つの値またはオブジェクトにまとめたい場合。

19
Jonas Wilms

Array.sortは配列を変更しますが、Array.reduceを使用すると純粋な関数が推奨されます。並べ替える前に配列を複製できます。

この質問は、制約を強制することにより、あなたが異なる考え方をするように設計されていると思います。 reduceがどのように機能するかについての知識をテストし、答えが示すように、猫の皮をむく多くの方法があります。これを解決する上で、jsの個人的なフレーバーが示されます。

Array.findIndexArray.spliceを使用することにしました。

const sortingReducer = (accumulator, value) => {
  const nextIndex = accumulator.findIndex(i => value < i );
  const index = nextIndex > -1 ? nextIndex : accumulator.length;
  accumulator.splice(index, 0, value);
  return accumulator;
}

const input = [5,4,9,1];
const output = input.reduce(sortingReducer, []);

サンプル入力でテストすると、

arr.reduce(sortingReducer, [])
// (17) [0, 3, 4, 4, 6, 7, 8, 11, 13, 23, 24, 52, 54, 59, 87, 91, 98]
6
Lex

sortingの例は、reduce関数を使用した降順の配列です。

reduce関数のinitialValueは何ですか

以下の関数では、初期値は[]は、reduce関数でthisArgとして渡されます。

 array.reduce(function(acc,curr,currIndex,array){
        //rest of the code here
        },[]//this is initial value also known as thisArg)

したがって、基本的に空の配列が渡され、要素はこの配列にプッシュされます

ここでのアキュムレーターは空の配列です。

const arr = [91, 4, 6, 24, 8, 7, 59, 3, 13, 0, 11, 98, 54, 23, 52, 87, 4];
var m = arr.reduce(function(acc, cur) {
  // this arrVar will have the initial array
  let arrVar = arr;
  // get the max element from the array using Math.max
  // ... is spread operator
  var getMaxElem = Math.max(...arrVar);
  // in the accumulator we are pushing the max value
  acc.Push(getMaxElem);
  // now need to remove the max value from the array, so that next time it 
  // shouldn't not be considered
  // splice will return a new array
  // now the arrVar is a new array and it does not contain the current
  // max value
  arrVar = arrVar.splice(arrVar.indexOf(getMaxElem), 1, '')
  return acc;
}, []);
console.log(m)
2
brk

Jonas W's 挿入ソートソリューションの(imo)よりエレガントなバージョンを次に示します。コールバックは、すべての低い値、新しい値、すべての高い値の新しい配列を作成するだけです。明示的なループまたはインデックスの使用を避けるため、正しく機能していることが一目でわかりやすくなります。

const insertValue = (arr, value) =>
  [...arr.filter(n => n <= value), value, ...arr.filter(n => n > value)]

const testArr = [91, 4, 6, 24, 8, 7, 59, 3, 13, 0, 11, 98, 54, 23, 52, 87, 4]

console.log(testArr.reduce(insertValue, []))
1
JollyJoker

reduceは、自分自身をオンラインソートアルゴリズムに制約します。ここで、配列の各要素は1回だけ表示され、配列の長さは事前に設定されていません(クロージャを使用すると、還元機能により多くの情報を与えることができますが、この種の質問の目的は無効になります)。

挿入ソートは明白で簡単な例です。この点で他の回答はすでに非常に良いので、実装の詳細は説明しません。ただし、インタビュアーにとって非常に良いと思われるいくつかの最適化について言及できます。

  • バイナリ検索を使用して挿入ポイントを見つけると、挿入ステップの複雑さをO(n)からO(log n)に減らすことができます。これはバイナリ挿入ソートと呼ばれます。比較の総数はO(n ^ 2)からO(n log n)に移動します。実際のコストは出力配列の「スプライシング」によるものなので、高速ではありませんが、高価な比較(たとえば、長い文字列の並べ替え)がある場合違いを生む可能性があります。

  • 整数をソートする場合は、 radix sort を使用して、reduceで線形オンラインソートアルゴリズムを実装できます。これは実装するのが非常に難しいですが、削減には非常に適しています。

1
Maxim

ただし、reduceは並べ替えには理想的ではありません。次の解決策は、forEachまたはArray.reduce関数で達成しようとするループ関数に似ています。

var arr = [91,4,6,24,8,7,59,3,13,0,11,98,54,23,52,87,4];

arr = arr.reduce(function(acc,val){
  
  if(acc.length) {
    var temp = [];
    while(acc[acc.length -1] > val) {
      temp.Push(acc.pop());
    } 
    acc.Push(val);
    while(temp.length) {
      acc.Push(temp.pop());
    }
  } else {
    acc.Push(val);  
  }
  return acc;
  
}, []);

console.log(arr);

ネイティブ関数 Array.sort を使用して並べ替えることができ、独自の並べ替えアルゴリズムを定義できる独自の並べ替え関数を持つこともできます。

0
Nikhil Aggarwal

挿入ソートを使用できます:

let array        = [91, 4, 6, 24, 8, 7, 59, 3, 13, 0, 11, 98, 54, 23, 52, 87, 4];
let countIfLess  = (array,v)=> array.reduce((c,n)=>n<v?c+1:c,0);
let countIfEqual = (array,v)=> array.reduce((c,n)=>n==v?c+1:c,0);

console.log(
  array.reduce(
    (a,v,i,array)=>( a[countIfLess(array,v) + countIfEqual(a,v)]=v, a ),
    new Array(array.length)
  )
);

これにより、宛先配列が一度作成され、宛先配列を再作成することなく、reduceの各ステップで挿入が実行されます。

countIfEqualを実装するより効率的な方法がありますが、他の配列関数ではなくreduceを使用してすべての関数を実装することにしました。

0
MT0

配列が提供されていない場合、配列を取得するためのいくつかの関数、2つのパラメーターを取得してソートされた配列を返す関数、およびソートされた配列の一部の結果を取得する別のreduceメソッドを使用して上記を含むソートコールバックを使用できます。

const
    getArray = a => Array.isArray(a) ? a : [a],
    order = (a, b) => a < b ? [a, b] : [b, a],
    sort = (a, b) => getArray(a).reduce((r, v) => r.concat(order(r.pop(), v)), [b]),
    array = [91, 4, 6, 24, 8, 7, 59, 3, 13, 0, 11, 98, 54, 23, 52, 87, 4];

console.log(array.reduce(sort));
.as-console-wrapper { max-height: 100% !important; top: 0; }
0
Nina Scholz