Filtering ranges of letters in Javascript - javascript

I have to create a program that takes the first letter of a prompt, and if that letter is between a and k, then it has to produce a certain output, if it's between l and p another and so on. Is there a way to do this without writing every letter of the alphabet down? (sorry I'm a new coder)

I think you should try to solve the problem, before asking - so you can show what you've already tried.
I think the snippet below points you in the right direction - but it takes any character, not just letters. You need to get everything filtered out that's not a lower case letter.
// UI elements
const input = document.getElementById('input1')
const result = document.getElementById('result')
// input event
// only the first character is taken into account
input.addEventListener('input', function(e) {
// adding the characters of the input value to an array, and
// picking the 0th element (or '', if there's no 0th element)
const a = [...this.value][0] || ''
let ret = ''
if (a !== '') {
// lowercaseing letters, so it's easier to categorize them
ret = categorizeAlphabet(a.toLowerCase().charCodeAt(0))
} else {
ret = 'The input is empty'
}
// displaying the result
result.textContent = ret
})
// you could use this function to filter and categorize
// according to the problem ahead of you - and return the result
// to be displayed.
// In this example this function is rather simple, but
// you can build a more complex return value.
const categorizeAlphabet = (chCode) => {
return `This is the character code: ${chCode}`
}
<label>
First character counts:
<input type="text" id='input1'>
</label>
<h3 id="result">The input is empty</h3>

Related

.toLowerCase() / .toUpperCase() not working

I'm trying to get better at javascript through codewars.com katas, and I came across an exercice in which things like element[i]=element[i].toLowerCase() doesn't change anything at all.
I would like to have some help with my code, here is the exercice's instructions followed by my code:
(Please note that I'm not very experienced with JS so the code may not be perfect at all)
A string is considered to be in title case if each word in the string
is either:
(a) capitalised (that is, only the first letter of the word
is in upper case) or
(b) considered to be an exception and put entirely into lower case unless it is the first word, which is always capitalised.
Write a function that will convert a string into title case, given an optional list of exceptions (minor words). The list of minor words will be given as a string with each word separated by a space.
Your function should ignore the case of the minor words string -- it should behave in the same way even if the case of the minor word string is changed.
Arguments:
First argument (required): the original string to be converted.
Second argument (optional): space-delimited list of minor words that must always be lowercase except for the first word in the string. The JavaScript/CoffeeScript tests will pass undefined when this argument is unused.
function titleCase(title, minorWords) {
if(title.length==0){return ""}
var titlesplit = title.split(" ")
if(minorWords){
minorWords=minorWords.split(" ")
}
var solutionstring = ""
titlesplit.forEach(element => myfunction(element,minorWords))
solutionstring[0] = solutionstring[0].toUpperCase()
return solutionstring
function myfunction(element,minorWords){
var elementlength= element.length
var i=0
if(minorWords && minorWords.includes(element)){
for(i;i<elementlength;i++){
element[i]=element[i].toLowerCase()
}
}else {
for(i;i<elementlength;i++){
if(i==0){element[i]=element[i].toUpperCase()}
else{element[i]=element[i].toLowerCase()}
}
}
if(solutionstring.length==0){solutionstring=solutionstring+element}else{solutionstring=solutionstring+" "+element}
return
}
}
As pointed out in comments, Strings are immutable in JavaScript.
Additionally, for searching use Maps instead of includes.
Likewise you can see what Set in JavaScript is and easily use Set here.
Added comments for you better understanding.
function titleCase(title, minorWords) {
// Use === for comparison
// Prefer using curly braces even for single statements
if (title.length === 0) {
return "";
}
var titlesplit = title.split(" ");
// Maps/Objects give O(1) search compared to arrays O(n)
// Key,value pairs - similar to dictionary
var minorWordsMap = {};
minorWords.split(" ").forEach(i => minorWordsMap[i.toLowerCase()] = true);
var finalWords = titlesplit.map((element, index) => convertCase(element, index));
finalWords[0] = toPascalCase(finalWords[0]);
return finalWords.join(" ");
function toPascalCase(s) {
s = s.split("");
s[0] = s[0].toUpperCase();
return s.join("");
}
function convertCase(element, index) {
const lElement = element.toLowerCase();
// If element is part of exception words, ignore
if(index !== 0 && minorWordsMap[lElement]) {
return element;
}
// If first element or not in exception list, send Title case
return toPascalCase(lElement);
}
}

Get initials and full last name from a string containing names

Assume there are some strings containing names in different format (each line is a possible user input):
'Guilcher, G.M., Harvey, M. & Hand, J.P.'
'Ri Liesner, Peter Tom Collins, Michael Richards'
'Manco-Johnson M, Santagostino E, Ljung R.'
I need to transform those names to get the format Lastname ABC. So each surename should be transformed to its initial which are appended to the lastname.
The example should result in
Guilcher GM, Harvey M, Hand JP
Liesner R, Collins PT, Richards M
Manco-Johnson M, Santagostino E, Ljung R
The problem is the different (possible) input format. I think my attempts are not very smart, so I'm asking for
Some hints to optimize the transformation code
How do I put those in a single function at all? I think first of all I have to test which format the string has...??
So let me explain how far I tried to solve that:
First example string
In the first example there are initials followed by a dot. The dots should be removed and the comma between the name and the initals should be removed.
firstString
.replace('.', '')
.replace(' &', ', ')
I think I do need an regex to get the comma after the name and before the initials.
Second example string
In the second example the name should be splitted by space and the last element is handled as lastname:
const elm = secondString.split(/\s+/)
const lastname = elm[elm.length - 1]
const initials = elm.map((n,i) => {
if (i !== elm.length - 1) return capitalizeFirstLetter(n)
})
return lastname + ' ' + initals.join('')
...not very elegant
Third example string
The third example has the already the correct format - only the dot at the end has to be removed. So nothing else has to be done with that input.
It wouldn't be possible without calling multiple replace() methods. The steps in provided solution is as following:
Remove all dots in abbreviated names
Substitute lastname with firstname
Replace lastnames with their beginning letter
Remove unwanted characters
Demo:
var s = `Guilcher, G.M., Harvey, M. & Hand, J.P.
Ri Liesner, Peter Tom Collins, Michael Richards
Manco-Johnson M, Santagostino E, Ljung R.`
// Remove all dots in abbreviated names
var b = s.replace(/\b([A-Z])\./g, '$1')
// Substitute first names and lastnames
.replace(/([A-Z][\w-]+(?: +[A-Z][\w-]+)*) +([A-Z][\w-]+)\b/g, ($0, $1, $2) => {
// Replace full lastnames with their first letter
return $2 + " " + $1.replace(/\b([A-Z])\w+ */g, '$1');
})
// Remove unwanted preceding / following commas and ampersands
.replace(/(,) +([A-Z]+)\b *[,&]?/g, ' $2$1');
console.log(b);
Given your example data i would try to make guesses based on name part count = 2, since it is very hard to rely on any ,, & or \n - which means treat them all as ,.
Try this against your data and let me know of any use-cases where this fails because i am highly confident that this script will fail at some point with more data :)
let testString = "Guilcher, G.M., Harvey, M. & Hand, J.P.\nRi Liesner, Peter Tom Collins, Michael Richards\nManco-Johnson M, Santagostino E, Ljung R.";
const inputToArray = i => i
.replace(/\./g, "")
.replace(/[\n&]/g, ",")
.replace(/ ?, ?/g, ",")
.split(',');
const reducer = function(accumulator, value, index, array) {
let pos = accumulator.length - 1;
let names = value.split(' ');
if(names.length > 1) {
accumulator.push(names);
} else {
if(accumulator[pos].length > 1) accumulator[++pos] = [];
accumulator[pos].push(value);
}
return accumulator.filter(n => n.length > 0);
};
console.log(inputToArray(testString).reduce(reducer, [[]]));
Here's my approach. I tried to keep it short but complexity was surprisingly high to get the edge cases.
First I'm formatting the input, to replace & for ,, and removing ..
Then, I'm splitting the input by \n, then , and finally (spaces).
Next I'm processing the chunks. On each new segment (delimited by ,), I process the previous segment. I do this because I need to be sure that the current segment isn't an initial. If that's the case, I do my best to skip that inital-only segment and process the previous one. The previous one will have the correct initial and surname, as I have all the information I neeed.
I get the initial on the segment if there's one. This will be used on the start of the next segment to process the current one.
After finishing each line, I process again the last segment, as it wont be called otherwise.
I understand the complexity is high without using regexp, and probably would have been better to use a state machine to parse the input instead.
const isInitial = s => [...s].every(c => c === c.toUpperCase());
const generateInitial = arr => arr.reduce((a, c, i) => a + (i < arr.length - 1 ? c[0].toUpperCase() : ''), '');
const formatSegment = (words, initial) => {
if (!initial) {
initial = generateInitial(words);
}
const surname = words[words.length - 1];
return {initial, surname};
}
const doDisplay = x => x.map(x => x.surname + ' ' + x.initial).join(', ');
const doProcess = _ => {
const formatted = input.value.replace(/\./g, '').replace(/&/g, ',');
const chunks = formatted.split('\n').map(x => x.split(',').map(x => x.trim().split(' ')));
const peoples = [];
chunks.forEach(line => {
let lastSegment = null;
let lastInitial = null;
let lastInitialOnly = false;
line.forEach(segment => {
if (lastSegment) {
// if segment only contains an initial, it's the initial corresponding
// to the previous segment
const initialOnly = segment.length === 1 && isInitial(segment[0]);
if (initialOnly) {
lastInitial = segment[0];
}
// avoid processing last segments that were only initials
// this prevents adding a segment twice
if (!lastInitialOnly) {
// if segment isn't an initial, we need to generate an initial
// for the previous segment, if it doesn't already have one
const people = formatSegment(lastSegment, lastInitial);
peoples.push(people);
}
lastInitialOnly = initialOnly;
// Skip initial only segments
if (initialOnly) {
return;
}
}
lastInitial = null;
// Remove the initial from the words
// to avoid getting the initial calculated for the initial
segment = segment.filter(word => {
if (isInitial(word)) {
lastInitial = word;
return false;
}
return true;
});
lastSegment = segment;
});
// Process last segment
if (!lastInitialOnly) {
const people = formatSegment(lastSegment, lastInitial);
peoples.push(people);
}
});
return peoples;
}
process.addEventListener('click', _ => {
const peoples = doProcess();
const display = doDisplay(peoples);
output.value = display;
});
.row {
display: flex;
}
.row > * {
flex: 1 0;
}
<div class="row">
<h3>Input</h3>
<h3>Output</h3>
</div>
<div class="row">
<textarea id="input" rows="10">Guilcher, G.M., Harvey, M. & Hand, J.P.
Ri Liesner, Peter Tom Collins, Michael Richards
Manco-Johnson M, Santagostino E, Ljung R.
Jordan M, Michael Jackson & Willis B.</textarea>
<textarea id="output" rows="10"></textarea>
</div>
<button id="process" style="display: block;">Process</button>

Function behaves strangely after first onclick

I have the following HTML:
<input type = "text" id = "pick"> <input type = "submit" value = "Submit" onclick = "guessWord()">
That runs my js function which works fine (with unrelated hiccups) on the first call. But if I change my text and submit it again without reloading my initial if/else statement behaves incorrectly. Specifically, the if/else is supposed to check if the user inputted word is in an array. It works properly on the first call, but after that it jumps to the else block even when it shouldn't.
Here is the js (apologies in advance for including the whole function, I'm just usually asked to include more code than I initially do):
function guessWord() {
var comWords, match, compWord = "";
var possWords = dictFive;
var firstFive = ["vibex", "fjord", "nymph", "waltz", "gucks"]; // note: right now choosing any of these words results in unexpected behavior -- either it doesn't accept them or it freezes.
var inputWord = document.getElementById("pick").value.toLowerCase().replace(/\s+/g, '');
if (possWords.includes(inputWord)) { // checks to see if the user inputted word is in our dictionary.i f not, requests a different word.
// start game loop:
// in order to try and get as much information as possible in the first few turns I start by guessing the five words in firstFive[]: vibex, fjord, nymph, waltz, gucks. together, these words give us information about 25 letters.
for (let d = 0; d < inputWord.length; d++) { // this loop will run for the length of the inputted word, making it scaleable so in the future the program could accept shorter or longer words. within the current scope it will always be 5.
compWord = firstFive[d]; // the computers word will loop through each word in firstFive[].
if (inputWord === compWord) { // if the word matches the user inputted word:
document.getElementById("otpt").innerHTML = "Your word was: " + firstFive[d] + ". I guessed it in " + (d + 1) + " turns.";
return;
} else { // if the word is not the user inputted word, then:
comWords = (inputWord + compWord).split('').sort().join(''); // we combine the users word with the comps word and sort them by character.
match = comWords.length - comWords.replace(/(\w)\1+/g, '$1').length; // match produces a numerical value for how many letters matched between both words.
for (let e = 0; e < possWords.length; e++) { // loop to cycle through our dictionary.
for (let f = 0; f < inputWord.length; f++) { // loop to cycle through all the different match options.
if (match === 0) { // if there are no matches we can:
if (possWords[e].includes(firstFive[f])) { // go through the dict and get rid of every word that has letters in common with the word.
possWords.splice(e, 1);
}
} else if (match === f) { // if there's at least one letter in common:
comWords = (possWords[e] + compWord).split('').sort().join(''); // as we cycle through the dict, pick each available word, combine and sort with the chosen word,
var matchFive = comWords.length - comWords.replace(/(\w)\1+/g, '$1').length; // and then find how many letters match.
if (matchFive != match) { // any words in dict that have a different match value can be deleted.
possWords.splice(e, 1);
}
}
}
}
}
}
// once we've worked through the words in firstFive[] we start guessing randomly.
for (let a = 0; a < possWords.length; a++) { // the loop max is set to the length of the array because that's the maximum amount of time the guessing can take.
compWord = possWords[Math.floor(Math.random() * possWords.length)]; // choose a random word.
if (compWord === inputWord) { // check if the random word is the inputted word. if it is:
document.getElementById("otpt").innerHTML = "Your word was: " + compWord + ". I guessed it in " + (a + 5) + " turns. I had " + possWords.length + " remaining words that were possible matches.";
return;
} else { // while the word still isn't correct:
comWords = (compWord + inputWord).split('').sort().join(''); // again, we join and sort it.
match = comWords.length - comWords.replace(/(\w)\1+/g, '$1'); // find its match value.
for (let c = 0; c < inputWord.length; c++) { // loop through inputted word's length to check all letters.
if (match === 0) { // again, no matches we can safely delete all words with those letters.
if (possWords.includes(compWord[c])) {
possWords.splice(c, 1);
}
} else if (match === c) { // if match is higher than 0:
for (let g = 0; g < possWords.length; g++) {
comWords = (possWords[g]+ compWord).split('').sort().join('');
matchAll = comWords.length - comWords.replace(/(\w)\1+/g, '$1');
if (match != matchAll) {
possWords.splice(g, 1);
}
}
}
}
}
}
} else { // If the user inputted word was not in our dictionary, requests a different word:
document.getElementById("otpt").innerHTML = "Please choose a different word.";
}
}
(For context, dictFive is an array located on a separate file.) The code is trying to guess the user inputted word by checking how many letters match and then splicing out words from the master array if they can't match, so the array possWords starts with about 2500 words and gets narrowed down to a few hundred by the end of the function. As far as I can tell, the function should be resetting the vars properly every time it's called, though, but I'm guessing it isn't for some reason?
Your dictFive array is being spliced each time the function is called.
When you set possWords = dictFive, and then splice possWords later, you're also splicing dictFive because both variables refer to the same array. Then, the second time the function is run, dictFive is still in its spliced state. Instead of setting possWords = dictFive, try making a copy of the array. That way, you'll splice the copy without affecting the original, dictFive. You can clone an array by possWords = dictFive.slice().
var dictFive = [0,1,2,3,4]; // Just an example of whatever dictFive might be
var possWords = dictFive; // This makes possWords refer to the same thing as dictFive
possWords.splice(0, 1); // Splicing the array at whatever point
possWords // [1,2,3,4] because the 0th element was spliced out
dictFive // also [1,2,3,4] because both dictFive and possWords are the same array
compare that to
var dictFive = [0,1,2,3,4];
var possWords = dictFive.slice(); // This makes a copy of the array instead of referencing the original dictFive
possWords.splice(0, 1);
possWords // [1,2,3,4];
dictFive // Now, this array is still [0,1,2,3,4] because only the possWords array was spliced. dictFive wasn't affected.

JavaScript regex: capturing optional groups while having a strict matching

I am trying to extract some data from user input that should follow this format: 1d 5h 30m, which means the user is entering an amount of time of 1 day, 5 hours and 30 minutes.
I am trying to extract the value of each part of the input. However, each group is optional, meaning that 2h 20m is a valid input.
I am trying to be flexible in the input (in the sense that not all parts need to be input) but at the same time I don't watch my regex to match some random imput like asdfasdf20m. This one should be rejected (no match).
So first I am getting rid of any separator the user might have used (their input can look like 4h, 10m and that's ok):
input = input.replace(/[\s.,;_|#-]+/g, '');
Then I am capturing each part, which I indicate as optional using ?:
var match = /^((\d+)d)?((\d+)h)?((\d+)m)?$/.exec(input);
It is kind of messy capturing an entire group including the letter when I only want the actual value, but I cannot say that cluster is optional without wrapping it with parentheses right?
Then, when an empty group is captured its value in match is undefined. Is there any function to default undefined values to a particular value? For example, 0 would be handy here.
An example where input is "4d, 20h, 55m", and the match result is:
["4d20h55m", "4d", "4", "20h", "20", "55m", "55", index: 0, input: "4d20h55m"]
My main issues are:
How can I indicate a group as optional but avoid capturing it?
How can I deal with input that can potentially match, like abcdefg6d8m?
How can I deal with an altered order? For example, the user could input 20m 10h.
When I'm asking "how to deal with x" I mean I'd like to be able to reject those matches.
As variant:
HTML:
<input type="text">
<button>Check</button>
<div id="res"></div>
JS:
var r = [];
document.querySelector('button').addEventListener('click', function(){
var v = document.querySelector('input').value;
v.replace(/(\d+d)|(\d+h)|(\d+m)/ig, replacer);
document.querySelector('#res').innerText = r;
}, false);
function trim(s, mask) {
while (~mask.indexOf(s[0])) {
s = s.slice(1);
}
while (~mask.indexOf(s[s.length - 1])) {
s = s.slice(0, -1);
}
return s;
}
function replacer(str){
if(/d$/gi.test(str)){
r[0] = str;
}
else if(/h$/gi.test(str)){
r[1] = str;
}
else if(/m$/gi.test(str)){
r[2] = str;
}
return trim(r.join(', '), ',');
}
See here.

jQuery check is letter is available in the array

I still have a problem with jQuery code.
I want to check characters from strings which is in array while user typing something in the input. If any of first one characters is available in the array i want to display "VALID".
var postcodes = ["00-240","80","32","90","91", "8", "11"];
$('input[name="text-391"]').keyup(function(){
var val = this.value;
var m = $.map(postcodes,function(value,index){
var reg = new RegExp('^'+val+'.*$')
return value.match(reg);
});
if(m.length && val.length) {
$('#error').hide(300);
} else {
$('#error').show(300);
}
});
This code checks that all what user type in the input is in array, but i want to check this letter after letter.
For example:
user types: 0 - it's ok
user types: 00 - it's still ok
user types 00-340 - it's not ok and now I want to display warning that we haven't it in the array
user types: 3 - it's ok
user types: 35 - it's not ok, and now i want to display warning that we haven't it in the array
user types 80-125 - it's still ok [important]
user types 11-1 - it's still ok [important]
I will be very grateful for any tips. Regards
you need to add below code in $.map
if(val.length>2 && val.indexOf("-")>-1 && !(value.indexOf("-")>-1))
val= val.substring(0,val.indexOf("-"))
Here is the working DEMO
Explanation:
You just want to check if enter value length is more than two and it contains - and value in map should not contain -(you need last and condition for letter like "xx-xxx"
Thanks
You'll have to check it both ways:
var m = $.map(postcodes,function(value,index){
var reg = new RegExp('^'+val+'.*$')
var result=value.match(reg);
if (result.length) {
return result;
} else {
reg = new RegExp('^'+value+'.*$')
return val.match(reg);
}
});
You can optimize further if you first create an array of regex's based on postcodes and then reference them by index in the callback function.

Categories

Resources