How do you map-replace characters in Javascript similar to the 'tr' function in Perl?

37.9k views Asked by At

I've been trying to figure out how to map a set of characters in a string to another set similar to the tr function in Perl.

I found this site that shows equivalent functions in JS and Perl, but sadly no tr equivalent.

the tr (transliteration) function in Perl maps characters one to one, so

     data =~ tr|\-_|+/|;

would map

     - => + and _ => /

How can this be done efficiently in JavaScript?

11

There are 11 answers

8
Jonathan Lonowski On BEST ANSWER

There isn't a built-in equivalent, but you can get close to one with replace:

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

Method:

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];
    });
};

A perfect example:

var s = "I think Peak rocks!"
s.mapReplace({"I think":"Actually","rocks":"sucks"})
// console: "Actually Peak sucks!"
1
qwertymk On

This will map all as to b and all y to 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];

EDIT:

Apparently you can't do that with strings. Here's an alternative:

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('');
0
ikegami On

In Perl, one can also write

tr{-_}{+/}

as

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

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

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

This latter version can surely be implemented in JS without much trouble.

(My version lacks the duplication of Jonathan Lonowski's solution.)

2
neniu On

I can't vouch for 'efficient' but this uses a regex and a callback to provide the replacement character.

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;
}

For long strings of search and replacement characters, it might be worth putting them in a hash and have the function return from that. ie, tr/abcd/QRST/ becomes the hash { a: Q, b: R, c: S, d: T } and the callback returns hash[ chr ].

0
catwith On

With only one map:

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

data = Object.entries(map).reduce((prev, entry) => prev.replace(...entry), data);
1
Данила Летуновский On

This functions, which are similar how it's built in 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($_);

0
randomraccoon On

I wanted a function that allows passing a custom map object, so I wrote one based on Jonathan Lonowski's answer. If you are trying to replace special characters (the kind that need to be escaped in regular expressions) you'll have to do some more work.

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]);
};

And it's used like this:

const map = { a: 'A', b: 'B', d: 'D' };
mapReplace('abcde_edcba', map);
// ABcDe_eDcBA
0
David Najman On

Another solution:

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

A function in the 2nd parameter of Replace will be invoked for every match of regex in the first parameter and its return value is used as the replacement text.

0
ndvo On

Here is a function that receives text orig dest and replaces in text each character for the one in the corresponding position in dest.

Is is not good enough for cases where more than one character must be replaced by only one or vice-versa. It is not good enough for removing accents from Portuguese texts, which is my use case.

function tr(text, orig, dest) {
    console.assert(orig.length == dest.length);
    const a = orig.split('').map(i=> new RegExp(i, 'g'));
    const b = dest.split('');
    return a.reduce((prev, curr, idx) => prev.replace(a[idx], b[idx]), text );
}

How to use it:

var port  = "ÀÂÃÁÉÊÍÓÔÕÜÚÇáàãâêéíóõôúüç";
var ascii = "AAAAEEIOOOUUCaaaaeeiooouuc";
console.log(tr("não têm ações em seqüência", port, ascii)) ;
0
Vladislav Savchuk On

Similiar to Jonathan Lonowski answer but with words support, not just single tr chars

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