I have been attempting to solve this codewars problem for a while in JavaScript:
"Complete the solution so that the function will break up camel casing, using a space between words. Example:"
"camelCasing" => "camel Casing"
"identifier" => "identifier"
"" => ""
I have it almost all the way, but for some reason my code is selecting the wrong space to add a blank space.
I'm hoping someone can tell me what I am doing wrong.
function solution(string) {
let splitStr = string.split("");
let newStr = string.split("");
let capStr = string.toUpperCase().split("");
for (i = 0; i < splitStr.length; i++) {
if (splitStr[i] === capStr[i]) {
newStr.splice(i, 0, ' ');
}
}
return newStr.join("");
}
console.log('camelCasing: ', solution('camelCasing'));
console.log('camelCasingTest: ', solution('camelCasingTest'));
The first insertion into newStr will be at the correct spot, but after that insertion of the space, the letters that follow it in newStr will be at an increased index. This means that when the next capital is found at i in splitStr (which did not change), the insertion into newStr (which did change) should really be at i+1.
A solution is to make your loop iterate from end to start:
function solution(string) {
let splitStr = string.split("");
let newStr = string.split("");
let capStr = string.toUpperCase().split("");
for (i = splitStr.length - 1; i >= 0; i--) {
if (splitStr[i] === capStr[i]) {
newStr.splice(i, 0, ' ');
}
}
return newStr.join("");
}
console.log('camelCasing: ', solution('camelCasing'));
console.log('camelCasingTest: ', solution('camelCasingTest'));
This kind of problem is however much easier solved with a regular expression:
function solution(string) {
return string.replace(/[A-Z]/g, " $&");
}
console.log('camelCasing: ', solution('camelCasing'));
console.log('camelCasingTest: ', solution('camelCasingTest'));
Explanation of the regular expression:
[A-Z] a capital letter from the Latin alphabet.
$& backreference to the matched letter, used in the replacement.
g global flag so all matches are replaced.
Here could be a solution with a simple loop and some if conditions
const breakCamelCase = (word) => {
let result = "";
// loop on letter
for (let letter of word) {
// if letter is uppercase and not the first letter of the word add a space followed by the letter
if (letter == letter.toUpperCase() && result) {
result += ` ${letter}`;
} else { // else just add the letter
result += letter;
}
}
return result;
}
function solution(string) {
let splitStr = string.split("");
let newStr = "";
splitStr.forEach(e =>{
if(e === e.toUpperCase()) newStr +=" "+e;
else newStr += e;
});
return newStr;
}
console.log(solution('camelCasing'));//success = "camel Casing"
console.log(solution('camelCasingTest'));
Related
new to coding I'm trying to make a function that makes "abbreviations/acronyms" of words, e.g. 'I love you' -> 'ily'.
I've tried rewriting the code in many ways but console.log only shows me the first letter of the first given word.
function makeAbbr(words) {
let abbrev = words[0];
let after = 0;
let i = 0;
for (const letter of words) {
if (letter === '') {
i = words.indexOf('', after);
abbrev += words[i + 1];
}
after++;
}
return abbrev;
}
const words = 'a bc def';
let result = makeAbbr(words);
console.log(result)
Without using arrays. But you really should learn about them.
Start by trimming leading and trailing whitespace.
Add the first character to your acronym.
Loop over the rest of the string and add the current character to the acronym if the previous character was a space (and the current character isn't).
function makeAbbr(words) {
words = words.trim();
const length = words.length;
let acronym = words[0];
for(let i = 1; i < length; i++) {
if(words[i - 1] === ' ' && words[i] !== ' ') {
acronym += words[i];
}
}
return acronym;
}
console.log(makeAbbr('I love you'));
console.log(makeAbbr('I love you'));
console.log(makeAbbr(' I love you '));
And here's the version for GottZ
function w(char) {
char = char.toLocaleLowerCase();
const coll = Intl.Collator('en');
const cmpA = coll.compare(char, 'a');
const cmpZ = coll.compare(char, 'z');
return cmpA >= 0 && cmpZ <= 0;
}
function makeAbbr(words) {
words = words.trim();
const length = words.length;
if(!length) return '';
let acronym = words[0];
for(let i = 1; i < length; i++) {
if(!w(words[i - 1]) && w(words[i])) {
acronym += words[i];
}
}
return acronym;
}
console.log(makeAbbr('I love you'));
console.log(makeAbbr('I love you'));
console.log(makeAbbr(' I love you '));
console.log(makeAbbr(' \tI ... ! love \n\r .you '));
console.log(makeAbbr(' \tI ... ! Löve \n\r .ÿou '));
Since you wanted something using your approach, try this (code is commented)
function makeAbbr(words) {
let abbrev = "";
for (let i = 0; i < words.length - 1; i++) { // Loop through every character except the last one
if (i == 0 && words[i] != " ") { // Add the first character
abbrev += words[i];
} else if (words[i] == " " && words[i + 1] != " ") { // If current character is space and next character isn't
abbrev += words[i + 1];
}
}
return abbrev.toLowerCase();
}
const words = 'a bc def';
let result = makeAbbr(words);
console.log(result)
here is my implementation of your function:
Split the sentence into an array, get the first letter of each word and join them into one string.
const makeAbbr = string => string.split(' ').map(word => word[0]).join('');
console.log(makeAbbr('stack overflow'));
console.log(makeAbbr('i love you'));
`
If you want to use your approach exactly, you had a typo on the line specified. A character can never be "" (an empty string), but a character can be a space " ". Fixing this typo makes your solution work.
function makeAbbr(words) {
let abbrev = words[0];
let after = 0;
let i = 0;
for (const letter of words) {
if (letter === ' ') { // This line here
i = words.indexOf(' ', after);
abbrev += words[i + 1];
}
after++;
}
return abbrev.toLowerCase(); // Also added .toLowerCase()
}
const words = 'a bc def';
let result = makeAbbr(words);
console.log(result)
There are couple of things tripping you up.
let abbrev = words[0]; is just taking the first letter of the word string you passed into the function, and at some point adding something new to it.
for (const letter of words) {...}: for/of statements are used for iterating over arrays, not strings.
Here's a remixed version of your code. It still uses for/of but this time we're creating an array of words from the string and iterating over that instead.
function makeAbbr(str) {
// Initialise `abbrev`
let abbrev = '';
// `split` the string into an array of words
// using a space as the delimiter
const words = str.split(' ');
// Now we can use `for/of` to iterate
// over the array of words
for (const word of words) {
// Now concatenate the lowercase first
// letter of each word to `abbrev`
abbrev += word[0].toLowerCase();
}
return abbrev;
}
console.log(makeAbbr('I love you'));
console.log(makeAbbr('One Two Three Four Five'));
I am trying to create a function that prints each word on a new line. The argument given is a string with words that aren't separated by a space but capitalized except the first word i.e. "helloMyNameIsMark". I have something that works but wondering if there's a better way of doing this in javaScript.
separateWords = (string) => {
const letters = string.split('');
let word = "";
const words = letters.reduce((acc, letter, idx) => {
if (letter === letter.toUpperCase()) {
acc.push(word);
word = "";
word = word.concat(letter);
} else if (idx === letters.length - 1) {
word = word.concat(letter);
acc.push(word);
} else {
word = word.concat(letter)
}
return acc
}, []);
words.forEach(word => {
console.log(word)
})
}
You could use the regex [A-Z] and replace each upper case letter with \n prefix
const separateWords = str => str.replace(/[A-Z]/g, m => '\n' + m)
console.log(separateWords('helloMyNameIsMark'))
Or you could use a lookahead (?=[A-Z]) to split at each upper case letter to get an array of words. Then loop through the array to log each word:
const separateWords = str => str.split(/(?=[A-Z])/g)
separateWords('helloMyNameIsMark').forEach(w => console.log(w))
I would separate the breaking of words into an array and the printing of that array into two distinct functions. Regular expressions make that first part much easier than your reduce call. (But that reduce is a good thought if you don't see a regex solution.)
My version might look like this:
const separateWords = (str) => str .replace (/([A-Z])/g, " $1") .split (' ')
const printSeparateWords = (str) => separateWords (str) .forEach (word => console.log (word) )
printSeparateWords ("helloMyNameIsMark")
Very similar to adiga's answer, but it can actually be simpler:
const separateWords = str => str.replace(/[A-Z]/g, '\n$&');
This will also benefit from improved performance (might matter if used at scale).
Here's a more literal interpretation of your stated requirement: prints each word on a new line.
function separateWords(str){
let currentWord = '';
for (let chr of str){
if (chr == chr.toUpperCase()){
console.log(currentWord);
currentWord = chr;
} else {
currentWord += chr;
}
}
if (currentWord)
console.log(currentWord);
}
separateWords('helloMyNameIsMark');
This test objective is to take the first consonant (or consonant cluster) of an English word, moves it to the end of the word and suffixes an "ay". If a word begins with a vowel you just add "way" to the end.
My main issue is to iterate through the string until it reaches a vowel and put the first consonants letters to the end of the string.
I’m having trouble iterating through the string until it reaches a vowel. How do you iterate a string until it stops at the first vowel? How do you get all of the first consonant letters before it reaches vowel? I iterate the string by using the for loop, and I use the “if” statement if the first letters are consonant.
function translatePigLatin(str) {
var vowel = /[aeiou]/g;
var cons = /[^aeiou]/g;
console.log(cons.test(str[2]))
var consonants = [];
var index = str.charAt(0);
console.log(str.slice(1, str.length))
for(var i = 0; i < str.length; i++){
console.log(str[i])
if(cons.test(str[i])){
consonants.push(str.charAt(i));
console.log(consonants)
var slice = str.slice(consonants.length, str.length);
}
}
return consonants + 'ay';
}
translatePigLatin("glove");
I would focus on just finding the index of the first vowel. Then you can test whether that index is 0 or something else. If it's zero, just add the way otherwise slice on that index:
function translatePigLatin(str) {
const vowels = ['a', 'e', 'i', 'o', 'u'];
let ind = [...str.toLowerCase()].findIndex(s => vowels.includes(s))
return ind
? str.slice(ind) + str.slice(0, ind) + 'ay' // doesn't start with vowel
: str + 'way'
}
console.log(translatePigLatin('glove'))
console.log(translatePigLatin('apple'))
console.log(translatePigLatin('Mark'))
console.log(translatePigLatin('Javascript'))
I'm not sure what the pig latin rules are for the edge case of a word with no vowels like rhythm.
I think you should break the problem into some basic cases, like I did in the code below:
Check if the first letter is a vowel; if true, just add 'way' to the end of the word and break the loop
If the current letter is a consonant (that means the first condition is not true), we just continue iterating
If the current letter is a vowel, we split the word into two separate parts and we add them on the other way around, not forgetting to ad the 'ay' suffix.
In the code below I also added to the vowels array the uppercase versions. With some modifications, it can be made to jump over delimiters. The Pig Latin rules were those I found on Wikipedia (although I did not find any rules for words only with consonants, so I put them in the first case).
Cheers!
function has(a, e) {
return a.indexOf(e) > -1;
}
function translateWord(str) {
let vows = ['a', 'e', 'i', 'o', 'u'];
vows = vows.concat(vows.map(v => v.toUpperCase()));
let lastVowelIndex = 0;
let conv = '';
for (let i = 0; i < str.length; ++i) {
let currentChar = str[i];
/* If the first character is a voewl, we just
* add the 'way' suffix to the initial word.
* I applied the same rule for words without
* consonants.
*/
if ((i === 0 && has(vows, currentChar))
|| (i === str.length - 1)) {
conv = str + 'way';
break;
}
/* If the current character is a consonant, we
* just continue until we reach a vowel.
*/
if (!has(vows, currentChar))
continue;
/* At this point, we reached a vowel, so we do the
* appropriate transformations.
*/
let firstPart = str.slice(0, i);
let secondPart = str.slice(i);
conv = secondPart + firstPart + 'ay';
break;
}
return conv;
}
function translateToPigLatin(str) {
let words = str.split(' ');
let converted = words.map(w => translateWord(w));
return converted.join(' ');
}
let s = translateToPigLatin("I have no rythm");
console.log(s);
Why not use a RegExp?
function translatePigLatin(str) {
let res = '';
let regex = /[aeiou]/;
if (!regex.test(str)) {
res = str + 'ay';
} else if (regex.test(str[0])) {
res = str + 'way';
} else {
let x = str.indexOf(regex.exec(str)[0]);
res = str.substr(x) + str.substr(0, x) + 'ay'
}
return res;
}
function titleCase(str) {
var str1 = str.match(/\S+\s*/g);
var str2;
for(var i = 0; i < str1.length; i++){
str2 = str1[i].toLowerCase().replace(str1[i].charAt(0), str1[i].charAt(0).toUpperCase());
}
return str2.join(' ');
}
titleCase("I'm a little tea pot");
What's wrong with my code? str2.join is not a function
Easiest way to go about this is to split the string on every space, then set the first letter of each element in the array to the capitalized version of the letter and join it back.
What you are doing is assigning the value of the result to str2, having a string type rather than an array, that is why join is not working for you.
function titleCase(str) {
const words = str.split(' ');
for (let i = 0; i < words.length; i++) {
words[i] = words[i][0].toUpperCase() + words[i].slice(1);
}
return words.join(' ');
}
A slightly different variant with some ES6 favor to it:
const titleCase = str => {
const result = [];
for (const word of str.split(' ')) {
result.push(word[0].toUpperCase() + word.slice(1));
}
return result.join(' ');
};
If you want to ensure space characters such as tabs, newlines etc. work, you can split using your regex or replace all whitespace characters with spaces as a first step, e.g.:
const words = str.replace(/\s/g, ' ').split(' ').filter(word => word !== '');
function titleCase(str) {
var str1 = str.match(/\S+\s*/g);
var str2 = [];
for(var i = 0; i < str1.length; i++){
str2[i] = str1[i].replace(str1[i].charAt(0), str1[i].charAt(0).toUpperCase());
}
return str2.join(' ');
}
titleCase("I'm a little tea pot");
This is a simple solution to your problem. However, there are many ways to get the same result this is one of them.
function capitalize(str) {
let str2 = str[0].toUpperCase();
return str.replace(str[0], str2);
}
I have a set of strings that I need to replace, but I need to keep the case of letters.
Both the input words and output words are of the same length.
For example, if I need to replace "abcd" with "qwer", then the following should happen:
"AbcD" translates to "QweR"
"abCd" translates to "qwEr"
and so on.
Right now I'm using JavaScript's replace, but capital letters are lost on translation.
r = new RegExp( "(" + 'asdf' + ")" , 'gi' );
"oooAsdFoooo".replace(r, "qwer");
Any help would be appreciated.
Here’s a helper:
function matchCase(text, pattern) {
var result = '';
for(var i = 0; i < text.length; i++) {
var c = text.charAt(i);
var p = pattern.charCodeAt(i);
if(p >= 65 && p < 65 + 26) {
result += c.toUpperCase();
} else {
result += c.toLowerCase();
}
}
return result;
}
Then you can just:
"oooAsdFoooo".replace(r, function(match) {
return matchCase("qwer", match);
});
I'll leave this here for reference.
Scenario: case-insensitive search box on list of items, partial match on string should be displayed highlighted but keeping original case.
highlight() {
const re = new RegExp(this.searchValue, 'gi'); // global, insensitive
const newText = name.replace(re, `<b>$&</b>`);
return newText;
}
the $& is the matched text with case
String.prototype.translateCaseSensitive = function (fromAlphabet, toAlphabet) {
var fromAlphabet = fromAlphabet.toLowerCase(),
toAlphabet = toAlphabet.toLowerCase(),
re = new RegExp("[" + fromAlphabet + "]", "gi");
return this.replace(re, function (char) {
var charLower = char.toLowerCase(),
idx = fromAlphabet.indexOf(charLower);
if (idx > -1) {
if (char === charLower) {
return toAlphabet[idx];
} else {
return toAlphabet[idx].toUpperCase();
}
} else {
return char;
}
});
};
and
"AbcD".translateCaseSensitive("abcdefg", "qwertyu")
will return:
"QweR"
Here's a replaceCase function:
We turn the input pattern into a regular expression
We have a nested replacer function which iterates through every character
We use regular expression /[A-Z]/ to identify capital letters, otherwise we assume everything is in lowercase
function replaceCase(str, pattern, newStr) {
const rx = new RegExp(pattern, "ig")
const replacer = (c, i) => c.match(/[A-Z]/) ? newStr[i].toUpperCase() : newStr[i]
return str.replace(rx, (oldStr) => oldStr.replace(/./g, replacer) )
}
let out = replaceCase("This is my test string: AbcD", "abcd", "qwer")
console.log(out) // This is my test string: QweR
out = replaceCase("This is my test string: abCd", "abcd", "qwer")
console.log(out) // This is my test string: qwEr
You could create your own replace function such as
if(!String.prototype.myreplace){
String.prototype.myreplace = (function(obj){
return this.replace(/[a-z]{1,1}/gi,function(a,b){
var r = obj[a.toLowerCase()] || a;
return a.charCodeAt(0) > 96? r.toLowerCase() : r.toUpperCase();
});
});
}
This takes in a object that maps different letters. and it can be called such as follows
var obj = {a:'q',b:'t',c:'w'};
var s = 'AbCdea';
var n = s.myreplace(obj);
console.log(n);
This means you could potentially pass different objects in with different mappings if need be. Here's a simple fiddle showing an example (note the object is all lowercase but the function itself looks at case of the string as well)
Expanding on Ryan O'Hara's answer, the below solution avoids using charCodes and the issues that maybe encountered in using them. It also ensures the replacement is complete when the strings are of different lengths.
function enforceLength(text, pattern, result) {
if (text.length > result.length) {
result = result.concat(text.substring(result.length, text.length));
}
if (pattern.length > text.length) {
result = result.substring(0, text.length);
}
return result;
}
function matchCase(text, pattern){
var result = '';
for (var i =0; i < pattern.length; i++){
var c = text.charAt(i);
var p = pattern.charAt(i);
if(p === p.toUpperCase()) {
result += c.toUpperCase();
} else {
result += c.toLowerCase();
}
}
return enforceLength(text, pattern, result);
}
This should replace while preserving the case. Please let me know if anyone finds any flaws in this solution. I hope this helps. Thank-you!
function myReplace(str, before, after) {
var match=function(before,after){
after=after.split('');
for(var i=0;i<before.length;i++)
{
if(before.charAt(i)==before[i].toUpperCase())
{
after[i]=after[i].toUpperCase();
}
else if(before.charAt(i)==before[i].toLowerCase())
{
after[i]=after[i].toLowerCase();
}
return after.join('');
}
};
console.log(before,match(before,after));
str =str.replace(before,match(before,after));
return str;
}
myReplace("A quick brown fox jumped over the lazy dog", "jumped", "leaped");
I had a sentence where I had to replace each word with another word and that word can be longer/shorter than the word its replacing so its similar to the question but instead of a fixed length, they're dynamic.
My solution
For simplicity, I am focusing on a single word.
const oldWord = "tEsT";
const newWord = "testing";
Split both words so that I can iterate over each individual letters.
const oldWordLetters = oldWord.split("");
const newWordLetters = newWord.split("");
Now, I would iterate over the newWord letters and use its index to then get the corresponding oldWord letter in the same position. Then I would check if the old letter is capital and if it is then make the new letter in the same position capital as well.
for (const [i, letter] of newWordLetters.entries()) {
const oldLetter = oldWordLetters[i];
// stop iterating if oldWord is shorter (not enough letters to copy case).
if (!oldLetter) {
break;
}
const isCapital = oldLetter === oldLetter.toUpperCase();
// make the new letter in the same position as the old letter capital
if (isCapital) {
newWordLetters[i] = letter.toUpperCase();
}
}
The final world would be tEsTing after joining the letters again.
const finalWord = newWordLetters.join("");
console.log(finalWord); // "tEsTing"
Full code
const oldWord = "tEsT";
const newWord = "testing";
const oldWordLetters = oldWord.split("");
const newWordLetters = newWord.split("");
for (const [i, letter] of newWordLetters.entries()) {
const oldLetter = oldWordLetters[i];
// stop iterating if oldWord is shorter (not enough letters to copy case).
if (!oldLetter) {
break;
}
const isCapital = oldLetter === oldLetter.toUpperCase();
// make the new letter in the same position as the old letter capital
if (isCapital) {
newWordLetters[i] = letter.toUpperCase();
}
}
const finalWord = newWordLetters.join("");
console.log(finalWord);
I think this could work
function formatItem(text, searchText){
const search = new RegExp(escapeRegExp(searchText), 'iu')
return text?.toString().replace(search, (m) => `<b>${m}</b>`)
}
function escapeRegExp(text) {
return text?.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&') ?? '';
}
Thank you for asking this. I had the same problem when I wanted to search text and replace certain words with links, which was a slightly more specific situation because it is replacing text strings with html strings. I'll put my solution here in case anyone who finds this is doing anything similar.
let elmt = document.getElementById('the-element');
let a = document.createElement('a');
a.href = "https://www.example.com";
let re = new RegExp('the string to find', 'gi');
elmt.innerHTML = elmt.innerHTML.replaceAll(re, function (match) {
a.innerText = match;
return a.outerHTML;
});
So the regular expression ensures that it searches for case-insensitive matches, and the function as the second argument of the replaceAll function specifies that it is supposed to set the innerText of the new tag equal to the old string verbatim, before then returning the outerHTML of the whole tag.
Here is a replaceAllCaseSensitive function. If your want, you can change replaceAll by replace.
const replaceAllCaseSensitive = (
text, // Original string
pattern, // RegExp with the pattern you want match. It must include the g (global) and i (case-insensitive) flags.
replacement // string with the replacement
) => {
return text.replaceAll(pattern, (match) => {
return replacement
.split("")
.map((char, i) =>
match[i] === match[i].toUpperCase() ? char.toUpperCase() : char
)
.join("");
});
};
console.log(replaceAllCaseSensitive("AbcD abCd", /abcd/gi, "qwer"));
// outputs "QweR qwEr"
console.log(replaceAllCaseSensitive("AbcD abCd", /abcd/gi, "qwe"));
// outputs "Qwe qwE"
The function works even if replacement is shorter than match.