すべてのコードは ES2015 構文を使用します。ES5 構文が必要な場合は、Babel - Try it out または TypeScript Playground を使用して翻訳できます。
質問がありました
今日、友人から質問がありました。フロントエンドは、Ajax を介してバックエンドから大量のデータを取得します。フィルタリング方法は次のとおりです。
class Filter {
filterA(s) {
let data = this.filterData || this.data;
this.filterData = data.filter(m => m.a === s);
}
filterB(s) {
let data = this.filterData || this.data;
this.filterData = data.filter(m => m.b === s);
}
}
ログイン後にコピー
今混乱しています、これがデータの処理方法だと思うのですが、間違っているのですが、対処方法が分かりません。
問題が見つかりました
問題はフィルタリングにあります。この方法で複数のフィルタリングを実現できますが (これは、最初に filterA() を呼び出し、次に filterB() を呼び出すことで実現できます)、このフィルタリングは元に戻すことができません。フィルタリングのプロセスが次のようになっているとします。
f.filterA("a1");
f.filterB("b1");
f.filterA("a2");
ログイン後にコピー
当初、データを「a1」と「b1」でフィルタリングし、最初の条件を「a2」に変更したかったのですが、結果は空のセットであることが判明しました。
問題を解決する
問題が見つかった場合は、それに応じて解決してください。この問題は不可逆的なフィルタリング プロセスによって発生するため、this.filterData から開始するのではなく、毎回 this.data から直接フィルタリングを開始することで問題を解決できます。これを行う場合は、まず選択したフィルター条件を記録する必要があります。
フィルター条件の記録
フィルター条件をリストに記録することは確かに可能ですが、同じ条件に対する 2 つのフィルターは相互に排他的であり、最後のフィルターのみを保持できることに注意してください。そのため、HashMap の方が適切であるはずです。
class Filter {
constructor() {
this.filters = {};
}
set(key, filter) {
this.filters[key] = filter;
}
getFilters() {
return Object.keys(this.filters).map(key => this.filters[key]);
}
}
ログイン後にコピー
この場合、上記のような処理は
f.set("A", m => m.a === "a1");
f.set("B", m => m.b === "b1");
f.set("A", m => m.a === "a1");
let filters = f.getFilters(); // length === 2;
ログイン後にコピー
と表現されます。上記の 3 番目の文で設定されたフィルタは、1 番目の文で設定されたフィルタをカバーします。最後に取得したフィルターを使用して、元のデータ this.data を順番にフィルター処理すると、正しい結果が得られます。
getFilters() によって返されるリストは set の順序ではないと考える人もいるかもしれません。実際、これは順序のない HashMap の特性です。ただし、単純な条件の判定ではどちらが先でも結果は同じです。ただし、一部の複合条件の判断では、影響がある可能性があります。
必要に応じて、マップの代わりに配列を使用して順序の問題を解決できますが、これにより検索効率(線形検索)が低下します。それでも検索効率の問題を解決したい場合は、配列 + マップを使用できます。ここで言うことはあまりありません。
フィルタリング
実際に使ってみると、毎回getFilter()を使ってループを使って処理しているととても遅いです。データは Filter にカプセル化されるため、filter() メソッドを直接指定してフィルタリング インターフェイスを提供することを検討できます。
class Filter {
filter() {
let data = this.data;
for (let f of this.getFilters()) {
data = data.filter(f);
}
return data;
}
}
ログイン後にコピー
ただし、特に大量のデータを扱う場合、これはあまり効率的ではないと思います。 lodash の遅延処理を利用するのもよいでしょう。
lodash の遅延処理を使用すると
filter() {
let chain = _(this.data);
for (let f of this.getFilters()) {
chain = chain.filter(f);
}
return chain.value();
}
ログイン後にコピー
lodash は、データが 200 を超える場合に遅延処理を有効にします。つまり、各フィルターを 1 回ループするのではなく、ループ処理して各フィルターを順番に呼び出します。
遅延処理と遅延しない処理の違いは、以下の図で確認できます。遅延のない処理では、合計 n (ここでは n = 3) 個の大きなループが実行され、n - 1 個の中間結果が生成されます。遅延処理は大きなループのみを実行し、中間結果は生成されません。
でも正直、ちょっとしたことで余計なライブラリをロードするのは嫌なので、簡単な実装だけ自分で作ります
遅延処理は自分で実装
filter() {
const filters = this.getFilters();
return data.filter(m => {
for (let f of filters) {
// 如果某个 filter 已经把它过滤掉了,也不用再用后面的 filter 来判断了
if (!f(m)) {
return false;
}
}
return true;
});
}
ログイン後にコピー
中のforループはArrayも使えます.prototype.every を簡略化します:
filter() {
const filters = this.getFilters();
return data.filter(m => {
return filters.every(f => f(m));
});
}
ログイン後にコピー
アイデアを明確にし、どのデータを保持する必要があるか、どのデータが一時的 (中間プロセス) であるか、どのデータが最終的なデータであるかを理解している限り、データ フィルタリングは実際には複雑な問題ではありません。 result... 活用 Array.prototype の関連メソッドや lodash などのツールを簡単に扱うことができます。