多くの場合、いくつかの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];
私はたくさん考えましたが、この質問にどのように答えるか、reduce
call back
関数はどうあるべきかわかりません。 initialValue
関数のreduce
とは何ですか? accumulator
のcall back
関数のcurrentValue
とreduce
は何ですか?
そして最後に、この方法には他のソートアルゴリズムよりもいくつかの利点がありますか?または、他のアルゴリズムを改善することは有用ですか?
ここで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つの値またはオブジェクトにまとめたい場合。
Array.sort
は配列を変更しますが、Array.reduce
を使用すると純粋な関数が推奨されます。並べ替える前に配列を複製できます。
この質問は、制約を強制することにより、あなたが異なる考え方をするように設計されていると思います。 reduce
がどのように機能するかについての知識をテストし、答えが示すように、猫の皮をむく多くの方法があります。これを解決する上で、jsの個人的なフレーバーが示されます。
Array.findIndex
とArray.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]
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)
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, []))
reduce
は、自分自身をオンラインソートアルゴリズムに制約します。ここで、配列の各要素は1回だけ表示され、配列の長さは事前に設定されていません(クロージャを使用すると、還元機能により多くの情報を与えることができますが、この種の質問の目的は無効になります)。
挿入ソートは明白で簡単な例です。この点で他の回答はすでに非常に良いので、実装の詳細は説明しません。ただし、インタビュアーにとって非常に良いと思われるいくつかの最適化について言及できます。
バイナリ検索を使用して挿入ポイントを見つけると、挿入ステップの複雑さをO(n)からO(log n)に減らすことができます。これはバイナリ挿入ソートと呼ばれます。比較の総数はO(n ^ 2)からO(n log n)に移動します。実際のコストは出力配列の「スプライシング」によるものなので、高速ではありませんが、高価な比較(たとえば、長い文字列の並べ替え)がある場合違いを生む可能性があります。
整数をソートする場合は、 radix sort を使用して、reduceで線形オンラインソートアルゴリズムを実装できます。これは実装するのが非常に難しいですが、削減には非常に適しています。
ただし、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 を使用して並べ替えることができ、独自の並べ替えアルゴリズムを定義できる独自の並べ替え関数を持つこともできます。
挿入ソートを使用できます:
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
を使用してすべての関数を実装することにしました。
配列が提供されていない場合、配列を取得するためのいくつかの関数、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; }