自然なJavaScript文字列ソート

ソートの問題は、おそらくプログラマーが最も頻繁に解決する問題です。 広く普及しているアルゴリズムはそれほど多くないため、すべての言語とプラットフォームで長い間記述され、最適化されてきましたが、SortList()などのメソッドはソースコードでフラッシュします。 おそらく、私たちはそれぞれバブルソートを複数回書いており、なぜそれが最初に機能しなかったのか疑問に思いました。



ただし、これはソートアルゴリズムに関するものではなく、文字列比較方法に関するものです。 ここではすべてが簡単なように思えます-最初から最初の異なるキャラクターを比較してください。 そして、行に数字があれば? 次に、このソート( 辞書式 )はシーケンス['file2'、 'file10'、 'file1']を['file1'、 'file10'、 'file2']に変換します。 しかし、人はテキストを読むときに数字を知覚し、同じシーケンスが直観的に順序付けられ、次のようになります:['file1'、 'file2'、 'file10']。 このようなソートは、自然ソートと呼ばれます。



カットの下- 自転車用の詳細なJavaScriptアルゴリズム。 最適性や美しさを装うわけではありませんが、マルチパスの「額」の実装よりも優れています。







ソートアルゴリズム自体は記述しませんが、組み込みのArray.sort()メソッドを使用します。 したがって、「メイン関数」は次のようになります。

function naturalSort(stringArray) {

var arr = stringArray;

// ...

return arr.sort(compareStrings) //

}






文字列を比較するためにcompareStrings()関数を記述することは残っています。 これはさまざまな方法で行うことができますが、私は次のアプローチを採用しました:行内のテキストから数値を分離し、結果の配列を同じインデックスを持つ行(または数値)の最初の違いに移動します。 splitString()関数(それについては少し後で)は行を分離し、比較自体は次のようになります。

function compareStrings(str1, str2) {

//

var compare = function (a, b) {

return (a < b) ? -1 : (a > b) ? 1 : 0;

};

// ; splitString()

var parts1 = splitString(str1);

var parts2 = splitString(str2);

//

var minLength = Math.min(parts1.length, parts2.length);

for ( var i = 0; i < minLength; i++) {

//

}

//

// , "", , ""

return compare(parts1.length, parts2.length);

}






ただし、このアプローチではすべてがスムーズではありません:比較する必要があるほとんどの行は最初の文字とは異なります。つまり、splitString()は多くの不要な作業を行います。 ただし、これは遅延計算を使用して修正できます。splitString()は既製の配列ではなく、next()およびcount()メソッドを持つイテレータオブジェクトを返します。 JavaScriptのクロージャーのおかげで、実装はそれほど大きくありませんでした。

(投稿の最後にUPD

function splitString(str) {

var from = 0; // slice()

var index = 0; //

var count = 0; //

var splitter = {}; //

//

splitter.count = function () {

return count;

}

//

splitter.next = function () {

// - null

if (index === str.length) {

return null ;

}

//

while (++index) {

var currentIsDigit = isDigit(str.charAt(index - 1)); // isDigit()

var nextChar = str.charAt(index);

var currentIsLast = (index === str.length);

// - ,

// ( / )

var isBorder = currentIsLast ||

xor(currentIsDigit, isDigit(nextChar)); // xor()

if (isBorder) {

var part = str.slice(from, index);

from = index;

count++;

return {

IsNumber: currentIsDigit,

Value: currentIsDigit ? Number(part) : part

};

} // end if

} // end while

}; // end next()



return splitter;

}






補助関数-xor()およびisDigit()を作成します。

function xor(a, b) {

return a ? !b : b;

}



function isDigit(chr) {

var charCode = function (ch) {

return ch.charCodeAt(0);

};

var code = charCode(chr);

return (code >= charCode( '0' )) && (code <= charCode( '9' ));

}






次に、文字列比較関数compareStrings()に戻ります。 イテレータの導入により、一般的なロジックはあまり変更されていません。

(投稿の最後にUPD

function compareStrings(str1, str2) {

//

var compare = function (a, b) {

return (a < b) ? -1 : (a > b) ? 1 : 0;

};

//

var splitter1 = splitString(str1);

var splitter2 = splitString(str2);

//

while ( true ) {

var first = splitter1.next();

var second = splitter2.next();

// ...

if ( null !== first && null !== second) {

// ( )

if (xor(first.IsNumber, second.IsNumber)) {

// ""

return first.IsNumber ? -1 : 1;

//

} else {

var comp = compare(first.Value, second.Value);

if (comp != 0) {

return comp;

}

} // end if

// ... - ""

} else {

return compare(splitter1.count(), splitter2.count());

}

} // end while

}






最後に、ソート関数を作成し、すべてをまとめてコードを「櫛」にします。

(投稿の最後にUPD

function naturalSort(stringArray) {

// ""

var xor = function (a, b) {

return a ? !b : b;

};

// ,

var isDigit = function (chr) {

var charCode = function (ch) {

return ch.charCodeAt(0);

};

var code = charCode(chr);

return (code >= charCode( '0' )) && (code <= charCode( '9' ));

};

//

var splitString = function (str) {

var from = 0; // slice()

var index = 0; //

var count = 0; //

var splitter = {}; //

//

splitter.count = function () {

return count;

}

//

splitter.next = function () {

// - null

if (index === str.length) {

return null ;

}

//

while (++index) {

var currentIsDigit = isDigit(str.charAt(index - 1));

var nextChar = str.charAt(index);

var currentIsLast = (index === str.length);

// - ,

// ( / )

var isBorder = currentIsLast ||

xor(currentIsDigit, isDigit(nextChar));

if (isBorder) {

var part = str.slice(from, index);

from = index;

count++;

return {

IsNumber: currentIsDigit,

Value: currentIsDigit ? Number(part) : part

};

} // end if

} // end while

}; // end next()

return splitter;

};

// ""

var compareStrings = function (str1, str2) {

//

var compare = function (a, b) {

return (a < b) ? -1 : (a > b) ? 1 : 0;

};

//

var splitter1 = splitString(str1);

var splitter2 = splitString(str2);

//

while ( true ) {

var first = splitter1.next();

var second = splitter2.next();

// ...

if ( null !== first && null !== second) {

// ( )

if (xor(first.IsNumber, second.IsNumber)) {

// ""

return first.IsNumber ? -1 : 1;

//

} else {

var comp = compare(first.Value, second.Value);

if (comp != 0) {

return comp;

}

} // end if

// ... - ""

} else {

return compare(splitter1.count(), splitter2.count());

}

} // end while

}

//

var arr = stringArray;

return arr.sort(compareStrings);

}








見つかったバグ、コードのヒント、提案に感謝します。 誰かが興味を持っていて、多分役に立つことを願っています。



UPD:

コメントの中で、 Alex_At_Net改行の結果をキャッシュするのがいいだろうと述べ、すぐに行を断片に分割し、既成のフラグメントセットをソートすることを提案しました。 その結果、スクリプトの改訂版と拡張版が登場しました。 オプションの抽出パラメータがソート機能に追加されました-任意のオブジェクトを文字列に変換する機能:任意のオブジェクトの配列をソートできるようになりました。 入力配列はすぐにスプリッターの配列に変換されますが、分割自体は、インデックスによってフラグメントにアクセスする場合にのみ、適切な場所に1回だけ発生します。 next()メソッドの代わりに、スプリッターには、インデックス(by index)によってフラグメントを返すpart(i)メソッドがあります。 コードはもう少し「くし」と「オブジェクト化」されています。 すべて次のようになります。



function naturalSort(array, extractor) {

"use strict" ;

//

var splitters = array.map(makeSplitter);

//

var sorted = splitters.sort(compareSplitters);

//

return sorted.map( function (splitter) {

return splitter.item;

});

//

function makeSplitter(item) {

return new Splitter(item);

}

//

// ""

function Splitter(item) {

var index = 0; //

var from = 0; //

var parts = []; //

var completed = false ; //

//

this .item = item;

// -

var key = ( typeof (extractor) === 'function' ) ?

extractor(item) :

item;

this .key = key;

//

this .count = function () {

return parts.length;

};

// ( parts[])

this .part = function (i) {

while (parts.length <= i && !completed) {

next(); //

}

return (i < parts.length) ? parts[i] : null ;

};

//

function next() {

//

if (index < key.length) {

//

while (++index) {

var currentIsDigit = isDigit(key.charAt(index - 1));

var nextChar = key.charAt(index);

var currentIsLast = (index === key.length);

// - ,

// ( / )

var isBorder = currentIsLast ||

xor(currentIsDigit, isDigit(nextChar));

if (isBorder) {

// parts[]

var partStr = key.slice(from, index);

parts.push( new Part(partStr, currentIsDigit));

from = index;

break ;

} // end if

} // end while

//

} else {

completed = true ;

} // end if

} // end next

//

function Part(text, isNumber) {

this .isNumber = isNumber;

this .value = isNumber ? Number(text) : text;

}

}

//

function compareSplitters(sp1, sp2) {

var i = 0;

do {

var first = sp1.part(i);

var second = sp2.part(i);

// ...

if ( null !== first && null !== second) {

// ( )

if (xor(first.isNumber, second.isNumber)) {

// ""

return first.isNumber ? -1 : 1;

//

} else {

var comp = compare(first.value, second.value);

if (comp != 0) {

return comp;

}

} // end if

// ... - ""

} else {

return compare(sp1.count(), sp2.count());

}

} while (++i);

//

function compare(a, b) {

return (a < b) ? -1 : (a > b) ? 1 : 0;

};

};

// ""

function xor(a, b) {

return a ? !b : b;

};

// :

function isDigit(chr) {

var code = charCode(chr);

return (code >= charCode( '0' )) && (code <= charCode( '9' ));

function charCode(ch) {

return ch.charCodeAt(0);

};

};

}








コメント付き

コメントなし



PS:

function thanks() {

alert( 'C PoiSoN Source Code Highlighter' );

};




* This source code was highlighted with Source Code Highlighter .







All Articles