Yes, I want a character "translate" function in javascript like that in php.
I made the following, but it is ghastly. Surely there must be a better way -- using regular expressions?
<html>
<head>
<script>
window.onload = function() {
"use strict";
console.log(translate("abcdefg", "bdf", "XYZ")); // gives aXcYeZg -=-=-
}
function translate(v1, xlatfrom, xlatto) {
var ch, ipos, retstr = "";
if (xlatfrom.length != xlatto.length) return ""; // lengths must be =
for (var i1=0; i1<v1.length; i1+=1) { // go through string
ch = v1.substring(i1, i1+1); // character by character
ipos = xlatfrom.indexOf(ch); // ck if in xlatfrom
if (ipos >= 0) ch = xlatto.substring(ipos, ipos+1); // if yes, replace
retstr += ch; } // build up return string
return retstr;
}
</script>
</head>
<body>
</body>
</html>
EDIT: I've accepted the #dani-sc answer. I'm not going to pursue performance. But it's so DIDACTIC! And thanks for the "spread operator" info. Here's how I might use his answer:
function translate(v1, xlatfrom, xlatto) { // like the PHP translate
var mapobj = strsToObject(xlatfrom, xlatto); // make str1.ch's:str2ch's object
return [...v1].map(ch => mapobj[ch] || ch).join(''); // ... is js "spread operator"
}
function strsToObject(str1, str2) { // make object from strings
if (str1.length != str2.length) return {}; // lengths must be =
var retobj = {};
for (var i1=0; i1<str1.length; i1+=1) { // just str[i1]: str2[i1]
retobj[str1.substring(i1, i1+1)] = str2.substring(i1, i1+1); }
return retobj;
}
or (this is GREAT! THANKS!)
function translate(v1, xlatfrom, xlatto) { // like the PHP translate
if (xlatfrom.length != xlatto.length) return ""; // lengths must be =
var mapobj = {}; // make object for mapping
for (var i1=0; i1<xlatfrom.length; i1+=1) { // just str[i1]: str2[i1]
mapobj[xlatfrom.substring(i1, i1+1)] = xlatto.substring(i1, i1+1); }
return [...v1].map(ch => mapobj[ch] || ch).join(''); // ... is js "spread operator"
}
Well, if you want, you could use regular expressions like this:
function translate(input, oldCharacters, newCharacters) {
let output = input;
const oldChArr = [...oldCharacters];
const newChArr = [...newCharacters];
for (let i = 0; i < oldChArr.length; i += 1) {
output = output.replace(new RegExp(oldChArr[i], 'g'), newChArr[i]);
}
return output;
}
function translateFixed(input, replacements) {
return input.replace(/./g, ch => replacements[ch] || ch);
}
function translateFixedNoRegEx(input, replacements) {
return [...input].map(ch => replacements[ch] || ch).join('');
}
console.log(translate("abcdefgbdb", "bdf", "XYZ"));
console.log(translate("abcdefg", "cde", "dec"));
console.log(translateFixed("abcdefg", {c: 'd', d: 'e', e: 'c'}));
console.log(translateFixedNoRegEx("abcdefg", {c: 'd', d: 'e', e: 'c'}));
If you would be okay with changing the method's signature, it could be made a bit more concise of course.
Edit: I've added two more methods which actually achieve what you're looking for. Just for reference, I left the original method translate in there as well.
translateFixed uses regular expressions to match every single character and replace it if it was specified in the replacements parameter.
translateFixedNoRegex just creates an array of characters out of the input string and iterates over them. If the character ch matches one in the replacements parameter, it's replaced, otherwise it's left unchanged. Afterwards, we'll convert it back to a string by concatenating the characters.
You asked about [...array]: It's the spread operator, introduced with ES6. When used on a string, it just takes every character and puts it as a single entry into an array. That means, these both lines are equivalent:
console.log([..."mystring"]);
console.log("mystring".split(''));
function translate(val, xlatfrom, xlatto) { //
if (xlatfrom.length !== xlatto.length) return "";
Array.from(xlatfrom).forEach((key, index) => {
val = val.replace(key, xlatto[index]);
})
return val;
}
console.log(translate("abcdefg", "bdf", "XYZ"));
Related
I have a function that performs some operations on a given symbol. But first it checks if the code of that symbol is in a specific range (to prevent errors):
function transformSpecialChar(char) {
const shouldTransform = char.charCodeAt(0) > someNumber && char.charCodeAt(0) < someOtherNumber;
if(shouldTransform) {
// do something
return transformed;
}
return undefined;
}
I also have a function stringToArray that breaks a given string into characters. Then if some character is special, it calls a transformSpecialChar function and passes that char as an argument. If the character is not special then the function does something else.
function stringToArray(str) {
const result = Array.from(str);
result = result.map(el => {
const isSpecial = el.charCodeAt(0) > someNumber && el.charCodeAt(0) < someOtherNumber;
if(isSpecial) {
return transformSpecialChar(el)
}
// else do something else with a character
})
return result
}
The problem is that I check the character twice. First when I check what kind of operation should a stringToArray function perform on an other character of a given string. And then I check the character again inside transformSpecialChar function to make sure that the given char could be transformed as it has to.
I can't drop that check process from the transformSpecialChar function since this function is used not only in stringToArray and it should always check the given char to avoid unexpected errors.
And I also can't drop that check process form the stringToArray function because different types of characters should be processed differently.
I tried to drop the check process from the stringToArray function and see if transformSpecialChar function returns undefined.
function stringToArray(str) {
const result = Array.from(str);
result = result.map(el => {
const transformed = transformSpecialChar(el);
if (transformed) {
return transormed
}
// else do something else with a character
})
return result
}
But in this case the transform function is run for every character in a string and it seems super inefficient.
Make transformSpecialChar a factory function to use for either mapping an Array of characters, or as a stand alone function. Something like:
const str = `{Some string with speçíal charäcters}`;
console.log(`Original string: ${str}`);
// note: undefined will map to null and will not be
// included in `join`
console.log(`stringToArray(str).join(\`\`) => ${
stringToArray(str).join(``)}`);
console.log(`stringToArray(str, true).join(\`\`) => ${
stringToArray(str, true).join(``)}`);
// use the factory as stand alone function
const transformer = transformSpecialChar(false);
console.log(`transformer(\`4\`) => ${transformer(`4`)}`);
console.log(`transformer(\`A\`) => ${transformer(`A`)}`);
// This factory function returns a function
// to be used in Array.map or stand alone.
// It uses [returnValues] from closure
function transformSpecialChar(returnValues = false) {
return chr => {
const chrCode = chr.charCodeAt(0);
return chrCode > 200 || chrCode < 62 ?
`#${chrCode};` : (returnValues && chr || undefined);
};
}
function stringToArray(str, includeNotTransformed = false) {
return [...str].map(transformSpecialChar(includeNotTransformed));
}
This might be a repeat question but I'm not sure how to look for the answer :P
I'm trying to extract and remove variables from a string.
The string might look like this: !text (<123456789>=<#$111111111>) (<7654312> = <#$222222222>) (🛠 =<#$3333333333>) Some text that I will need!
I need the two items in each block?
e.g. [["123456789", 111111111],['7654312','222222222'],["🛠","3333333333"]]
Then I need the string exactly but with the variables removed?
e.g. Some more text that I will need!
I'm not sure of the best way to do this, any help is appreciated.
You don't always have to use regexes, for instance why not write a parser? This gives you much more flexibility. Note that I added <> around the 🛠 for simplicity, but you could make brackets optional in the parser.
The parser assumes anything that isin't within () is free text and captures it as string nodes.
For instance if you wanted only the last text node you could do...
const endingText = parse(text).filter(t => typeof t === 'string').pop();
const text = '!text (<123456789>=<#$111111111>) (<7654312> = <#$222222222>) (<🛠> =<#$3333333333>) Some text that I will need!';
console.log(parse(text));
function parse(input) {
let i = 0, char = input[i], text = [];
const output = [];
while (char) {
if (char === '(') {
if (text.length) output.push(text.join(''));
output.push(entry());
text = [];
} else {
text.push(char);
consume();
}
}
if (text.length) output.push(text.join(''));
return output;
function entry() {
match('(');
const key = value();
whitespace();
match('=');
whitespace();
const val = value();
match(')');
return [key, val];
}
function value() {
const val = [];
match('<');
while (char && char !== '>') val.push(char), consume();
match('>');
return val.join('');
}
function whitespace() {
while (/\s/.test(char)) consume();
}
function consume() {
return char = input[++i];
}
function match(expected) {
if (char !== expected) throw new Error(`Expected '${expected}' at column ${i}.`);
consume();
}
}
I wrote a function that is supposed to replace code in between of two delimiters with the value, it returns (The string I'm applying this to is the .outerHTML of a HTML-Object).
This will be used similar to how it is used in e.g. Vue.js or Angular.
It looks like this:
static elemSyntaxParse(elem) {
let elem = _elem.outerHTML;
if (elem.includes("{{") || elem.includes("}}")) {
let out = "";
if ((elem.match(/{{/g) || []).length === (elem.match(/}}/g) || []).length) {
let occurs = elem.split("{{"),
key,
temp;
for (let i = 1; i < occurs.length; i++) {
if (occurs[i].includes("}}")) {
key = occurs[i].substring(0, occurs[i].indexOf("}}"));
temp = eval(key) + occurs[i].substring(occurs[i].indexOf("}}") + 2);
out += temp;
} else {
ModularCore.err("Insert-Delimiters \"{{\" and \"}}\" do not match.");
break;
return elem;
}
}
return occurs[0] + out;
} else {
ModularCore.err("Insert-Delimiters \"{{\" and \"}}\" do not match.");
return elem;
}
}
return elem;
}
(The function is inside of a class and refers to some external functions.)
Example use:
<body>
<p id="test">{{ Test }}</p>
<script>
let Test = 27;
document.getElementById("test").outerHTML = elemSyntaxParse(document.getElementById("test"));
</script>
</body>
Returns this string:
<p id="test">27</p>
It works but it is rather ugly and kinda slow.
How would I go about cleaning this up a bit? I am open to ES6.
PS: I now "eval() is evil" but this is the only occurrence in my code and it is (as far as i know) not replaceable in this situation.
Thanks!
I think you can omit a few checks and end up at:
const text = elem.outerHTML.split("{{");
let result = text.shift();
for(const part of text) {
const [key, rest, overflow] = part.split("}}");
if(!key || rest == undefined || overflow) {
ModularCore.err("Insert-Delimiters \"{{\" and \"}}\" do not match.");
return elem.outerHTML;
}
result += eval(key) + rest;
}
return result;
Invert the testing logic to get rid of nesting and else clauses
if (! elem.includes("{{") || !elem.includes("}}")) {
ModularCore.err("Insert-Delimiters \"{{\" and \"}}\" do not match.");
return elem;
}
// original loop code here
Don't double check - as #Bergi comment says.
test for return values indicating "not found, etc"
// if is removed. next line...
let occurs = elem.split("{{"), key, temp;
// if the separator is not in the string,
// it returns a one-element array with the original string in it.
if(occurs[0] === elem) return "no substring found";
The above should eliminate 2 nesting levels. You can then do a similar thing in that inner for loop.
Simplify compound logic.
!a || !b is equivalent to !(a && b). This is De Morgan's law
My objective is to sort an array based on the priority of JS object that uses regular expressions. Here's what I have:
JS objects defined:
var rx_CondoIndicator = new RegExp(/\bCONDO(MINIUM)?(?!S)/);
var rx_TownhouseIndicator = new RegExp(/\bTOWN\s*(HOUSE|HOME)(?!S)/);
var rx_TimeshareIndicator = new RegExp(/\bTIMESHARE(?!S)/);
Sort objects defined:
var so_UnitKeywordIndicator = {
rx_CondoIndicator: 1,
rx_TownhouseIndicator: 2,
rx_TimeshareIndicator: 3
};
arrUnitNumber = ['TIMESHARE A-1', 'TOWNHOUSE 407', 'CONDO #13']
The following logic ignores the sort object priority and does not sequence the array in the desired order which is "CONDO #13", "TOWNHOUSE 407", "TIMESHARE A-1"
arrUnitNumber.sort(function(x,y){
return so_UnitKeywordIndicator[x] - so_UnitKeywordIndicator[y];
})
Please note that only a fraction of the JS sort objects are shown for brevity.
If this is not the best solution for the problem, I'm open to any suggestions being a novice at JS.
The main problem is that you're never using your regular expressions for anything in the sort loop, and x and y will be entries from the array ('TIMESHARE A-1' and similar), which don't exist on so_UnitKeywordIndicator as properties. So so_UnitKeywordIndicator[x] will always be undefined.
It looks like you want to use the regular expressions to categorize the strings, and then sort them based on the relatives values in so_UnitKeywordIndicator. So you'll need to test the strings against the regular expressions.
If you have to start from those strings, I think I'd probably approach it like this (removing so_UnitKeywordIndicator):
arrUnitNumber.sort(function(x,y){
return getSortingKey(x) - getSortingKey(y);
});
function getSortingKey(value) {
if (rx_CondoIndicator.test(value)) {
return 1;
}
if (rx_TownhouseIndicator.test(value)) {
return 2;
}
if (rx_TimeshareIndicator.test(value)) {
return 3;
}
return 4;
}
Live Example:
var rx_CondoIndicator = /\bCONDO(MINIUM)?(?!S)/;
var rx_TownhouseIndicator = /\bTOWN\s*(HOUSE|HOME)(?!S)/;
var rx_TimeshareIndicator = /\bTIMESHARE(?!S)/;
var arrUnitNumber = ['TIMESHARE A-1', 'TOWNHOUSE 407', 'CONDO #13'];
arrUnitNumber.sort(function(x, y) {
return getSortingKey(x) - getSortingKey(y);
});
arrUnitNumber.forEach(function(entry) {
snippet.log(entry);
});
function getSortingKey(value) {
if (rx_CondoIndicator.test(value)) {
return 1;
}
if (rx_TownhouseIndicator.test(value)) {
return 2;
}
if (rx_TimeshareIndicator.test(value)) {
return 3;
}
return 4;
}
<!-- Script provides the `snippet` object, see http://meta.stackexchange.com/a/242144/134069 -->
<script src="http://tjcrowder.github.io/simple-snippets-console/snippet.js"></script>
If there are a lot of entries, though, that's inefficient because it has to recalculate the sorting key every time two entries in the array are compared, and so does it repeatedly even for the same value (potentially). So if there are a large number of entries, you're probably best off building an array of objects with the sort key on them instead:
var unitObjects = arrUnitNumber.map(function(entry) {
return {
str: entry,
key: getSortingKey(entry)
};
});
unitObjects.sort(function(x, y){
return x.key - y.key;
});
Then either just use that array, or if you want strings again, just map at the end:
arrUnitNumber = unitObjects.map(function(entry) {
return entry.str;
});
But again, only if there are a lot (lot) of entries in the array.
Live Example:
var rx_CondoIndicator = /\bCONDO(MINIUM)?(?!S)/;
var rx_TownhouseIndicator = /\bTOWN\s*(HOUSE|HOME)(?!S)/;
var rx_TimeshareIndicator = /\bTIMESHARE(?!S)/;
var arrUnitNumber = ['TIMESHARE A-1', 'TOWNHOUSE 407', 'CONDO #13'];
var unitObjects = arrUnitNumber.map(function(entry) {
return {
str: entry,
key: getSortingKey(entry)
};
});
unitObjects.sort(function(x, y){
return x.key - y.key;
});
unitObjects.forEach(function(entry) {
snippet.log(entry.str);
});
function getSortingKey(value) {
if (rx_CondoIndicator.test(value)) {
return 1;
}
if (rx_TownhouseIndicator.test(value)) {
return 2;
}
if (rx_TimeshareIndicator.test(value)) {
return 3;
}
return 4;
}
<!-- Script provides the `snippet` object, see http://meta.stackexchange.com/a/242144/134069 -->
<script src="http://tjcrowder.github.io/simple-snippets-console/snippet.js"></script>
Side note: You don't need (or want) the new RegExp( part where you're defining your regular expressions, JavaScript is unusual in that it has regular expression literals (like string literals); that's what you have in your /.../:
var rx_CondoIndicator = /\bCONDO(MINIUM)?(?!S)/;
var rx_TownhouseIndicator = /\bTOWN\s*(HOUSE|HOME)(?!S)/;
var rx_TimeshareIndicator = /\bTIMESHARE(?!S)/;
First, put all of your regexes in an array. The order in which they are listed will determine their rank, where the first element is the highest.
var so_UnitKeywordIndicator = [rx_CondoIndicator, rx_TownhouseIndicator, rx_TimeshareIndicator];
Now this function will give the rank for a given unit.
function getSortRank(unit) {
for (var i = 0; i < so_UnitKeywordIndicator.length; i++) {
if (so_UnitKeywordIndicator[i].test(unit)) {
return i;
}
}
return so_UnitKeywordIndicator.length;
}
Finally, you can then use this function to sort your array of units like this:
var sortedUnits = arrUnitNumber.sort(function(x, y) {
return getSortRank(x) - getSortRank(y);
});
just for sharing my problem & solution. I need to order my format data by tag.
const nameTagDatas = ["name(ddd)", "name(ccc)", "name(bbb)", "name(aaa)"];
const tagOrder = ["aaa", "bbb", "ccc", "ddd"];
nameTagDatas.sort(
(a, b) =>
tagOrder.findIndex((e) => a.search(new RegExp("\\(" + e + "\\)")) != -1) -
tagOrder.findIndex((e) => b.search(new RegExp("\\(" + e + "\\)")) != -1)
);
console.log(nameTagDatas) // [ 'name(aaa)', 'name(bbb)', 'name(ccc)', 'name(ddd)' ]
extract function
nameTagDatas.sort(
(a, b) => findIndexByTag(tagOrder, a) - findIndexByTag(tagOrder, b)
);
function findIndexByTag(_orderArr, _searchStr) {
return _orderArr.findIndex(
(e) => _searchStr.search(new RegExp("\\(" + e + "\\)")) != -1
);
}
You can use .match(/<regex>/)
Here is JS
let wordsArr = ['2.World', '1.Hello'];
const regex = /\d/
let answer = wordsArr.sort((a,b) => {
return a.match(regex) - b.match(regex);
});
console.log('answer', answer);
Here is a fiddle.
I need a regular expression in javascript that will get a string with a specific substring from a list of space delimited strings.
For example, I have;
widget util cookie i18n-username
I want to be able to return only i18n-username.
How
You could use the following function, using a regex to match for your string surrounded by either a space or the beginning or end of a line. But you'll have to be careful about preparing any regular expression special characters if you plan to use them, since the search argument will be interpreted as a string instead of a RegExp literal:
var hasClass = function(s, klass) {
var r = new RegExp("(?:^| )(" + klass + ")(?: |$)")
, m = (""+s).match(r);
return (m) ? m[1] : null;
};
hasClass("a b c", "a"); // => "a"
hasClass("a b c", "b"); // => "b"
hasClass("a b c", "x"); // => null
var klasses = "widget util cookie i18n-username";
hasClass(klasses, "username"); // => null
hasClass(klasses, "i18n-username"); // => "i18n-username"
hasClass(klasses, "i18n-\\w+"); // => "i18n-username"
As others have pointed out, you could also simply use a "split" and "indexOf":
var hasClass = function(s, klass) {
return (""+s).split(" ").indexOf(klass) >= 0;
};
However, note that the "indexOf" function was introduced to JavaScript somewhat recently, so for older browsers you might have to implement it yourself.
var hasClass = function(s, klass) {
var a=(""+s).split(" "), len=a.length, i;
for (i=0; i<len; i++) {
if (a[i] == klass) return true;
}
return false;
};
[Edit]
Note that the split/indexOf solution is likely faster for most browsers (though not all). This jsPerf benchmark shows which solution is faster for various browsers - notably, Chrome must have a really good regular expression engine!
function getString(subString, string){
return (string.match(new RegExp("\S*" + subString + "\S*")) || [null])[0];
}
To Use:
var str = "widget util cookie i18n-username";
getString("user", str); //returns i18n-username
Does this need to be a regex? Would knowing if the string existed be sufficient? Regular expressions are inefficient (slower) and should be avoided if possible:
var settings = 'widget util cookie i18n-username',
// using an array in case searching the string is insufficient
features = settings.split(' ');
if (features.indexOf('i18n-username') !== -1) {
// do something based on having this feature
}
If whitespace wouldn't cause an issue in searching for a value, you could just search the string directly:
var settings = 'widget util cookie i18n-username';
if (settings.indexOf('i18n-username') !== -1) {
// do something based on having this value
}
It then becomes easy to make this into a reusable function:
(function() {
var App = {},
features = 'widget util cookie i18n-username';
App.hasFeature = function(feature) {
return features.indexOf(feature) !== -1;
// or if you prefer the array:
return features.split(' ').indexOf(feature) !== -1;
};
window.App = App;
})();
// Here's how you can use it:
App.hasFeature('i18n-username'); // returns true
EDIT
You now say you need to return all strings that start with another string, and it is possible to do this with a regular expression as well, although I am unsure about how efficient it is:
(function() {
var App = {},
features = 'widget util cookie i18n-username'.split(' ');
// This contains an array of all features starting with 'i18n'
App.i18nFeatures = features.map(function(value) {
return value.indexOf('i18n') === 0;
});
window.App = App;
})();
/i18n-\w+/ ought to work. If your string has any cases like other substrings can start with i18n- or your user names have chars that don't fit the class [a-zA-Z0-9_], you'll need to specify that.
var str = "widget util cookie i18n-username";
alert(str.match(/i18n-\w+/));
Edit:
If you need to match more than one string, you can add on the global flag (/g) and loop through the matches.
var str = "widget i18n-util cookie i18n-username";
var matches = str.match(/i18n-\w+/g);
if (matches) {
for (var i = 0; i < matches.length; i++)
alert(matches[i]);
}
else
alert("phooey, no matches");