javascriptで配列内の要素をワイルドカードを使って検索する



 LinuxやMacを含むUnix系のOSでは、コマンドライン上でファイルやフォルダを操作、検索する時に、(  ' * ' ) の特殊文字を使って 絞り込む、 " あいまい検索 " をすることは珍しくありません。

# ワイルドカードを使って検索する例
find *.js # カレントフォルダから .jsで終わるファイルを検索


 そのようなワイルドカードを使った検索は ' glob matching' と呼ばれており、サーバーの管理をする人、CLI上から操作するには 馴染みのあるコマンドの1つだと思われます。

そんなワイルドカードを使った細かい検索を、配列内の要素向けに適応できる javascript用の ライブラリが「micromatch」です。

micromatch を使って出来ること

- ワイルドカードを使って配列内の要素が検索出来る

// 第一引数の配列内に 'fから始まる','bから始まる' 要素を検索
mm(['foo', 'bar', 'qux'], ['f*', 'b*']); 
//=> ['foo', 'bar'] // 先頭に'f' 又は 'b' から始まる要素を返す


micromatch のインストール

micromatchは ' npm ' 又は ' yarn ' コマンドを使ってインストールします。

# npm
npm i micromatch --save
# yarn
yarn add micromatch


micromatch の概要

micromatch は 下記のような特殊文字 ( パターン) を使って要素にフィルターをかけていくライブラリです。

マッチングに使う特殊文字の種類
  ・  〜を含む : *
  ・ 〜を含まない処理 : !
  ・ 特定の文字の種類( 数値、アルファベットなど ) : [:alpha:] [:digit:]
  ・ 数値のレンジ、配列 : foo/{1..5}.md, bar/{a,b,c}.js
  ・ 正規表現 : foo-[1-5].js, (foo/(abc|xyz).js)

絞り込み時によく使いそうな例として、幾つかサンプルを作りました。

下のスニペットは 検索するパターンに ' * ' 文字を使って、配列 ' pref ' 内から' が付く ' 文字を絞り込んでいます。

const mm = require("micromatch");

var pref = ['東京', '神奈川', '北海道', '愛知', '大阪', '大分'];
console.log(mm(pref, '*大*'));
// => [ '大阪', '大分' ]


上記に対して、下のスニペットは ' ! ' 文字を使って 、' が含まれない ' 要素を絞り込んでいます。

const mm = require("micromatch");

var pref = ['東京', '神奈川', '北海道', '愛知', '大阪', '大分'];
console.log(mm(pref, '!*大*'));
// => [ '東京', '神奈川', '北海道', '愛知' ]


下のスニペットは ' { } ' を使って ' 東京 ' と ' 青山 ' が含まれる要素を抜き出しています。

const mm = require("micromatch");

var university = [
  '東京大学', '慶応大学', '青山学院大学', '早稲田大学',
  '東京工業大学', '一橋大学'
];

console.log(mm(university, '*{東京,青山}*'));
// => [ '東京大学', '青山学院大学', '東京工業大学' ]

 

micromatch の使い方 

micromatch は検索に紐付ける 特殊文字のパターンさへわかれば、簡単に使うことが出来ます。

  micromatch( list, pattern ) : Array

 micromatch は メインとなる関数で、第一引数には ' 元になる配列 ' 、第二引数には ' 判定するパターン ( 配列 ) ' が入り、絞込された配列で値が返ってきます。

const mm = require("micromatch");
// .js で終わる配列を指定
mm(['a.js', 'b.txt', 'c.rb', 'd.py'], ['*.js']);
// =>  ['a.js']
// 複数指定も可 * 'f'から始まらない条件を追加
mm(['a.js', 'b.txt', 'c.rb', 'd.py', 'f.js'],['*.js', '!f*']);
// =>  ['a.js']


  .match( list, string_patern ) : Array

 match メソッドは上記のメイン関数と同じ動きをしますが、第二引数の指定するパターンが ' String型 ' の必要があります。

const mm = require("micromatch");
// .js で終わる配列を指定
mm(['a.js', 'b.txt', 'c.rb', 'd.py'], '*.js');
// =>  ['a.js']


  .isMatch( string, pattern ) : Bool

 isMatch メソッドは、判定を行います。第一引数には ' 元となる文字列 ' 、第二引数には 判定するパターンの文字列を指定すると、Bool型で値が返ってきます。

 javascriptに標準で搭載されている ' str.includes ' メソッドの代わりに使えば高機能なマッチングができそうです。

* マッチパターンに配列で複数指定出来る  ' micromatch.contains '  メソッドも用意されています。

mm.isMatch('test.js', '*.js'); // true
mm.isMatch('test.js', '*.py'); // false


  .some( { String | Array }, pattern ) : Bool

 ベースとなる配列の1つ以上の要素が、パターンと合致すれば true を返します。また、全ての要素が一致した時に true を返す ' .every ',  ' .all 'メソッドも用意されています。

// 要素 bar.jsがパターンと合致 ' foo.js 'は 不一致
mm.some(['foo.js', 'bar.js'], ['*.js', '!foo.js']);
// => true
mm.some(['foo.js'], ['*.js', '!foo.js']);
// => false

// every メソッド .every( {string | array}, pattern)
mm.every(['foo.js', 'bar.js'], ['*.js', '!foo.js']);
// => false

// all メソッド .all( string, pattern )
mm.all('foo.js', ['*.js', '!foo.js']);
// => false


  .matchKeys( object, pattern ) : Object

 matchKeys メソッドは オブジェクトの key名をベースにフィルターをかけるメソッドです。第一引数にはベースとなるオブジェクト、第二引数にはパターンが入り、オブジェクトで値が返ってきます。

var obj = { str1: 'a', str2: 'b', str3: 'c' };
// 2が含まれるkeyのオブジェクトを絞込
mm.matchKeys(obj, '*2');
// => { str2: 'b' }


  .matcher( pattern ) : Function

 .matcher は指定したパターンを関数としてまとめるメソッドです。第一引数にパターンを指定し、' Function ' 型で値を返し、Bool判定します。

// 画像タイプを定義する例
var isImage = mm.matcher('*.+(*jpg|jpeg|png|svg)');

isImage('a.png');
//=> true
isImage('a.svg');
//=> true
isImage('a.mp4');
//=> false


  .makeMe( pattern ) : RegExp

 makeMeメソッドは パターンを正規表現の式に変換するメソッドです。他ライブラリと併用して RegExpパターンが欲しい場面で使うと便利そうです。

// 文字列が .jpeg, jpeg, .png, .svgで終わる正規表現パターンを作成
console.log(mm.makeRe('*.+(*jpg|jpeg|png|svg)'));
// => /^(?:(?:(?:\.(?:\/|\\))(?=.))?(?!\/)(?!\.)(?=.)[^\/]*?\.(?:(?!\.)(?=.)[^\/]*?jpg|jpeg|png|svg)+(?:(?:\/|\\)|$))$/


 JSON オブジェクトなどの複数からなる Key-Value オブジェクトで使いたい場合でも、javascriptに標準で搭載されている ' filter ' メソッドと組み合わせれば、簡単に判定が可能です。

// jsonなどを判定する例
const mm = require("micromatch");
//
const lookup = ({ object, key, word, exclude }) => {
  let ban = exclude == "" ? "" : `!*${exclude}*`;
  return object.filter(val => mm.all(val[key], [`*${word}*`, ban]));
};
// 検索するオブジェクト
let language = [
  { id: 1, name: "ruby" },
  { id: 2, name: "python" },
  { id: 3, name: "java" },
  { id: 4, name: "php" },
  { id: 5, name: "javascript" },
  { id: 6, name: "node.js" },
  { id: 7, name: "swift" },
];

let str = "ja"; // 検索する文字列

// language オブジェクトから 文字列 'ja' が含まれる判定しつつ 'scr'が含まれるオブジェクトは除外する
lookup({
  object: language, // 元となるオブジェクト
  key: "name", // 検索するオブジェクトの key名
  word: str, // 検索する文字
  exclude: "scr" // 判定に除外する文字 
});
// => [ { id: 3, name: 'java' } ]

/* excludeが無い場合 */
lookup({
  object: language, // 元となるオブジェクト
  key: "name", // 検索するオブジェクトの key名
  word: str, // 検索する文字
  // exclude: "scr" // 判定に除外する文字 
});
// => [ { id: 3, name: 'java' }, { id: 5, name: 'javascript' } ]


Summary

 以上が micromatch を使ってワイルドカード検索をする方法の紹介でした。 micromatch は 数々のパッケージの依存ライブラリとして使用されており、有名所では ' babel-core ', ' yarn ', ' jest ' の内部でも使用されています。

 micromatch を部分的に使えば、CLIアプリ作成時に コマンドが見つからない時に ' コマンド候補を表示させる サジェスト機能 '  なんて事も 比較的簡単に実装できそうです。

GitHub : micromatch/micromatch

 

この記事のカテゴリ
プログラミング

この記事に付けられているタグ