数値を降順で並べ替えますが、先頭は「0」です


36

私はJavaScriptの課題を抱えていますが、しばらくの間、もう少し理解しようとしています。

この配列を考えてみましょう:

let arr = [0, 1, 0, 2, 0, 3, 0, 4, 0, 5];

この結果を出力する必要があります:

arr = [0, 0, 0, 0, 0, 5, 4, 3, 2, 1]

次のロジック行に従って、ゼロを前に配置し、インデックス値を調整します。

arr.sort((x, y) => {
    if (x !== 0) {
        return 1;
    }

    if (x === 0) {
        return -1;
    }

    return y - x;
});

しかし、私はこの結果に行き詰まっています。

arr = [0, 0, 0, 0, 0, 1, 2, 3, 4, 5]

これを解決するためのヒントはありますか?


6
負の数がないことが保証されていますか?
マイケル-クレイシャーキーは

2
最後の行に切り替えると修正されませんreturn x - y;か?
Mooing Duck

2
JavaScriptでゼロをカウントして削除し、残りの要素を通常どおりに並べ替えることは効率的でしょうか?(カスタムコンパレータがないため、JSエンジンが組み込みの数値ソート関数を使用できることを願っています)。次に、結果に適切な数のゼロを付加します。ゼロが多い場合は、並べ替える前にゼロを削除すると、問題が小さくなります。または、1つのパスでゼロを配列の先頭にスワップしてから、配列の末尾を並べ替えますか?
Peter Cordes

2
これまでに到達することはありreturn y - x;ますか?javascriptでも、どちら===0でもないものは思いつきません!==0
ジョージT

2
回答のJSPerf
Salman A

回答:


35

band のデルタa(降順の並べ替えの場合)で並べ替えてNumber.MAX_VALUE、ゼロのような偽の値の場合はを取ることができます。

この:

Number.MAX_VALUE - Number.MAX_VALUE

ゼロに等しい。

let array = [0, 1, 0, 2, 0, 3, 0, 4, 0, 5];

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

console.log(...array);


13
比較関数はNaNaとの両方bがゼロの場合に戻ります。これは望ましくない動作である可能性があります。
dan04

20
の結果はArray.prototype.sort、コンパレータがを返す場合は実装で定義されるNaNため、このコンパレータはお勧めできません。それは賢いことを試み、それを間違ってしまいます。
user2357112はMonica

3
配列内の要素が無限大の場合はどうなりますか?比較関数が返す0 0に無限の数を比較
マルコ

4
皆さん、ありがとうございました。自分で減算できる最大の数を取得し、この数がopの配列で使用されていないことを願っています。
Nina Scholz、

4
絶対に読めない。かっこいいですが、判読できません。
Salman A

24

mdn docsが言うように:

aとbが比較される2つの要素の場合、次のようになります。

もしcompareFunction(a, b)戻っ未満0ソート、aインデックスへのより低いですb(つまりaが最初に来ます)。

もし compareFunction(a, b)0を返すは、aとbを互いに変更せずに、すべての異なる要素に関してソートします。注:ECMAscript標準はこの動作を保証しないため、すべてのブラウザー(たとえば、少なくとも2003年まで遡るMozillaバージョン)がこれを尊重するわけではありません。

compareFunction(a, b) 0より大きい値を返す場合はb、aより小さいインデックスにソートします(つまり、b最初に来る)。

compareFunction(a, b)要素の特定の組が与えられたとき、常に同じ値を返す必要がありますaし、bその2つの引数としてを。一貫性のない結果が返される場合、ソート順は未定義です。

したがって、比較関数は次の形式になります。

function compare(a, b) {
  if (a is less than b by some ordering criterion) {
    return -1;
  }
  if (a is greater than b by the ordering criterion) {
    return 1;
  }
  // a must be equal to b
  return 0;
}

let arr = [0, 1, 0, 2, 0, 3, 0, 4, 0, 5];

arr.sort((x, y) => {
    if (x > 0 && y > 0) {
        return y - x;
    }
    return x - y;
});

console.log(arr);


3
+1は、負の数を含むすべての入力に対して実際に一貫している(標準で定義されている)比較関数を使用した、これまでに唯一のソリューションであると思われるものを与えるため。
Ilmari Karonen

3
@IlmariKaronen:残念ながら、負の数の比較は一貫していますが、負の数の比較は間違っています。ネガティブはこのコンパレーターで0の前にソートされ、昇順でソートされます。
user2357112はMonica

2
@ user2357112supportsMonicaそれにもかかわらず、OPは負の数の望ましい動作を指定していません。このプロトタイプでは、OPの要件に合わせて負の数の動作を調整するのは簡単です。この答えの良い点は、それが慣用的であり、標準的な比較関数を使用して仕事をすることです。
J ...

13

効率を重視する場合は、最初にゼロを除外するのがおそらく最も高速です。あなたはsortそれらを見ても時間を無駄にしたくないし、その特別なケースを処理するために比較コールバックに余分な作業を追加することは言うまでもありません。

特に、かなりの数のゼロが予想される場合、データを1回通過させてフィルターで除外する方が、各ゼロを複数回参照するより大きなO(N log N)ソートを実行するよりもはるかに優れています。

次のことができ、効率的完了したら、適切な数のゼロ付加できます。

結果のコードを読むのも同じくらい簡単です。TypedArrayを使用したのは、効率的で数値の並べ替えが簡単になるためです。しかし、この手法は通常の配列で使用でき、(a,b)=>a-bforの標準的なイディオムを使用し.sortます。

let arr = [0, 1, 0, 2, 0, 3, 0, 4, 0, 5];

let nonzero_arr = Int32Array.from(arr.filter(n => n != 0));
let zcount = arr.length - nonzero_arr.length;
nonzero_arr.sort();      // numeric TypedArray sorts numerically, not alphabetically

// Reverse the sorted part before copying into the final array.
nonzero_arr.reverse();

 // efficient-ish TypedArray for main result
let revsorted = new Int32Array(arr.length);   // zero-filled full size
revsorted.set(nonzero_arr, zcount);           // copy after the right number of zeros

console.log(Array.from(revsorted));      // prints strangely for TypedArray, with invented "0", "1" keys

/*
   // regular Array result
let sorted = [...Array(zcount).fill(0), ...nonzero_arr]  // IDK if this is efficient
console.log(sorted);
*/

TypedArrayがあれば、私にはわからない.sort()し、次に.reverse速い降順でソートするカスタム比較関数を使用するよりもです。または、イテレータを使用してオンザフライでコピーおよびリバースできる場合。


検討する価値もあります:全長のTypedArrayを1つだけ使用してくださいます。。

を使用する代わりに.filter、ループしてループを実行し、配列の前にゼロを入れ替えます。これは、データを1回通過するだけです。

次に使用します .subarray()、同じ基になるArrayBufferのゼロ以外の要素の新しいTypedArrayビューを取得するします。並べ替えでは、開始点がゼロで末尾が並べ替えられた完全な配列が残ります。並べ替えでは、ゼロ以外の要素のみが表示されます。

ArrayまたはTypedArrayメソッドにパーティション関数は表示されませんでしたが、JavaScriptはほとんど知りません。優れたJITがあれば、ループが組み込みメソッドよりも悪くなることはありません。(特に、そのメソッドにのようなコールバックが含まれる場合.filter、およびrealloc縮小するために内部し、実際にフィルタリングする前に割り当てるメモリ量を把握する必要があります)。

TypedArray .filter() 変換する前に regular-Arrayを使用しました。入力がすでにTypedArrayである場合、この問題は発生しないため、この戦略はさらに魅力的になります。


これはおそらく、より単純で、かつ/またはより慣用的であるか、少なくともよりコンパクトである可能性があります。JSエンジンがコピーまたはゼロ初期化の多くを排除または最適化して管理する場合、またはTypedArrayメソッドの代わりにループを使用すると役立つ場合(たとえば、reverse + .setではなく逆方向にコピーする場合)。私はそれをスピードテストすることに慣れませんでした。誰かがそれをしたいのなら、私は興味があります。
Peter Cordes

@Salmanのテストによると、この答えは小さめの配列(約1000要素、そのうちの10%は0)に対してはがらくたのように動作します。おそらく、新しいアレイの作成はすべて問題があります。または、TypedArrayと通常の不注意な混合ですか?TypedArray内のインプレースパーティションで、すべて0と0以外のサブアレイの並べ替えを行うと、私の回答の2番目のセクションで提案されているように、はるかに優れている可能性があります。
Peter Cordes

8

このように比較関数の条件を変更するだけです-

let arr = [-1, 0, 1, 0, 2, -2, 0, 3, -3, 0, 4, -4, 0, 5, -5];
arr.sort((a, b) => {
   if(a && b) return b-a;
   if(!a && !b) return 0;
   return !a ? -1 : 1;
});

console.log(arr);


6
入力のいずれかが負になる可能性がある場合、これは一貫したコンパレータではありません。前にソート1の前に、あなたのコンパレータに従って、0種類-1 0の前にソート
イルマリKaronen

負の入力で更新
Harunur Ra​​shid

@SalmanA、両方がゼロの場合!aでも、条件は真です。戻る-1
Harunur Ra​​shid

私は大したことはaとbの両方がゼロだとは思わない@SalmanA
Harunur Ra​​shid

1
正確には、私は答えを編集しました。条件を追加しましたa=b=0
Harunur Ra​​shid

6

ここでコードゴルフをしない:

let arr = [0, 1, 0, 2, 0, 3, 0, 4, 0, 5, -1];
arr.sort(function(a, b) {
  if (a === 0 && b !== 0) {
    // a is zero b is nonzero, a goes first
    return -1;
  } else if (a !== 0 && b === 0) {
    // a is nonzero b is zero, b goes first
    return 1;
  } else {
    // both are zero or both are nonzero, sort descending
    return b - a;
  }
});
console.log(arr.toString());


5

すでに存在する場合は、独自の数値ソートを記述しないでください。あなたがしたいのはまさにあなたがタイトルで言うことです。先頭のゼロ以外の降順で数値を並べ替えます。

const zeroSort = arr => [...arr.filter(n => n == 0),
                         ...new Float64Array(arr.filter(n => n != 0)).sort().reverse()];

console.log(zeroSort([0, 1, 0, 2, 0, 3, 0, 4, 0, 500]));

不要なコードは記述しないでください。あなたはそれを誤解するかもしれません。

配列で処理する数値のタイプに基づいてTypedArrayを選択します。Float64は、すべての通常のJS番号を処理するため、適切なデフォルトです。


@IlmariKaronenベター?
JollyJoker

2回フィルタリングする必要はありません。私の答えが示すように、一度フィルタリングして長さを引くと、の数値を取得できますArray(n).fill(0)
Peter Cordes

@PeterCordesそれはもっと単純に呼ばれるかもしれませんが、コードは読みにくくなるほど長くなると思います
JollyJoker

はい、効率を上げるにはtmp変数に1行追加する必要があります。CとC ++とアセンブリ言語に慣れているため、私の答えは複数の余分な行を使用します。分離した行を増やすと、コンパイラー生成のasmを追跡して、最適化/プロファイリング時に責任のあるステートメントに簡単に戻ることができます。さらに、私は通常JSをしないので、すべてを数行に詰め込む必要はありませんでした。しかし、あなたが行うことができlet f64arr = new Float64Array(arr.filter(n => n != 0))、その後、[ ...Array(arr.length - f64arr.length).fill(0),それは1行を追加し、最後の行を簡素化しますので...。
Peter Cordes

4

これは次のようにして行うことができます:

let arr = [0, 1, 0, 2, 0, 3, 0, 4, 0, 5];

let result = arr.sort((a,b) => {
  if(a == 0 || b == 0)
    return a-b;
  return b-a;
})
console.log(result)

またはこれを行うことができます:

let arr = [0, 1, 0, 2, 0, 3, 0, 4, 0, 5];

let result = arr.sort().sort((a,b) => {
  if(a > 0 && b > 0)
    return b-a
  return 0
})

console.log(result)


4
最初の解決策は、負の入力に関する一貫性の問題がHarunur Ra​​shidの回答と同じです。2つ目一貫していますが、すべての負の数を0として扱い、相互のソート順は未定義のままにします。
Ilmari Karonen

-2

const myArray = [0, 1, 0, 2, 0, 3, 0, 4, 0, 5];
const splitArray = myArray.reduce((output, item) => {
     if(!item) output[0].push(item);
    else output[1].push(item);
    return output;
}, [[], []]);
const result = [...splitArray[0], ...splitArray[1].reverse()]
console.log(result);

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.