如何在Javascript中进行字符映射替换,类似于Perl中的'tr'函数?

47

我一直在尝试找出如何将字符串中一组字符映射到另一组类似于Perl中tr函数的设置。

我发现了这个网站,它展示了JS和Perl中等效的函数,但可惜没有类似的tr函数。

Perl中的tr(转换)函数一对一地映射字符,因此

     data =~ tr|\-_|+/|;
would map
     - => + and _ => /

如何在JavaScript中高效地完成此操作?


这个回答解决了你的问题吗?[在一次替换调用中替换多个字符] (https://dev59.com/U2Qn5IYBdhLWcg3wxZni) - user202729
11个回答

85

没有内置的等价物,但您可以使用replace方法来实现相似的功能:

data = data.replace(/[\-_]/g, function (m) {
    return {
        '-': '+',
        '_': '/'
    }[m];
});

1
回调函数非常好,return {...}[m] 是什么意思? - qodeninja
3
对象({...})定义了字符映射,期望匹配作为键/属性,替换内容作为值。当前匹配项 m 被用作查找对象中对应的替换项([m]),并将其返回给 replace 函数以执行实际的替换。 - Jonathan Lonowski
显然答案是“是”,但不像Perl那样简洁。 - Pete Alvin
1
当我们有一个包含这些替换的数组arr,并且匹配遵循可以完全由正则表达式给出的简单模式时,如果正则表达式有点太普遍并且匹配了不在arr中的内容,则最好有一个默认值。 在这种情况下,函数体应该是return arr[m] || m;,以返回匹配本身而没有任何替换(而不是undefined)。 - Mike
2
不确定这样做的效率如何,但将“字典”对象定义为全局变量可以更快(避免每次重新创建)。 - user202729

8

方法:

String.prototype.mapReplace = function(map) {
    var regex = [];
    for(var key in map)
        regex.push(key.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"));
    return this.replace(new RegExp(regex.join('|'),"g"),function(word){
        return map[word];
    });
};

一个完美的例子:
var s = "I think Peak rocks!"
s.mapReplace({"I think":"Actually","rocks":"sucks"})
// console: "Actually Peak sucks!"

2
@qodeninja 这是相同的技术,但这种方法允许使用动态JSON kv映射。它可能比答案慢,因为它会为您生成正则表达式和替换字典。因此,如果您需要动态替换,请使用此方法;如果您只需要处理静态词集,请使用答案。 - PeakJi

7

我不能保证“高效”,但这个方法使用正则表达式和回调来提供替换字符。

function tr( text, search, replace ) {
    // Make the search string a regex.
    var regex = RegExp( '[' + search + ']', 'g' );
    var t = text.replace( regex, 
            function( chr ) {
                // Get the position of the found character in the search string.
                var ind = search.indexOf( chr );
                // Get the corresponding character from the replace string.
                var r = replace.charAt( ind );
                return r;
            } );
    return t;
}

对于长字符串的搜索和替换字符,将它们放入哈希表中并让函数从中返回可能是值得的。例如,tr/abcd/QRST/ 变成了哈希表 { a: Q, b: R, c: S, d: T },回调返回 hash[chr]。


1
+1,尽管:不支持“]”,“\”和“^”。不支持范围。不支持“\”转义。 - ikegami
@ikegami 请看我在这里加强此函数的尝试:https://codereview.stackexchange.com/questions/192385/attempt-at-perl-transliteration-function-in-javascript-with-flags - Jarede

3
这些函数与Perl中的构建方式类似。

function s(a, b){ $_ = $_.replace(a, b); }
function tr(a, b){ [...a].map((c, i) => s(new RegExp(c, "g"), b[i])); }

$_ = "Εμπεδοκλης ο Ακραγαντινος";

tr("ΑΒΓΔΕΖΗΘΙΚΛΜΝΟΠΡΣΤΥΦΧΩ", "ABGDEZITIKLMNOPRSTIFHO");
tr("αβγδεζηθικλμνοπρστυφχω", "abgdezitiklmnoprstifho");
s(/Ξ/g, "X"); s(/Ψ/g, "Ps");
s(/ξ/g, "x"); s(/ψ/g, "Ps");
s(/ς/g, "s");

console.log($_);


似乎这是最好的答案...但请更好地解释一下s()是什么以及如何使用tr()。理想情况下只需要一个函数tr(x,from,to) - Peter Krauss

2

另一种解决方案:

var data = data.replace(/[-_]/g, (match) => {
    return  '+/'['-_'.indexOf(match)];
});

在Replace的第二个参数中,一个函数将会被调用来替换第一个参数中正则表达式的每一个匹配项,并且它的返回值将作为替换文本。


1

我希望有一个函数可以传递自定义的地图对象,所以我根据Jonathan Lonowski的答案编写了一个。如果你想替换特殊字符(那些需要在正则表达式中转义的字符),你需要做更多的工作。

const mapReplace = (str, map) => {
  const matchStr = Object.keys(map).join('|');
  if (!matchStr) return str;
  const regexp = new RegExp(matchStr, 'g');
  return str.replace(regexp, match => map[match]);
};

"这是这样使用的:"
const map = { a: 'A', b: 'B', d: 'D' };
mapReplace('abcde_edcba', map);
// ABcDe_eDcBA

1

这将把所有的a映射到b,并把所有的y映射到z

var map = { a: 'b', y: 'z' };
var str = 'ayayay';

for (var i = 0; i < str.length; i++)
    str[i] = map[str[i]] || str[i];

编辑:

显然你不能用字符串做到那个。这里有一个替代方案:

var map = { a: 'b', y: 'z' };
var str = 'ayayay', str2 = [];

for (var i = 0; i < str.length; i++)
    str2.push( map[str[i]] || str[i] );
str2.join('');

4
为什么在没有测试的情况下发布答案,为什么在编辑后不删除无效的答案? - Mr. Lance E Sloan

1
在Perl中,也可以编写
tr{-_}{+/}

作为

my %trans = (
   '-' => '+',
   '_' => '/',
);

my $class = join '', map quotemeta, keys(%trans);
my $re = qr/[$class]/;

s/($re)/$trans{$1}/g;

这个版本可以很容易地在JS中实现。

(我的版本缺少Jonathan Lonowski解决方案的复制。)


1

类似于Jonathan Lonowski的答案,但支持单个字符以外的词语

"aaabbccddeeDDDffd".replace( /(a|cc|DDD|dd)/g, m => ({'a':'B', 'cc':'DDD', 'DDD':'ZZZ', dd:'QQ'}[m]) ) 
// RESULT: "BBBbbDDDQQeeZZZffd"

1

只用一个映射表:

const map = {
    '-': '+',
    '_': '/'
};

data = Object.entries(map).reduce((prev, entry) => prev.replace(...entry), data);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接