Turn array into comma separated grammatically correct sentence - javascript

If I have an array in Javascript that looks like
searchComponents = ['element1', 'element2', 'element3'];
What is the necessary logic to turn it into a sentence like:
"element1, element2, and element3"
Likewise, if there are only two elements it should read like:
"element1 and element2"
and so on and so forth. I am stuck.

One easy solution:
function arrayToSentence (arr) {
var last = arr.pop();
return arr.join(', ') + ' and ' + last;
}
console.log(arrayToSentence(['one','two','three']));
JS Fiddle demo.
And a slightly more complex/ridiculous solution (because who doesn't like silly, occasionally...):
function arrayToSentence (arr) {
var len = arr.length;
return arr.reduce(function(a,b,c){
return a + (c - 1 === length ? ', ' : ' and ') + b;
});
}
console.log(arrayToSentence(['one','two','three']));
JS Fiddle demo.
References:
Array.prototype.join().
Array.prototype.pop().
Array.prototype.reduce().

function toSentence(arr) {
return arr.slice(0, -2).join(', ') +
(arr.slice(0, -2).length ? ', ' : '') +
arr.slice(-2).join(' and ');
}
usage
toSentence([1])
1
toSentence([1, 2])
1 and 2
toSentence([1, 2, 3])
1, 2 and 3
toSentence([1, 2, 3, 4, 5, 6])
1, 2, 3, 4, 5 and 6

Here's a one liner:
const arrayToSentence = (a) => [a.slice(0, -1).join(', '), a.pop()].filter(w => w !== '').join(' and ');
console.log(arrayToSentence(['foo', 'bar', 'baz']));
console.log(arrayToSentence(['foo', 'bar']));
console.log(arrayToSentence(['foo']));
console.log(arrayToSentence([]));

Late answer, but this is my attempt at it. Relatively simple, immutable arguments, and works for 0-N items.
const toListSentence = (arr) => arr.length < 3 ?
arr.join(' and ') :
`${arr.slice(0, -1).join(', ')}, and ${arr[arr.length - 1]}`;
console.log(toListSentence([]));
console.log(toListSentence(['apple']));
console.log(toListSentence(['apple', 'banana']));
console.log(toListSentence(['apple', 'banana', 'peach']));

Try my compound-subject library:
https://github.com/adamshaylor/compound-subject
It includes controls over what character to delimit with (e.g. commas versus semicolons) as well as whether to delimit all the subjects (i.e. the “Oxford comma”).

More ES6/7 version without mutation:
function buildSentence (arr = []) {
if (arr.length === 0) {
return '';
}
if (arr.length === 1) {
return arr[0];
}
const newArr = [...arr];
const last = newArr.pop();
return newArr.join(', ') + ' and ' + last;
}
buildSentence([]);
buildSentence(['a']);
buildSentence(['a', 'b']);
buildSentence(['a', 'b', 'c']);

Here's a typed version that utilizes lodash functions:
Codesandbox: https://codesandbox.io/s/strange-architecture-z1854?file=/src/App.js
import { concat, first, join, last, slice } from 'lodash'
function joinOxford (list: string[]): string {
switch (list.length) {
case 1: return first(list)
case 2: return `${first(list)} and ${last(list)}`
default: return join(concat(slice(list, 0, -1), [`and ${last(list)}`]), ', ')
}
}
Usage:
joinOxford(['Alona'])
// Alona
joinOxford(['Alona', 'Loyce'])
// Alona and Loyce
joinOxford(['Alona', 'Loyce', 'Mikel'])
// Alona, Loyce, and Mikel
joinOxford(['Alona', 'Loyce', 'Mikel', 'Carrie'])
// Alona, Loyce, Mikel, and Carrie
joinOxford(['Alona', 'Loyce', 'Mikel', 'Carrie', 'Josie'])
// Alona, Loyce, Mikel, Carrie, and Josie

How's this...
function parseArray(arr) {
var s=arr.toString();
var c=s.lastIndexOf(",");
if(c!=-1) s=(s.substr(0,c)+" and "+s.substr(c+1)).replace(/,/g,", ");
return s[0].toUpperCase()+s.substr(1)+".";
}
console.log(parseArray(["one", "two","three","four","five"]));
console.log(parseArray(["one", "two"]));
console.log(parseArray(["one"]));
Output is:
One, two, three, four and five.
One and two.
One.
Grammatically correct?

Related

Refactoring JavaScript (Swtich & Function)

I'm a beginner at coding, and I'm studying JS. I'd like to know how to write a function inside a switch in this code below (if possible), turning the code smaller.
I tried to place the funtion for each operation inside of the switch, but it never worked.
Help me to improve my code. Thank you!
//Calculator of Basic Operations
function addition(a, b) {
return (a + b);
}
function subtraction(a, b) {
return (a - b);
}
function multiplication(a, b) {
return (a * b);
}
function division(a, b) {
return (a / b);
}
console.log('Choose the number for the operation you want to use.');
console.log('1 - Addition');
console.log('2 - Subtraction');
console.log('3 - Multiplication');
console.log('4 - Division');
let calcAB = prompt('Operation: ');
switch (calcAB) {
case '1':
a = Number(prompt('Enter the value for A: '));
b = Number(prompt('Enter the value for B: '));
console.log(`The addition result is "${addition(a, b)}"`);
break;
case '2':
a = Number(prompt('Enter the value for A: '));
b = Number(prompt('Enter the value for B: '));
console.log(`The subtraction result is "${subtraction(a, b)}"`);
break;
case '3':
a = Number(prompt('Enter the value for A: '));
b = Number(prompt('Enter the value for B: '));
console.log(`The multiplication result is "${multiplication(a, b)}"`);
break;
case '4':
a = Number(prompt('Enter the value for A (dividend): '));
b = Number(prompt('Enter the value for B (divisor): '));
console.log(`The division result is "${division(a, b)}"`);
break;
}
The only things that change between the cases are
The function called
The name of the operation called
The (dividend) (divisor) for /
I'd use an array for the functions and operator names instead - for example, [0] will refer to (a, b) => a + b, so that way you just have to subtract 1 from the number chosen by the user to get to the function. To get a and b, interpolate (dividend) (divisor) only if the case is 4 - but it can all be done at once.
const fns = [
[(a, b) => a + b, 'addition'],
[(a, b) => a - b, 'subtraction'],
[(a, b) => a * b, 'multiplication'],
[(a, b) => a / b, 'division']
];
const op = prompt('Operation: ');
const item = fns[op - 1];
if (!item) {
throw new Error('Invalid');
}
const a = Number(prompt(`Enter the value for A${op === '4' ? ' (dividend)' : ''}: `));
const b = Number(prompt(`Enter the value for b${op === '4' ? ' (divisor)' : ''}: `));
console.log(`The ${item[1]} result is ${item[0](a, b)}`);
Don't make your code complex in order to make it smaller, it makes it more confusing and misses the point, which is to make you understand the syntax and what you are doing

How do you replace multiple things at once using regex replace function? Is it possible? Trying to learn regex

Clearly it's only replacing the last thing which is i. Is there a way to do this all at once under multiple conditions? The function should replace all letter a with 4, letter e with 3, letter i with 1, letter o with 0, and letter s with 5.
function hackerSpeak(str) {
return str.replace(/a/ig, 4) && str.replace(/e/ig, 3) && str.replace(/i/ig, 1) && str.replace(/o/ig, 0) && str.replace(/s/ig, 5);
}
console.log(hackerSpeak("javascript is cool")); //"j4v45cr1pt 15 c00l"
TLDR; here's a solution:
function hackerSpeak(str) {
return str
.replace(/a/gi, 4)
.replace(/e/gi, 3)
.replace(/i/gi, 1)
.replace(/o/gi, 0)
.replace(/s/gi, 5);
}
Expalanation:
The String.prototype.replace method is a pure function, meaning it produces a new string each time you call it and leaves the original string intact (which is immutable in js anyway). The a && b operator evaluates to b if a is "truthy", so when you say str.replace(...) && str.replace(...), only the part on the right gets returned (given the first one didn't result in an empty string.
Used an object for a more dynamic approach.
const toReplaceWith = {
a: 4,
s: 5,
i: 1,
o: 0
}
function hackerSpeak(str){
Object.keys(toReplaceWith).forEach(letter => str = str.replaceAll(new RegExp(letter, 'gi'), toReplaceWith[letter]));
return str;
}
console.log(hackerSpeak("javascript is cool")); //"j4v45cr1pt 15 c00l"
// Bind replaces
const hackerSpeak = (str) => {
return str.replace(/a/ig, 4).replace(/e/ig, 3).replace(/i/ig, 1).replace(/o/ig, 0).replace(/s/ig, 5);
}
// Demo
console.log(hackerSpeak("javascript is cool")); //"j4v45cr1pt 15 c00l"
// Or use object library
const hackerSpeak = (str) => {
// Set your library
const lib = {
a: 4,
e: 3,
i: 1,
o: 0,
s: 5
};
// Apply RegExp's in loop
Object.keys(lib).forEach(k => str = str.replace(new RegExp(k, 'gi'), lib[k]));
return str;
}
// Demo
console.log(hackerSpeak("javascript is cool")); //"j4v45cr1pt 15 c00l"
// Or use just one RegExp
const hackerSpeak = str => str.replace(/([aeios])/ig, m => ({ a:4, e:3, i:1, o:0, s:5 })[m]);
// Demo
console.log(hackerSpeak("javascript is cool")); //"j4v45cr1pt 15 c00l"
JavaScript really cool)

reduce sum of digits recursivel down to a one digit number

I'm trying to solve a challenge (Digit Degree) on Code signal where the task is to find the the number of times we need to replace this number with the sum of its digits until we get to a one digit number. I.e. of the incoming number is 5 it's already a one digit number so the outcome should be 0. If the number is 100 the sum of its digits is 1 which is one digit so the outcome should be 1 and so on...
I'm doing a recursive solution like this:
let count = 0;
function digitDegree(n) {
if (n < 10) {
console.log(count);
return count;
};
const arr = n.toString().split('').map(Number);
const sumOfDigits = arr.reduce((acc, curr) => acc + curr);
count++;
digitDegree(sumOfDigits);
}
At the second loop and forth I'm getting null as output even if the console log shows the correct value. Where does it go wrong?
I saw that the question was up before but the answer was quite mathematical. Would this approach be ok or would it be considered bad practise?
You need a return statement for getting the result of the recursion.
return digitDegree(sumOfDigits);
A shorter approach would remove explicit conversions in advance and remove temporary arrays.
Then take another parameter for count and omit a global variable.
function digitDegree(n, count = 0) {
if (n < 10) return count;
return digitDegree(
n .toString()
.split('')
.reduce((acc, curr) => acc + +curr, 0),
count + 1
);
}
console.log(digitDegree(9999));
To return a value, update the last line in your function to return digitDegree(sumOfDigits)
function digitDegree(n) {
if (n < 10) {
console.log(count);
return count;
};
const arr = n.toString().split('').map(Number);
const sumOfDigits = arr.reduce((acc, curr) => acc + curr);
count++;
// add return
return digitDegree(sumOfDigits);
}
Pitfalls with your current approach:
It's impure.
digitDegree(100) returns 1 the first time you run it but returns 2 when you run it again. This is because count was declared outside the function (making it global)
let count = 0
function digitDegree(n) {
if (n < 10) {
return count;
};
const arr = n.toString().split('').map(Number);
const sumOfDigits = arr.reduce((acc, curr) => acc + curr);
count++;
return digitDegree(sumOfDigits);
}
// wrong - output should not change
console.log(digitDegree(100)) //=> 1
console.log(digitDegree(100)) //=> 2
console.log(digitDegree(100)) //=> 3
Make your function pure
A pure function is a specific kind of value-producing function that not only has no side effects but also doesn’t rely on side effects from other code—for example, it doesn’t read global bindings whose value might change.
A pure function has the pleasant property that, when called with the same arguments, it always produces the same value (and doesn’t do anything else)
Source
Suggestions:
Pass count as an argument
function digitDegree(n,count=0) {
if (n < 10) {
return count;
};
const arr = n.toString().split('').map(Number);
const sumOfDigits = arr.reduce((acc, curr) => acc + curr);
count++;
return digitDegree(sumOfDigits, count);
}
// correct
console.log(digitDegree(100)) //=> 1
console.log(digitDegree(100)) //=> 1
console.log(digitDegree(100)) //=> 1
Wrap your recursion function inside another function
function recursionWrapper(num){
let count = 0;
function digitDegree(n) {
if (n < 10) {
return count;
};
const arr = n.toString().split('').map(Number);
const sumOfDigits = arr.reduce((acc, curr) => acc + curr);
count++;
// notice how we don't need a return in this approach
digitDegree(sumOfDigits)
}
digitDegree(num)
return count
}
// correct
console.log(recursionWrapper(100)) //=> 1
console.log(recursionWrapper(100)) //=> 1
console.log(recursionWrapper(100)) //=> 1
Further reading:
How to deal with dirty side effects in your pure functional JavaScript
Javascript and Functional Programming — Pt. 3: Pure Functions

Math.max method on array with equal values

I'm working on some coderbyte code, and noticed that when I try to get the max item in an array of equal values undefined is returned. When logging the min value is logs 80 and not undefined. Why is this?
Updated Code:
function noRepeat(arr) {
tmp = []
if (arr.length === 2 && arr[0] === arr[1]) {
return arr;
}
for (var i = 0;i<arr.length;i++) {
if (tmp.indexOf(arr[i]) === -1) {
tmp.push(arr[i])
}
}
return tmp
}
function SecondGreatLow(arr) {
arr = noRepeat(arr).sort(function (a,b) {return a-b;});
var low = arr[arr.indexOf(Math.min.apply(Math,arr))+1];
console.log("low",low);
var high = arr[arr.indexOf(Math.max.apply(Math,arr))-1];
console.log("high",high);
return low +" "+high;
}
console.log(SecondGreatLow([80,80]));
Output:
"low" 80
"high" undefined
"80 undefined"
That's, actually, ok. How do you want to find the second largest \ smallest number in an array of two similar numbers?
It should output "no solution" or something else. Just like there is no solution for an empty array.
function SecondGreatLow(arr)
{
arr = noRepeat(arr).sort(function (a,b) {return a-b;});
if (arr.length < 2)
return "No solution";
console.log("low ", arr[1]);
console.log("high ", arr[arr.length - 2]);
return low + " " + high;
}
You don't need Math min and max functions as your array is sorted and values are unique. You need to take the second from beginning and the second from the end.
Also, you don't need this part as it is calculated right by algorithm.
if (arr.length === 2)
{
return arr[1] + " " + arr[0];
}
For example, you have an array [1, 1, 2].
You remove repetitions and get [1, 2].
Now your algorithms returns low = arr[1] = 2 and high = arr[2 - 2] = arr[0] = 1.
The answer is correct - 2 is the second minimum number and 1 is the second largest.

Show arrays arranged in a specific format

I have the following array in JavaScript:
myArray = ["lu9","lu10","lu11","ma9","ma10","ma11","mi9","mi10","mi11"];
Then, I need to display the values ​​(for example in an alert) but must be arranged as follows:
"lu9,ma9,mi9,lu10,ma10,mi10,lu11,ma11,mi11"
How I can do this?
Each item in your list has two parts: The leading mishmash of characters (mi, ma, lu) and a numerical suffix. To properly sort, we have to take both into account.
array.sort(function sorter (a, b) {
var re = /^(\D+)(\d+)$/,
left = re.exec(a),
right = re.exec(b);
if (left[1] === right[1]) {
return Number(left[2]) - Number(right[2]);
}
return left[1] < right[1] ? -1 : 1;
});
Let's say a = lu9 and b = lu10:
1. left = ['lu9', 'lu', '9']
2. right = ['lu10', 'lu', '10']
3. left[1] === right[1]
1. Number(left[2]) = 9
2. Number(right[2]) = 10
3. return 9 - 10 (negative number, a before b)
Now if our input is a = lu9 and b = mi4:
1. left = ['lu9', 'lu', '9']
2. right = ['mi4', 'mi', '4']
3. left[1] !== right[1]
1. left[1] < right[1] = true
2. return -1
var myArray = ["lu9", "lu10", "lu11", "ma9", "ma10", "ma11", "mi9", "mi10", "mi11"];
function myResult(myArray) {
myArray = myArray.slice().sort(function (a, b) {
var reg = /\d+/ //A regex to extract the numerical part
var num = 2 * (+a.match(reg) - +b.match(reg)) //Put a weight of 2 on the numerical value
var str = a > b ? 1 : a < b ? -1 : 0 //The strings value with a single weight
return num + str //add them and we have a positive or negative value with a correct weight on the numerical part
})
return "" + myArray
}
console.log (myResult(myArray)) //"lu9,ma9,mi9,lu10,ma10,mi10,lu11,ma11,mi11"
Heres a Fiddle
var myArray = ["lu9","lu10","lu11","ma9","ma10","ma11","mi9","mi10","mi11"];
var derp = function(a, b) {
a = a.replace(/[^0-9]+/g, '', a);
b = b.replace(/[^0-9]+/g, '', b);
return a < b;
}
myArray.sort(derp);
We need to sort first by number and then by letters.
no need for regex here.
We will use padding:
so ma11 will be 0011ma
and mi11 will be 0011mi
and ma11 will be 0011ma
(and mi9 will be 0009mi , the padding helps 11 to be bigger then 2 as string)
so sorting it now - will yield the right result.
var a = ["ma9", "ma10", "ma11", "mi9", "mi10", "mi11", "lu9", "lu10", "lu11"]
a.sort(function (a, b)
{
return calc(a) > calc(b);
});
function calc(x)
{
return ("0000" + x.slice(2)).slice(-4) + x.slice(0,2);
}
result :
["ma9", "ma10", "ma11", "mi9", "mi10", "mi11", "lu9", "lu10", "lu11"]

Categories

Resources