Javascript:字符串中有效的基于数组的替换

Javascript: effective array-based replacement in strings

本文关键字:数组 替换 于数组 字符串 有效 Javascript      更新时间:2023-09-26

我正在寻找一种方法来有效地(如:使用尽可能少的资源)用Javascript中的其他字符串替换字符串的限制。重点是计算时间,而不是内存消耗。

搜索术语和替换项作为用作字典的对象给出

var replacements = {
    search    : 'replace',
    another   : 'replacement',
    'and one' : 'more'
}

目前,我正在迭代这些键,并用它们构建一个regexp(带有set g标志),然后在字典中查找每个匹配项并替换它:

String.prototype.mapReplace = function (map, replaceFullOnly = false) {
    var regexp = [];
    for (var key in map) {
        regexp.push(RegExp.escape(key));
    }
    regexp = regexp.join('|');
    if (replaceFullOnly) {
        regexp = '''b(?:' + regexp + ')''b';
    }
    regexp = new RegExp(regexp, 'gi');
    return this.replace(regexp, function (match) {
        return map[match.toLowerCase()];
    });
}

这是可行的,但是我每次都需要编译一个新的正则表达式。我的问题是:有人能想出一种有效的方法来缓存正则表达式吗?如果再次给出相同的映射(与"相同的键"中的映射相同,既没有"相同的对象"也没有"相同值",也没有"键的顺序相同"),则会重复使用正则表达式?

一种显而易见的方法是对键进行排序、序列化和散列,将其用作存储正则表达式的键,并在将来的调用中重复使用存储的正则表达式(如果存在)。然而,我认为这很可能比每次编译一个新的正则表达式需要更多的时间。。。

想法/投入?


Edit:RegExp.eescape()是一个对字符串中的特殊字符进行转义的函数,用于正则表达式:

RegExp.escape= function(s) {
    return s.replace(/[-'/''^$*+?.()|[']{}]/g, '''$&');
};

使用信息:

  • 更换次数很多,就像我在聊天系统中使用的那样
  • 更换地图的更改并不常见,但这取决于聊天运营商如何使用该功能。自动脚本可以自动且频繁地添加和删除替换规则。但是,与将替换映射应用于字符串相比,对替换映射的更改频率始终较低
  • 一个或多个替换映射可能同时使用并且彼此独立

这就是我想到的:

var ReplacementMap = function (map, replaceFullOnly, ignoreCase) {
    var regexp = null;
    var update = function (search, replacement) {
        if (!isDefined(replacement)) {
            if (!(search in map)) return;
            delete map[search];
        } else {
            if (map[search] == replacement) return;
            map[search] = replacement;
        }
        invalidateRegexp();
    }
    var buildRegexp = function () {
        if (regexp != null) return;
        regexp = [];
        for (var key in map) {
            regexp.push(RegExp.escape(key));
        }
        regexp = regexp.join('|');
        if (replaceFullOnly) {
            regexp = '''b(?:' + regexp + ')''b';
        }
        regexp = new RegExp(regexp,'g' + (ignoreCase ? 'i' : ''));
    }
    var invalidateRegexp = function () {
        regexp = null;
    }
    Object.defineProperties(this, {
        fullOnly : {
            set : value => {
                if (replaceFullOnly == value) return;
                replaceFullOnly = !!value;
                invalidateRegexp();
            },
            get : () => replaceFullOnly
        },
        ignoreCase : {
            set : value => {
                if (ignoreCase == value) return;
                ignoreCase = !!value;
                invalidateRegexp();
            },
            get : () => ignoreCase
        }
    });
    this.set = function set (search, replacement) {
        if (Array.isArray(search)) {
            if (Array.isArray(search[0])) {
                search.forEach(function (search) {
                    set(search);
                });
            } else {
                update(search[0], search.length > 1 ? search[1] : undefined);
            }
        } else if (search instanceof Object 
                && search !== null 
                && !String.isString(search)) {
            for (key in search) {
                update(key, search[key]);
            }
        } else update(search, replacement);
    }
    this.get = function (search) {
        return search in map ? map[search] : undefined;
    }
    this.remove = function(search) {
        update(search);
    }
    this.apply = function (string) {
        buildRegexp();
        return string.replace(regexp, function (match) {
            return map[match.toLowerCase()];
        });
    }
    this[Symbol.iterator] = function* () {
        for (let key of Object.keys(map)) {
            yield {[key] : map[key]};
        }
        return;
    }
    if (isDefined(replaceFullOnly)) {
        replaceFullOnly = !!replaceFullOnly;
    } else {
        replaceFullOnly = true;
    }
    if (isDefined(ignoreCase)) {
        ignoreCase = !!ignoreCase;
    } else {
        ignoreCase = true;
    }
    if (isDefined(map)) {
        let entries = map;
        map = Object.create(null);
        this.set(entries);  
    } else {
        map = Object.create(null);
    }
}

用法:

// ---- CREATE MAP ----
// Empty Map
var m0 = new ReplacementMap(); 
// Map initialized with one replacement: foo => bar
var m1_1 = new ReplacementMap('foo','bar'); 
var m1_2 = new ReplacementMap(['foo','bar']); 
var m1_3 = new ReplacementMap({foo : 'bar'}); 
// Map initialized with two replacements: foo => bar, fooz => baz
var m2_1 = new ReplacementMap([['foo','bar'], ['fooz', 'baz']]); 
var m2_2 = new ReplacementMap({foo : 'bar', fooz : 'baz'}); 
var m2_3 = new ReplacementMap([{foo : 'bar'}, {fooz : 'baz'}]); 
// ---- ADD/MODIFY ENTRIES ----
var m0.set(...) // ... parameters work the same as in the constructor
// ---- REMOVE ENTRIES ----
var m2_1.delete('foo')  // removes replacement rule for foo => bar
var m2_1.delete('test') // fails silently
// ---- READ ENTRIES ----
var m2_1.get('foo')  // returns "bar"
var m2_1.get('test') // returns undefined;
for (rule of m2_1) {
    alert(JSON.stringify(rule));
}
// alerts "{'foo':'bar'}" and "{'fooz':'baz'}"

// ---- APPLY ON STRING ----
alert(m2_1.apply("foo bar")) // bar bar
// change behaviour:
m2_1.fullOnly = true;   // replace foo with bar, but not foobar with barbar
                        // default: true;
m2_1.ignoreCase = true; // ignore case. default: true