【问题标题】:Javascript: effective array-based replacement in stringsJavascript:字符串中有效的基于数组的替换
【发布时间】:2016-08-31 16:02:00
【问题描述】:

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

搜索词和替换词作为字典对象给出

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

目前我正在遍历键并从中构建一个正则表达式(带有设置 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()];
    });
}

这行得通,但是我每次都需要编译一个新的正则表达式。我的问题是:有人可以想出一种有效的方法来缓存常规表达式,如果相同的映射(与“相同的键”相同,既不是“相同的对象”,也不是“相同的值”,也不是“相同的键顺序”)再次给出,正则表达式是否被重用?

一种明显的方法是对键进行排序、序列化和散列,将其用作存储正则表达式的键,并在将来的调用中重新使用存储的正则表达式。但是,我认为这很可能比每次都编译一个新的常规表达式需要更多的时间......

想法/意见?


编辑:RegExp.escape() 是一个转义字符串中的特殊字符以用于正则表达式的函数:

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

使用信息:

  • 替换做了很多,因为这是我在聊天系统中使用的
  • 替换地图的更改很少见,但这取决于聊天操作员如何使用该功能。自动化脚本自动频繁地添加和删除替换规则是可能的。但是,对替换映射的更改总是比将替换映射应用于字符串的频率要低。
  • 一个或多个替换地图可能同时使用并且彼此独立。

【问题讨论】:

  • 你有一些例子吗,mapreplace 在做什么?
  • 为什么不显式缓存已编译的正则表达式?只是不要每次都传递不同的地图对象。
  • @NinaScholz 用于聊天系统,操作员可以动态设置新的替换规则。存在针对不同用户的不同规则集。
  • @JohannesH。你是说你有一个从多个地方更新的全球replacements 地图?然后将其明确化,并将其封装在一个对象中。
  • @JohannesH.:是的,这种方法正是我所说的封装。您还可以向其添加测量代码。

标签: javascript string dictionary caching replace


【解决方案1】:

这是我想出的:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-12-31
    • 2010-10-31
    • 2018-04-02
    • 2010-09-27
    • 2013-10-30
    • 1970-01-01
    • 1970-01-01
    • 2019-12-10
    相关资源
    最近更新 更多