Domanda

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?

È stato utile?

Soluzione

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

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

Altri suggerimenti

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 ].

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!"

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($_);

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

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.)

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

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

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"
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top