Shorter more efficient way to tackle this codewars challenge? - javascript

I'm fairly new to JavaScript (and development in general). I wanted to try a challenge from Codewars. The challenge was to process a string through a function that would flip any words that were over 5 characters and return the original string with those flipped words. Here's the code I came up with (It did work!).
//this function turns each word into an array that will get flipped.
let wordFlipper = (word) => {
var splitWord = word.split(''); //convert word to array
var reversedWord = splitWord.reverse(); //flips the indexes for the array
var joinReversedWord = reversedWord.join('').toString(); //turns array back to a string.
return joinReversedWord;
}
function spinWords(phrase){
let finalArray = [];
let wordsToArray = phrase.split(' ');
const processFlipWords = wordsToArray.forEach(word => {
if (word.toString().length > 4) {
var flippedWord = wordFlipper(word); //here's where we call the function wordFlipper()
finalArray.push(flippedWord);
}
else {
finalArray.push(word);
}
});
return finalArray.join(' ');
}
How would you experts suggest writing this? I'm sure I'm not being too efficient at writing this code.
Thank you!
Here's what it looks like inside codewars!

I'd use a regular expression to match 5 or more word characters (\w{5,}), and have a replacer function (String.replace()) return the reversed (reverse()) word:
const spinWords = phrase => phrase.replace(
/\w{5,}/g,
word => [...word].reverse().join('')
);
console.log(spinWords('foo barbar more words go here'));
\w matches a word character - something from A to Z, case-insensitive, or a digit, or an underscore.
The brackets indicates the number of times to repeat the previous token. {5,} starts with a 5 (so, "at least 5") and has nothing after the comma ("up to any number").
Then /g, the global flag, matches and replaces every substring that matches this pattern, not just the first.
The callback function runs for every matched substring, where the argument is the matched substring, and what is returned gets replaced at that point in the original string.

Related

is there a way for the content.replace to sort of split them into more words than these?

const filter = ["bad1", "bad2"];
client.on("message", message => {
var content = message.content;
var stringToCheck = content.replace(/\s+/g, '').toLowerCase();
for (var i = 0; i < filter.length; i++) {
if (content.includes(filter[i])){
message.delete();
break
}
}
});
So my code above is a discord bot that deletes the words when someone writes ''bad1'' ''bad2''
(some more filtered bad words that i'm gonna add) and luckily no errors whatsoever.
But right now the bot only deletes these words when written in small letters without spaces in-between or special characters.
I think i have found a solution but i can't seem to put it into my code, i mean i tried different ways but it either deleted lowercase words or didn't react at all and instead i got errors like ''cannot read property of undefined'' etc.
var badWords = [
'bannedWord1',
'bannedWord2',
'bannedWord3',
'bannedWord4'
];
bot.on('message', message => {
var words = message.content.toLowerCase().trim().match(/\w+|\s+|[^\s\w]+/g);
var containsBadWord = words.some(word => {
return badWords.includes(word);
});
This is what i am looking at. the var words line. specifically (/\w+|\s+|[^\s\w]+/g);.
Anyway to implement that into my const filter code (top/above) or a different approach?
Thanks in advance.
Well, I'm not sure what you're trying to do with .match(/\w+|\s+|[^\s\w]+/g). That's some unnecessary regex just to get an array of words and spaces. And it won't even work if someone were to split their bad word into something like "t h i s".
If you want your filter to be case insensitive and account for spaces/special characters, a better solution would probably require more than one regex, and separate checks for the split letters and the normal bad word check. And you need to make sure your split letters check is accurate, otherwise something like "wash it" might be considered a bad word despite the space between the words.
A Solution
So here's a possible solution. Note that it is just a solution, and is far from the only solution. I'm just going to use hard-coded string examples instead of message.content, to allow this to be in a working snippet:
//Our array of bad words
var badWords = [
'bannedWord1',
'bannedWord2',
'bannedWord3',
'bannedWord4'
];
//A function that tests if a given string contains a bad word
function testProfanity(string) {
//Removes all non-letter, non-digit, and non-space chars
var normalString = string.replace(/[^a-zA-Z0-9 ]/g, "");
//Replaces all non-letter, non-digit chars with spaces
var spacerString = string.replace(/[^a-zA-Z0-9]/g, " ");
//Checks if a condition is true for at least one element in badWords
return badWords.some(swear => {
//Removes any non-letter, non-digit chars from the bad word (for normal)
var filtered = swear.replace(/\W/g, "");
//Splits the bad word into a 's p a c e d' word (for spaced)
var spaced = filtered.split("").join(" ");
//Two different regexes for normal and spaced bad word checks
var checks = {
spaced: new RegExp(`\\b${spaced}\\b`, "gi"),
normal: new RegExp(`\\b${filtered}\\b`, "gi")
};
//If the normal or spaced checks are true in the string, return true
//so that '.some()' will return true for satisfying the condition
return spacerString.match(checks.spaced) || normalString.match(checks.normal);
});
}
var result;
//Includes one banned word; expected result: true
var test1 = "I am a bannedWord1";
result = testProfanity(test1);
console.log(result);
//Includes one banned word; expected result: true
var test2 = "I am a b a N_N e d w o r d 2";
result = testProfanity(test2);
console.log(result);
//Includes one banned word; expected result: true
var test3 = "A bann_eD%word4, I am";
result = testProfanity(test3);
console.log(result);
//Includes no banned words; expected result: false
var test4 = "No banned words here";
result = testProfanity(test4);
console.log(result);
//This is a tricky one. 'bannedWord2' is technically present in this string,
//but is 'bannedWord22' really the same? This prevents something like
//"wash it" from being labeled a bad word; expected result: false
var test5 = "Banned word 22 isn't technically on the list of bad words...";
result = testProfanity(test5);
console.log(result);
I've commented each line thoroughly, such that you understand what I am doing in each line. And here it is again, without the comments or testing parts:
var badWords = [
'bannedWord1',
'bannedWord2',
'bannedWord3',
'bannedWord4'
];
function testProfanity(string) {
var normalString = string.replace(/[^a-zA-Z0-9 ]/g, "");
var spacerString = string.replace(/[^a-zA-Z0-9]/g, " ");
return badWords.some(swear => {
var filtered = swear.replace(/\W/g, "");
var spaced = filtered.split("").join(" ");
var checks = {
spaced: new RegExp(`\\b${spaced}\\b`, "gi"),
normal: new RegExp(`\\b${filtered}\\b`, "gi")
};
return spacerString.match(checks.spaced) || normalString.match(checks.normal);
});
}
Explanation
As you can see, this filter is able to deal with all sorts of punctuation, capitalization, and even single spaces/symbols in between the letters of a bad word. However, note that in order to avoid the "wash it" scenario I described (potentially resulting in the unintentional deletion of a clean message), I made it so that something like "bannedWord22" would not be treated the same as "bannedWord2". If you want it to do the opposite (therefore treating "bannedWord22" the same as "bannedWord2"), you must remove both of the \\b phrases in the normal check's regex.
I will also explain the regex, such that you fully understand what is going on here:
[^a-zA-Z0-9 ] means "select any character not in the ranges of a-z, A-Z, 0-9, or space" (meaning all characters not in those specified ranges will be replaced with an empty string, essentially removing them from the string).
\W means "select any character that is not a word character", where "word character" refers to the characters in ranges a-z, A-Z, 0-9, and underscore.
\b means "word boundary", essentially indicating when a word starts or stops. This includes spaces, the beginning of a line, and the end of a line. \b is escaped with an additional \ (to become \\b) in order to prevent javascript from confusing the regex token with strings' escape sequences.
The flags g and i used in both of the regex checks indicate "global" and "case-insensitive", respectively.
Of course, to get this working with your discord bot, all you have to do in your message handler is something like this (and be sure to replace badWords with your filter variable in testProfanity()):
if (testProfanity(message.content)) return message.delete();
If you want to learn more about regex, or if you want to mess around with it and/or test it out, this is a great resource for doing so.

How do I replace the last letter of a string element in an array with replace()

I've just started coding..I'm a super beginner and have no idea about regex yet so for now I'd rather not use it. This is an exercise I'm trying to solve. The problem is that when a word contains matching characters, the first character gets the lower case, but what I actually want is the last character of the word to become small.
I don't really require a solution for the problem. Instead I'd rather have some insight on what I'm doing wrong and maybe direct me to the right path :)
function alienLanguage(str) {
let bigWords = str.toUpperCase().split(" ");
let lastLetterSmall = [];
bigWords.forEach(words => {
lastLetterSmall
.push(words
.replace(words
.charAt(words.length -1), words.charAt(words.length -1).toLowerCase()));
});
console.log(lastLetterSmall.join(' '));
}
alienLanguage("My name is John");
alienLanguage("this is an example");
alienLanguage("Hello World");
alienLanguage("HELLO WORLD");
Since you only really want to work with indicies of the string - you don't need to replace anything dynamically other than the last index - replace won't work well, since if you pass it a string, it will only replace the first matching letter. For example:
'foo'.replace('o', 'x')
results in 'fxo', because the first o (and only the first o) gets replaced.
For your code, instead of replace, just concatenate the two parts of the string together: the part from index 0 to next-to-last index, and the character at the last index with toLowerCase() called on it:
function alienLanguage(str) {
const result = str
.toUpperCase()
.split(" ")
.map(line => line.slice(0, line.length - 1) + line[line.length - 1].toLowerCase())
.join(' ');
console.log(result);
}
alienLanguage("My name is John");
alienLanguage("this is an example");
alienLanguage("Hello World");
alienLanguage("HELLO WORLD");

I need help getting the first n characters of a string up to when a number character starts

I'm working with a string where I need to extract the first n characters up to where numbers begin. What would be the best way to do this as sometimes the string starts with a number: 7EUSA8889er898 I would need to extract 7EUSA But other string examples would be SWFX74849948, I would need to extract SWFX from that string.
Not sure how to do this with regex my limited knowledge is blocking me at this point:
^(\w{4}) that just gets me the first four characters but I don't really have a stopping point as sometimes the string could be somelongstring292894830982 which would require me to get somelongstring
Using \w will match a word character which includes characters and digits and an underscore.
You could match an optional digit [0-9]? from the start of the string ^and then match 1+ times A-Za-z
^[0-9]?[A-Za-z]+
Regex demo
const regex = /^[0-9]?[A-Za-z]+/;
[
"7EUSA8889er898",
"somelongstring292894830982",
"SWFX74849948"
].forEach(s => console.log(s.match(regex)[0]));
Can use this regex code:
(^\d+?[a-zA-Z]+)|(^\d+|[a-zA-Z]+)
I try with exmaple and good worked:
1- somelongstring292894830982 -> somelongstring
2- 7sdfsdf5456 -> 7sdfsdf
3- 875werwer54556 -> 875werwer
If you want to create function where the RegExp is parametrized by n parameter, this would be
function getStr(str,n) {
var pattern = "\\d?\\w{0,"+n+"}";
var reg = new RegExp(pattern);
var result = reg.exec(str);
if(result[0]) return result[0].substr(0,n);
}
There are answers to this but here is another way to do it.
var string1 = '7EUSA8889er898';
var string2 = 'SWFX74849948';
var Extract = function (args) {
var C = args.split(''); // Split string in array
var NI = []; // Store indexes of all numbers
// Loop through list -> if char is a number add its index
C.map(function (I) { return /^\d+$/.test(I) === true ? NI.push(C.indexOf(I)) : ''; });
// Get the items between the first and second occurence of a number
return C.slice(NI[0] === 0 ? NI[0] + 1 : 0, NI[1]).join('');
};
console.log(Extract(string1));
console.log(Extract(string2));
Output
EUSA
SWFX7
Since it's hard to tell what you are trying to match, I'd go with a general regex
^\d?\D+(?=\d)

Word counter in javascript

I'm working on a lab assignment for a web applications class and am stuck on implementing a word counter for a basic HTML webpage. The setup of the tests and HTML are already done for us. I simply need to write a function called countWords that takes a string and returns the number of words. It works differently from your traditional word counter though. A word is defined as anything A-Z. Everything else is considered not part of a word. So, if the string is just "234##$^" then the word count is 0. So, I'm not just counting white space like most word counters. All the answers I've found on StackOverflow to similar questions try to just count white space and don't work for my situation. Hence why I made a new question.
My idea was to have a return statement that matches any grouping of a-z using a regular expression and return the length. Then, have a conditional to check for the empty string or string with no letters a-z.
function countWords(s) {
if(s === "" || s === "%$#^23#") {
return 0
}
return s.match(/[^a-z]/gi).length
}
Right now the if statement is just matching the two test cases so that I can pass my tests. I'm not sure how to go about writing another match regular expression to check for no letters in the string or the empty string. Any help is appreciated! Been stuck for a while.
const str1 = '%$#^23#';
const str2 = 'String with ___ special characters and #$&# white spaces !!!';
const str3 = 'Special &$%# characters --> and %$#^5# connected,words but our <++##||++> function,still_works!';
const wordCount = (str) => str.replace(/[\W_\d]/g,' ').split(' ').filter(Boolean).length;
console.log(wordCount(str1)); // 0
console.log(wordCount(str2)); // 7
console.log(wordCount(str3)); // 11
use "regex" to replace all special characters, underscores, numbers, and extra white spaces with an empty space
--> replace(/[\W_\d]/g,' ')
convert the string into an array
--> .split(' ')
use filter to remove all empty string(s) in the array
--> .filter(Boolean)
then, get the word count with "length"
--> .length
You first need to filter the string, remove all the special characters and numbers:
var filtered_test = my_text.replace(/[^a-zA-Z ]/g, '');
then do a normal split and count:
var words = filtered_test.split(" ");
console.log(words.length); //prints out the count of words
You can use a functional replace method to chunk all of the "words" into an array, then simply return the array length. This has the added benefit of providing a 0 count:
explanatory version:
function countWords(str, words = []) {
str.replace(/[A-Z]+/gi, (m) => words.push(m));
return words.length;
}
minimal version:
let countWords = (str, words = []) =>
( str.replace(/[A-Z]+/gi, (m) => words.push(m)), words.length );
let countWords = (str, words = []) => (str.replace(/[A-Z]+/gi, (m) => words.push(m)), words.length);
console.log( "##asdfadf###asfadf: " + countWords("##asdfadf###asfadf") )
console.log("##13424#$#$#$%: " + countWords("##13424#$#$#$%"));
How about this regular expression: /.*?[a-z]+.*?(\s|$)/gi
Use return s.match(/.*?[a-z]+.*?(\s|$)/gi).length
Anything with at least 1 letter in it is counted. Then the phrase O##ne two $#!+ ##%Three four^&&$ five would count as 5 words.
Edit: If you want to be evil to pass your test cases when there are 0 matches use (input.match(/.*?[a-z]+.*?(\s|$)/gi) || "").length

How to make first character uppercase of all words in JavaScript?

I have searched for solution but did not find yet.
I have the following string.
1. hello
2. HELLO
3. hello_world
4. HELLO_WORLD
5. Hello World
I want to convert them to following:
1. Hello
2. Hello
3. HelloWorld
4. HelloWorld
5. HelloWorld
If there is No space and underscore in string just uppercase first and all others to lowercase. If words are separated by underscore or space then Uppercase first letter of each word and remove space and underscore. How can I do this in JavaScript.
Thanks
Here is a regex solution:
First lowercase the string:
str = str.toLowerCase();
Replace all _ and spaces and first characters in a word with upper case character:
str = str.replace(/(?:_| |\b)(\w)/g, function(str, p1) { return p1.toUpperCase()})
DEMO
Update: Less steps ;)
Explanation:
/ // start of regex
(?: // starts a non capturing group
_| |\b // match underscore, space, or any other word boundary character
// (which in the end is only the beginning of the string ^)
) // end of group
( // start capturing group
\w // match word character
) // end of group
/g // and of regex and search the whole string
The value of the capturing group is available as p1 in the function, and the whole expression is replaced by the return value of the function.
You could do something like this:
function toPascalCase(str) {
var arr = str.split(/\s|_/);
for(var i=0,l=arr.length; i<l; i++) {
arr[i] = arr[i].substr(0,1).toUpperCase() +
(arr[i].length > 1 ? arr[i].substr(1).toLowerCase() : "");
}
return arr.join("");
}
You can test it out here, the approach is pretty simple, .split() the string into an array when finding either whitespace or an underscore. Then loop through the array, upper-casing the first letter, lower-casing the rest...then take that array of title-case words and .join() it together into one string again.
function foo(str) {
return $(str.split(/\s|_/)).map(function() {
return this.charAt(0).toUpperCase() + this.slice(1).toLowerCase();
}).get().join("");
}
Working demo: http://jsfiddle.net/KSJe3/3/
(I used Nicks regular expression in the demo)
Edit: Another version of the code - I replaced map() with $.map():
function foo(str) {
return $.map(str.split(/\s|_/), function(word) {
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
}).join("");
}
Working demo: http://jsfiddle.net/KSJe3/4/
An ES6 / functional update of #NickCraver's answer. As with #NickCraver's answer this function will handle multiple spaces / underscores properly by filtering them out.
const pascalWord = x => x[0].toUpperCase() + x.slice(1).toLowerCase();
const toPascalCase2 = (str) => (
str.split(/\s|_/)
.filter(x => x)
.map(pascalWord)
.join('')
);
const tests = [
'hello',
'HELLO',
'hello_world',
'HELLO_WORLD',
'Hello World',
'HELLO__WORLD__',
'Hello World_',
].map(toPascalCase2).join('<br>');
document.write(tests);
var city = city.replace(/\s+/g,' ') //replace all spaceses to singele speace
city = city.replace(/\b\w/g,city => city .toUpperCase()) //after speace letter convert capital

Categories