This question already has answers here:
How do I replace a character at a particular index in JavaScript?
(30 answers)
Closed 9 years ago.
SITUATION
I started to implement the game "HANGMAN" using only JavaScript and HTML on client side machines. I have completed all the logical work. Now only the aesthetics are remaining. And I am stuck up because we can't write to strings in JS.
a = "abcd"; a[0] = "e"; -- This type of thing is not allowed.
What I have done
CODE
The code I have tried, This does not work:
See here
I now have the following three arrays:
a -- The array that contains the letters of the word(for gel this would be g,e,l)
gai -- This array consists of the letters that have been entered by the user and are there in the word.(like g or e)
ag -- This array consists of the letters that have been entered by the user regardless of whether or not they are there in the word to be guessed or not.
What I need to do
I need to generate the output that would show the letter in case it has been guessed by the user and is there in the word(letters in the array gai) and _ in place of letters that have not yet been entered by the user.
What I want
A function that will return the desired output, as explained in the following example.
Example
Let the word to be guessed be:
together
User enters e:
The function should return: _ _ _ e _ _ e _
User then enters d, my program will prompt him to say that this letter is not there in the word.
User enters r
Function should return: _ _ _ e _ _ e r
And so on....
I hope you get the idea!
In case you don't get it(owing to my bad explanation or otherwise!!)
You can play a game of hangman here: Hangman
Just watch the output at the bottom of the screen... Thats what I want this function to generate.
Kindly help me with this issue!
Reading just the start of your question (because no other code is provided), there is a problem which could be solved using native javascript functions.
var strarr = "Hello world!".split(''); // this will give you an array of characters to be manipulated
strarr[6] = "X"; // so you can do this
console.dir(strarr.join('')); // test it!
As an idea (not mine, its from comments) one could even wrap this simple code as a missing toCharArray() function and use that.
Another thing is that this method is fast enough even for relatively large bulk of text. For a test I used lorem ipsum with 1000 words. On my rather old dev machine, manipulation executes in milliseconds.
For more information, see this discussion.
Also you can use this function to set char at specified index of string:
function setCharAt(str,index,chr) {
if(index > str.length-1) return str;
return str.substr(0,index) + chr + str.substr(index+1);
}
You could use regex to mask letters in a word using String.replace.
var word = "together"
, correct = "";
function guess(c) {
if (word.indexOf(c) > -1) {
correct += c;
}
console.log(word.replace(new RegExp("[^" + correct + "]", "g"), "_"));
}
Related
This question already has an answer here:
Find all regex matches
(1 answer)
Closed last year.
Okay, so I have this string "nesˈo:tkʰo:x", and I want to get the index of all the zero-width positions that don't occur after any instance of the character ˈ (the IPA primary stress symbol). So in this case, those expected output would be 0, 1, 2, and 3 - the indices of the letters nes that occur before the one and only instance of ˈ, plus the ˈ itself.
I'm doing this with regex for reasons I'll get into in a bit. Regex101 confirms that /(?=.*?ˈ)/ should match all 4 of those zero-width positions with JS' regex flavor... but I can't actually get JS to return them.
A simple setup might look like this:
let teststring = "nesˈo:tkʰo:x";
let re = new RegExp("(?=.*?ˈ)", "g");
while (result = re.exec(teststring)) {
console.log("Match found at "+result.index);
}
...except that this loops forever. It seems to get stuck on the first match, which I understand has something to do with how RegExp.exec is supposed to auto-increment RegExp.lastIndex for global regexes, or something. But I also can't make the regex not global, or it won't return all the matches for strings like this where more than one match is expected.
Okay, so what if I manually increment RegExp.lastIndex to prevent it from looping?
let teststring = "nesˈo:tkʰo:x";
let re = new RegExp("(?=.*?ˈ)", "g");
while (result = re.exec(teststring)) {
if (result.index == re.lastIndex) {
re.lastIndex++;
} else {
console.log("Match found at "+result.index);
}
}
Now it... prints out nothing at all. Now, to be fair, if lastIndex starts at 0 by default, and the index of the first match is 0, I half expect that to be skipped over... but why isn't it at least giving me 1, 2 and 3 as matches?
Now, I can already hear the chorus of "you don't need regex for this, just do Array(teststring.indexOf("ˈ")).keys() or something to generate [0,1,2,3]". That may work for this specific example, but the actual use case is a parser function that's supposed to be a general solution for "for this input string, replace all instances of A with B, if condition C is true, unless condition D is true". Those conditions might be something like "if A is at the end of the string" or "if A is right next to another instance of A" or "if A is between 'n' and 't'". That kind of complicated string matching problem is why the parser creates and executes regexes on the fly and why regex is getting involved, and it does work for almost everything except this one annoying edge case, which I'd rather not have to refactor the entire mechanism of the parser to deal with if I don't have to.
Use String.prototype.matchAll() to get all the matches.
let teststring = "nesˈo:tkʰo:x";
let re = new RegExp("(?=.*?ˈ)", "g");
[...teststring.matchAll(re)].forEach(result =>
console.log("Match found at " + result.index)
)
.search() returns the index of a match. .exec() returns an array of the match. Note a look ahead (?=) isn't needed, a standard capture group () suffices.
const str =`nesˈo:tkʰo:x",`;
const rgx = /(.*?ˈ)/;
let first = str.search(rgx);
let last = rgx.exec(str)[0].length - 1;
console.log('Indices: '+first+' - '+(first + last)+' \nLength: '+(last+1));
At the time of login, I need to allow either username (alphanumeric and some special characters) or email address or username\domain format only. For this purpose, I used this regex with or (|) condition. Along with this, I need to allow some other language characters like Japanese, Chinese etc., so included those as well in the same regex. Now, the issue is when I enter characters (>=30) and # or some special character, the evaluation of this regex is taking some seconds and browser goes in hang mode.
export const usernameRegex = /(^[a-zA-Z0-9._~^#!%+\-]+#[a-z0-9.-]+\.[a-z]{2,4})+|^[a-zA-Z0-9._~^#!\-]+\\([._-~^#!]|[\p{Ll}\p{Lm}\p{Lt}a-zA-Z0-9-\u3000-\u303f\u3040-\u309f\u30a0-\u30ff\uff00-\uff9f\u4e00-\u9faf\u3400-\u4dbf\u3130-\u318F\uAC00-\uD7AF])+|^([._-~^#!]|[\p{Ll}\p{Lm}\p{Lt}a-zA-Z0-9-\u3000-\u303f\u3040-\u309f\u30a0-\u30ff\uff00-\uff9f\u4e00-\u9faf\u3400-\u4dbf\u3130-\u318F\uAC00-\uD7AF])+$/gu;
When I tried removing the other language character set such as [\p{Ll}\p{Lm}\p{Lt}a-zA-Z0-9-\u3000-\u303f\u3040-\u309f\u30a0-\u30ff\uff00-\uff9f\u4e00-\u9faf\u3400-\u4dbf\u3130-\u318F\uAC00-\uD7AF])+|^([._-~^#!]|[\p{Ll}\p{Lm}\p{Lt}a-zA-Z0-9-\u3000-\u303f\u3040-\u309f\u30a0-\u30ff\uff00-\uff9f\u4e00-\u9faf\u3400-\u4dbf\u3130-\u318F\uAC00-\uD7AF] it works fine.
I understood that generally regex looks simple but it does a lot under the hood. Is there any modification that needs to be done in this regex, so that it doesn't take time to evaluate. Any help is much appreciated!
Valid texts:
stackoverflow,
stackoverflow1~,
stackoverflow!#~^-,
stackoverflow#g.co,
stackoverflow!#~^-#g.co,
こんにちは,
你好,
tree\guava
EDIT:
e.g. Input causing the issue
stackoverflowstackoverflowstackoverflow#
On giving the above text it is taking long time.
https://imgur.com/T2Vg4lg
Your regex seems to consist of three regular expressions concatenated with |
(^[a-zA-Z0-9._~^#!%+\-]+#[a-z0-9.-]+\.[a-z]{2,4})+
^[a-zA-Z0-9._~^#!\-]+\\([._-~^#!]|[\p{Ll}\p{Lm}\p{Lt}a-zA-Z0-9-\u3000-\u303f\u3040-\u309f\u30a0-\u30ff\uff00-\uff9f\u4e00-\u9faf\u3400-\u4dbf\u3130-\u318F\uAC00-\uD7AF])+
^([._-~^#!]|[\p{Ll}\p{Lm}\p{Lt}a-zA-Z0-9-\u3000-\u303f\u3040-\u309f\u30a0-\u30ff\uff00-\uff9f\u4e00-\u9faf\u3400-\u4dbf\u3130-\u318F\uAC00-\uD7AF])+$
first regex (^...)+ how many times do you think this entire pattern can occur that starts at the beginning of the string. Either it's a second occurence OR it starts at the beginning of the string it can't be both.
So ^[a-zA-Z0-9._~^#!%+\-]+#[a-z0-9.-]+\.[a-z]{2,4}
parts 2 and 3 are mostly identical, only that nr. 2 contains this block [a-zA-Z0-9._~^#!\-]+\\ followed by what's the rest of the 3rd part.
So let's combine them: ^(?:[a-zA-Z0-9._~^#!\-]+\\)? ... and make sure to use non-capturing groups when possible.
([abc]|[def])+ can be simplified to [abcdef]+. This btw. is the part that's killing your performance.
your regex ends with a $. This was only part of the last part, but I assume you always want to match the entire string? So let's make all 3 (now 2) parts ^ ... $
Summary:
/^[a-zA-Z0-9._~^#!%+-]+#[a-z0-9.-]+\.[a-z]{2,4}$|^(?:[a-zA-Z0-9._~^#!-]+\\)?[._-~^#!\p{Ll}\p{Lm}\p{Lt}a-zA-Z0-9-\u3000-\u303f\u3040-\u309f\u30a0-\u30ff\uff00-\uff9f\u4e00-\u9faf\u3400-\u4dbf\u3130-\u318F\uAC00-\uD7AF]+$/u
A JS example how a simple regex would try to match a string, and how it fails, backtracks, retries with the other side of the | and so on, and so on.
// let's implement what `/([a-z]|[\p{Ll}])+/u` would do,
// how it would try to match something.
const a = /[a-z]/; // left part
const b = /[\p{Ll}]/u; // right part
const string = "abc,";
const testNextCharacter = (index) => {
if (index === string.length) {
return true;
}
const pattern = index + " ".repeat(index + 1) + "%o.test(%o)";
const character = string.charAt(index);
console.log(pattern, a, character);
// checking the left part && if successful checking the next character
if (a.test(character) && testNextCharacter(index + 1)) {
return true;
}
// checking the right part && if successful checking the next character
console.log(pattern, b, character);
if (b.test(character) && testNextCharacter(index + 1)) {
return true;
}
return false;
}
console.log("result", testNextCharacter(0));
.as-console-wrapper{top:0;max-height:100%!important}
And this are only 4 characters. Why don't you try this with 5,6 characters to get an impression how much work this will be at 20characters.
I'm using RPG Maker MV which is a game creator that uses JavaScript to create plugins. I have a plugin in JavaScript already, however I'm trying to edit a part of the plugin so that it basically checks if a certain string exists in a character in the game and if it does, then sets specific variables to numbers within that string.
for (var i = 0; i < page.list.length; i++) {
if (page.list[i].code == 108 && page.list[i].parameters[0].contains("<post:" + (n) + "," + (n) + ">")) {
var post = page.list[i].parameters[0];
var array = post.split(',');
this._origMovement.x = Number(array[1]);
this._origMovement.y = Number(array[1]);
break;
};
};
So I know the first 2 lines work and contains works when I only put a specific string. However I can't figure out how to check for 2 numbers that are separated by a comma and wrapped in '<>' tags, without knowing what the numbers would be.
Then it needs to extract those numbers and assign one to this._origMovement.x and the other to this._origMovement.y.
Any help would be greatly appreciated.
This is one of those rare cases where I'd use a regular expression. If you haven't come across regular expressions before I suggest reading an introduction to them, such as this one: https://regexone.com/
In your case, you probable want something like this:
var myRegex = /<post:(\d+),(\d+)>/;
var matches = myParameter.match(myRegex);
this._origMovement.x = matches[1]; //the first number
this._origMovement.y = matches[2]; //the second number
The myRegex variable is a regular expression that looks for the pattern you describe, and has 2 capture groups which look for a string of one or more digits (\d+ means "one or more digits"). The result of the .match() call gives you an array containing the entire match and the results of the capture groups.
If you want to allow for decimal numbers, you'll need to use a different capture group that allows for a decimal point, such as ([\d\.]+), which means "a sequence of one or more digits and decimal points", or more sophisticated, (\d+\.?\d*), which is "a sequence of one or more digits, following by an optional decimal point, followed by zero or more digits).
There are lots of good tutorials around to help you write good regular expressions, and sites that will help you live-test your expressions to make sure they work correctly. They're a powerful tool, but be careful not to over-use them!
Got it to work. For anyone who may ever be interested, the code is below.
for (var i = 0; i < page.list.length; i++) {
if (page.list[i].code == 108 && page.list[i].parameters[0].contains("<post:")) {
var myRegex = /<post:(\d+),(\d+)>/;
var matches = page.list[i].parameters[0].match(myRegex);
this._origMovement.x = matches[1]; //the first number
this._origMovement.y = matches[2]; //the second number
break;
}
};
var name = "AlbERt EINstEiN";
function nameChanger(oldName) {
var finalName = oldName;
// Your code goes here!
finalName = oldName.toLowerCase();
finalName = finalName.replace(finalName.charAt(0), finalName.charAt(0).toUpperCase());
for(i = 0; i < finalName.length; i++) {
if (finalName.charAt(i) === " ")
finalName.replace(finalName.charAt(i+1), finalName.charAt(i+1).toUpperCase());
}
// Don't delete this line!
return finalName;
};
// Did your code work? The line below will tell you!
console.log(nameChanger(name));
My code as is, returns 'Albert einstein'. I'm wondering where I've gone wrong?
If I add in
console.log(finalName.charAt(i+1));
AFTER the if statement, and comment out the rest, it prints 'e', so it recognizes charAt(i+1) like it should... I just cannot get it to capitalize that first letter of the 2nd word.
There are two problems with your code sample. I'll go through them one-by-one.
Strings are immutable
This doesn't work the way you think it does:
finalName.replace(finalName.charAt(i+1), finalName.charAt(i+1).toUpperCase());
You need to change it to:
finalName = finalName.replace(finalName.charAt(i+1), finalName.charAt(i+1).toUpperCase());
In JavaScript, strings are immutable. This means that once a string is created, it can't be changed. That might sound strange since in your code, it seems like you are changing the string finalName throughout the loop with methods like replace().
But in reality, you aren't actually changing it! The replace() function takes an input string, does the replacement, and produces a new output string, since it isn't actually allowed to change the input string (immutability). So, tl;dr, if you don't capture the output of replace() by assigning it to a variable, the replaced string is lost.
Incidentally, it's okay to assign it back to the original variable name, which is why you can do finalName = finalName.replace(...).
Replace is greedy
The other problem you'll run into is when you use replace(), you'll be replacing all of the matching characters in the string, not just the ones at the position you are examining. This is because replace() is greedy - if you tell it to replace 'e' with 'E', it'll replace all of them!
What you need to do, essentially, is:
Find a space character (you've already done this)
Grab all of the string up to and including the space; this "side" of the string is good.
Convert the very next letter to uppercase, but only that letter.
Grab the rest of the string, past the letter you converted.
Put all three pieces together (beginning of string, capitalized letter, end of string).
The slice() method will do what you want:
if (finalName.charAt(i) === " ") {
// Get ONLY the letter after the space
var startLetter = finalName.slice(i+1, i+2);
// Concatenate the string up to the letter + the letter uppercased + the rest of the string
finalName = finalName.slice(0, i+1) + startLetter.toUpperCase() + finalName.slice(i+2);
}
Another option is regular expression (regex), which the other answers mentioned. This is probably a better option, since it's a lot cleaner. But, if you're learning programming for the first time, it's easier to understand this manual string work by writing the raw loops. Later you can mess with the efficient way to do it.
Working jsfiddle: http://jsfiddle.net/9dLw1Lfx/
Further reading:
Are JavaScript strings immutable? Do I need a "string builder" in JavaScript?
slice() method
You can simplify this down a lot if you pass a RegExp /pattern/flags and a function into str.replace instead of using substrings
function nameChanger(oldName) {
var lowerCase = oldName.toLowerCase(),
titleCase = lowerCase.replace(/\b./g, function ($0) {return $0.toUpperCase()});
return titleCase;
};
In this example I've applied the change to any character . after a word boundary \b, but you may want the more specific /(^| )./g
Another good answer to this question is to use RegEx to do this for you.
var re = /(\b[a-z](?!\s))/g;
var s = "fort collins, croton-on-hudson, harper's ferry, coeur d'alene, o'fallon";
s = s.replace(re, function(x){return x.toUpperCase();});
console.log(s); // "Fort Collins, Croton-On-Hudson, Harper's Ferry, Coeur D'Alene, O'Fallon"
The regular expression being used may need to be changed up slightly, but this should give you an idea of what you can do with regular expressions
Capitalize Letters with JavaScript
The problem is twofold:
1) You need to return a value for finalName.replace, as the method returns an element but doesn't alter the one on which it's predicated.
2) You're not iterating through the string values, so you're only changing the first word. Don't you want to change every word so it's in lower case capitalized?
This code would serve you better:
var name = "AlbERt EINstEiN";
function nameChanger(oldName) {
// Your code goes here!
var finalName = [];
oldName.toLowerCase().split(" ").forEach(function(word) {
newWord = word.replace(word.charAt(0), word.charAt(0).toUpperCase());
finalName.push(newWord);
});
// Don't delete this line!
return finalName.join(" ");
};
// Did your code work? The line below will tell you!
console.log(nameChanger(name));
if (finalName.charAt(i) === " ")
Shouldn't it be
if (finalName.charAt(i) == " ")
Doesn't === check if the object types are equal which should not be since one it a char and the other a string.
I think I'm asking this kind of question in the right place, the "discussion" tag's description seems to fit. If I'm mistaking, well I'm sorry :s
So here goes, my question concerns a codefights challenge. The challenge was:
Given a string, transform it (by shuffling its contents) into a palindrome!
A palindrome is a string that reads the same left-to-right and right-to-left.
Example:
"abcd"->"imp"
"cdcd"->"cddc"
"AABBaabb33!!??"->"!3?ABabbaBA?3!"
The input is a string and can contain letters ,digits and/or ? ,!
If it is not possible you should return imp (for impossible) and if there is more than one solution return the first one in the lexicographic order
I failed solving it so I went and had a look at the best solution the day after:
function outputString(s) {
s = s.split(a = b = c = "").sort()
i = 0
while (x = s[i])
x == s[++i] ?
[a += x, c = x + c, i++] :
b += x
return b[1] ? "imp" : a+b+c
}
But I don't understand it.
Let me try to explain what I don't understand exactly:
What exactly is this input for the split function ?
I understand that inside the while there is a shorthand if but what is done in it is incomprehensible for me :s
I understand that these kind of question don't really have their place here but I think I managed to make it so that a short, to the point answer is possible.
If someone could break these two elements down for me. I'd appreciate it.
The split is actually split("") which returns an array of single character strings. The assignment is returning what was assigned, so the code is also initializing a, b and c there, but it doesn't affect the split.
In the while the program is looping through the ordered characters and creates the result the following way: add the letter after a and in front of c if the next letter is the same and step two ahead (note the additional i++), otherwise add the letter to b. In the end if there was more than one single letter (only one can be in the middle) return "imp" otherwise a+b+c (which will be the first solution when this algorithm is used).
See expanded version of Andreas.