はじめに
みなさんは['test1', 'test4', 'test3', 'test2']の様な、数字を含む文字列の配列がある場合、どの様に並び替えようとしますか?数字を昇順に沿って並べ替える場合、以下の様な結果を期待しますよね。['test1', 'test2', 'test3', 'test4']
Splitメソッドを使って、連想配列(オブジェクト)を作りそこから処理を回しますか?
私は実際、そういう風に書いていました。それで大体約15行のほどのコード。
function order(words){
// var arry = [];
words = words.split(" ").map(el => el.split(""));
var obj = {};
for (var i = 0; i<words.length;i++){
var key = words[i].find(el => el>0);
obj[key] = words[i];
}
var arry = Object.values(obj).map(el => el.join(""));
if (arry.length > 0) {
return arry.join(" ")
} else {
return "";
}
}
はい、なんとも冗長な汚いコードです。
そんな中、とある場所で美しいコードを発見しました。それは、正規表現を使う方法!たったの5行で書かれていました lol
正規表現って何?と思われた方はこちらをどうぞ。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Regular_Expressions
今まであまり触れてくることのなかった正規表現。もちろん他のメソッドを駆使した書き方でも問題はありませんが、コードを短く美しく書き直せると知った感動はいいですよねー。
その過程で学んだことをまとめたいと思います。
使うメソッド
はい、この3つだけです。シンプル〜。
さ、一つ一つ見て行きましょう!
練習(前置き)
実際に検証ツールのコンソール画面などを使ってやってみましょう!
matchメソッドと\dを使って文字列から数字の取得
var str = 'test1test2test3test4'
var num = str.match(/\d/);
console.log(arry); //結果:"1"
matchメソッドと正規表現の/\d/を組み合わせることで、文字列に含まれる最初の数字を取得することができます。ちなみに取得したデータは数値ではなく文字列ですので、このまま計算に使うと予期せぬ結果が返ってきますのでご注意を。
var str = 'test1test2test3test4'
var num = str.match(/\d/);
console.log(num + 1); //結果:'11' (文字列に足しているため)
console.log(+num + 1); //結果:2 (数値化して処理している)
gフラグを追加して文字列から全数字を取得
var str = 'test1test2test3test4'
var arry = str.match(/\d/g); //←'g'フラグを追加
console.log(arry); //結果:['1','2','3','4']
gフラグをつけることで、マッチする全てを含む配列を返してくれます。ここのポイントは、配列を返すということです。これとてもすごくないですか?単純にgをつけるだけで配列まで作ってくれるんですよ!他のメソッドも組み合わせていけばかなりいろいろなことができちゃいますね。
sortメソッドで並び替え
var arry = [7, 6, 4, 1, 2]
arry.sort();
console.log(arry); // 結果:[1, 2, 4, 6, 7]
配列内を比較して、小さい順に並び替えてくれるメソッドです。とても便利ですよね!しかし、このメソッドも気をつけなければならないポイントがあります。それは、要素はそれぞれの文字列に変換したものを比較して辞書 (あるいは電話帳。数的でない) 順にソートされるという点です。どういうことでしょうか?
先ほどは1桁の数字のみの配列でしたので問題ありませんでしたが、2桁の数字が絡んでくると問題が出て来ます。つまりこういうことです。↓↓
var arry = [7, 16, 4, 1, 2]
arry.sort();
console.log(arry); // 結果:[1, 16, 2, 4, 7]
あれ、16が2の前に来てしまっています!これが先ほど言った文字列に変換したものを比較して辞書順にソートされるということです。つまり16の2桁目が1であるが故に、2という値よりも先に来ると判断されてしまうのです。
数値として判断して、ソートしたい場合はこのままでは思う様に並び替えることができません。そこで必要になってくるのが、比較関数です。
sortメソッドと比較関数をセットで使う
比較処理を行う関数を定義することで、sortメソッドがより使いやすくなります。比較関数はこの様な形です。
function (a, b) {
return a - b;
}
とてもシンプルですね!きちんと>や<を使って条件分岐をしてからreturnする書き方もありますが、コードが冗長になりますので、こちらの書き方を覚えておくだけでいいと思います。関数に入ってくるaとbを比較して、昇順に並べ替えてくれます。a - bの部分をb - aと書き換えると降順になります。
それでは、この比較関数を先ほどのsortメソッドの中に入れてみましょう。
var arry = [7, 16, 4, 1, 2]
arry.sort(function(a, b){
return a - b
});
console.log(arry); //結果:[1, 2, 4, 7, 16]
// ちなみにArrow関数で書くとfunctionと書かなくて済む
// arry.sort((a, b) => {
// return a - b
// });
// console.log(arry); //結果:[1, 2, 4, 7, 16]
するときちんと昇順に並び替えができています!しかしこれはシンプルな数値の配列に限ります。
では、これらのメソッドを組み合わせて、いよいよ「数字を含む文字列」の配列内ソートをしてみましょう!
実践
「数字を含む文字列」の配列内ソートにおいて必要となるのはmatchメソッド+正規表現の/\d/+sortメソッドの掛け合わせです。
var words = ['test1', 'test7', 'test4', 'test2'];
words.sort(function(a,b){
return a.match(/\d/) - b.match(/\d/);
});
console.log(words); // 結果:['test1', 'test2', 'test4', 'test7'];
配列に対してsortメソッドをかけ、その中の比較関数でmatchメソッドと正規表現/\d/を利用しています。それだけで、配列内の各文字列内の数値を比較して並べ替えています。
var words = ['a1aaa', 'd4ddd', 'bbb2b', 'ccc3cc'];
words.sort(function(a,b){
return a.match(/\d/) - b.match(/\d/);
});
console.log(words); // 結果:['a1aaa', 'bbb2b', 'ccc3cc', 'd4ddd'];
数字が文字列の途中であっても、きちんと並べ替えてくれますね。
しかし、こちらは文字列をそのまま比較しておりますので、もちろん辞書順での比較になります。つまり、2桁の数字を含む場合は、以下のような結果になってしまいます。
var words = ['a1aaa', 'd16ddd', 'bbb2b', 'ccc3cc'];
words.sort(function(a,b){
return a.match(/\d/) - b.match(/\d/);
});
console.log(words); // 結果:["a1aaa", "d16ddd", "bbb2b", "ccc3cc"];
この問題を解決するために使うのが、/\d+/です。+をつけることで、直前の文字の 1 回以上の繰り返しにマッチします。(hogefugaさんのコメントに感謝です)
こちらを使うことで、見事に文字列に含まれた数字の値順に並び変わってくれます。
var words = ['a1aaa', 'd16ddd', 'bbb2b', 'ccc3cc'];
words.sort(function(a,b){
return a.match(/\d+/) - b.match(/\d+/);
});
console.log(words); // 結果:["a1aaa", "bbb2b", "ccc3cc","d16ddd"];
最後に
正規表現パターンは他にもたくさんあります。私も駆け出しの身ですので、まだまだ至らない部分はありますが、今回は正規表現の便利さにビックリしました。少しプログラミングに慣れて来たら、正規表現を勉強することで、活用の場が広がって行きそうです。