refactoring function with different methods - javascript - javascript

This function current takes in an array, and outputs it a character with its count if the element's length is greater than one
Is there a way I can do this same thing, perhaps with a different javascript array method without having to use a new array or a result variable?
const a = ['aaa', 'z', 'eeeee'];
const compressCharacters = arr => {
let newArray = [];
let result = arr.forEach(e => e.length > 1 ? newArray.push(`${e[0]}${e.length}`) : newArray.push(e))
return newArray.join("");
}
console.log(compressCharacters(a));

You can just use map() instead of forEach() and return the values you want. There's no need for the extra array. It's often the case than forEach + push() == map()
const a = ['aaa', 'z', 'eeeee'];
const compressCharacters = arr => {
return arr.map(e => e.length > 1
? `${e[0]}${e.length}`
: e)
.join("");
}
console.log(compressCharacters(a));

You can construct and immediately return a mapped array, that's joined by the empty string:
const a = ['aaa', 'z', 'eeeee'];
const compressCharacters = arr => (
arr.map(str => str[0] + (str.length > 1 ? str.length : ''))
.join('')
)
console.log(compressCharacters(a));
When you're trying to construct a new array by transforming all elements from some other array, .map is the appropriate method to use. forEach should be reserved for generic iteration with side effects, when you aren't trying to construct a new object. (when you are trying to construct a new object, but it's not an array, or the new array isn't one-to-one with the old array, usually you can use .reduce)

This is an alternative using the function reduce to build the desired output.
const a = ['aaa', 'z', 'eeeee'],
result = a.reduce((a, [m], i, arr) => {
let {length} = arr[i];
return a + m + (length <= 1 ? "" : length);
}, "");
console.log(result);

Related

Sorting an array of letters by order they came out from original string in JavaScript

I'm currently learning javascript sorting on terminal/console and due to my limited experience, I'm getting stuck figuring this one out... so please, bare with me.
I'm making a function that will return an array containing vowels of a string. The result array's elements is ordered by which character came first. For example:
Suppose I have this string = "Sample Case"
The function should return a result array like this = [a,a,e,e]
Below is my code and right now, what I can do is to extract the vowels with my function:
const prompt = require('prompt-sync')();
const isVowel = (c) => {
if(c) {
return ['a', 'e', 'i', 'o', 'u'].indexOf(c.toLowerCase()) !== -1;
} else {
return 'string is empty';
}
};
const sortChar = () => {
const wordsInput = prompt("Input one line of word(s): ");
let vowels = wordsInput.split(' ').join('').split('').filter((x) => isVowel(x));
let consonants = wordsInput.split(' ').join('').split('').filter((x) => !isVowel(x));
console.log(vowels.sort((a, b) => {
if(a === b) {
return 1;
}
return 0;
}))
};
sortChar();
Current output with above code: [a,e,a,e]
How to sort this result array so that the new result is [a,a,e,e]?
Any input will be appreciated, thank you in advance!
Note: if what you're looking for is not sorting vowels lexically (in alphabetic order) but in the order of appearance, localeCompare() doesn't seem to be an appropriate tool. If I missed the whole point, you may safely ignore this answer.
To me, sorting is a way too expensive operation for this particular use case. I would rather think of it as filtering out vowels and stacking them in the order of appearance (where Map might come in handy).
const sourceString = 'Sample Case'
const isVowel = char =>
'aeiouAEIOU'.includes(char)
const stackFilteredVowels = str => {
const vowelsMap = [...str.toLowerCase()]
.reduce((acc, char) => {
const isCharVowel = isVowel(char)
if(isCharVowel) {
const vowelOccurence = acc.get(char)
acc.set(char, (vowelOccurence || 0)+1)
}
return acc
}, new Map)
const stackedVowels = [...vowelsMap.entries()]
.reduce((acc, [vowel, occurence]) =>
(acc.push(...Array(occurence).fill(vowel)), acc), [])
return stackedVowels
}
console.log(stackFilteredVowels(sourceString))
.as-console-wrapper{min-height: 100%;}
You could
get all vowels,
iterate the vowels,
check if index for theis vowel exist, if not assign a new index and push an empty array to the result set vowels,
push vowel to the according array,
get flat array,
return this array.
function orderVowels(string) {
const
indices = {},
vowels = [];
for (const v of string.match(/[aeiou]/ig)) {
if (!(v in indices)) {
indices[v] = vowels.length;
vowels.push([]);
}
vowels[indices[v]].push(v);
}
return vowels.flat();
}
console.log(orderVowels("Sample Case"));
const isVowel = (c) => !c ? 'string is empty' : ['a', 'e', 'i', 'o', 'u'].includes(c);
const sortChar = () => {
const wordsInput = "Input one line of word(s): ";
const vowels = [...wordsInput].filter(char => isVowel(char) && char.match(/\w/));
const consonants = [...wordsInput].filter(char => !isVowel(char) && char.match(/\w/));
console.log({
vowelsSorted: vowels.sort((a, b) => new Intl.Collator('en').compare(a, b)),
consonantsSorted: consonants.sort((a, b) => new Intl.Collator('en').compare(a, b))
})
}
;
sortChar();
Let's simplify this. Spread the string into an array and filter the consonants with isVowel. Sort using localeCompare
const isVowel = c => `aeiou`.indexOf((c || ``).toLowerCase()) > -1;
// ^ use empty string if !c
const sortChar = words => {
return [...words].filter( chr => isVowel(chr) )
// ^ spread into array
// ^ filter delivers array
.sort( (a, b) => a.localeCompare(b) )
// ^ sort using localeCompare
.join(``);
};
console.log(sortChar(`As said: input one line of word(s)`));

JavaScript Trying to Print The Number of Times a Letter Appears In String But It Prints More than Once

In the code below, I am trying to check how many times a letter in a string appears. Problem with the code below is that it prints each letter more than once. It needs to collect all the same letters and show the number of times it occurs in the string and display it once.
const string = 'mississippi'
const letters = [...string]
let currentLetter = ''
let letterOccurance = []
for(let i = 0; i < letters.length; i++){
let letterFrequency = letters.filter((letter)=>{
return letter === letters[i]
})
letterOccurance.push([`${letters[i]}`,letterFrequency.length])
}
console.log(letterOccurance)
That's too much code just to get the number of times a letter appears in a string. Try the following code:
const string = 'mississippi';
let frequency = {};
for (let letter of string) {
if (frequency[letter]) {
frequency[letter]++;
} else {
frequency[letter] = 1;
}
}
console.log(frequency);
You're always pushing the letter to the array, whether it already exists there or not:
letterOccurance.push([`${letters[i]}`,letterFrequency.length])
You could check if it exists first:
if (!letterOccurance.find(l => l[0] === letters[i])) {
letterOccurance.push([`${letters[i]}`,letterFrequency.length])
}
Or even skip it entirely if you've already seen it, since the first time you find any letter you already know its complete count:
for(let i = 0; i < letters.length; i++){
if (letterOccurance.find(l => l[0] === letters[i])) {
continue;
}
// the rest of the loop...
}
There's honestly a variety of ways you could step back and re-approach the problem. But for the question about why letters are repeating, that's simply because each iteration of the loop unconditionally appends the letter to the resulting array.
How about writing a more generic item-counting function and then layering countLetters as a simple partial application of the identity function?
const countBy = (fn) => ([...xs]) =>
xs .reduce ((a, x) => {const k = fn (x); a [k] = (a[k] || 0) + 1; return a}, {})
const countLetters = countBy (x => x)
console .log (countLetters ('missisippi'))
countBy is fairly generic. You pass it a function to convert your values to strings, and pass your array of items to the function it returns. Strings are array-like enough that this just works for our simple countLetters. But we could use it for other counts as well, such as:
countBy (x => x .grade) ([{id: 1, grade: 'A'}, {id: 2, grade: 'B'}, {id: 3, grade: 'A'}])
//=> {"A": 2, "B": 1}
Here's a solution using a Set to get the individual letters and String.split() to count.
const countChars = str => Object.fromEntries(
[...new Set(str)]
.map(c => [c, str.split(c).length-1])
)
console.log(countChars('mississippi'));
Using reduce to build the object
const countChars = str => [...str].reduce(
(a, c) => (a[c] ? a[c]++ : a[c]=1, a),
{}
)
console.log(countChars('mississippi'));
var result =

How to simply find matching values in [[], [], []] arrays in javascript

new to javascript. i have these two arrays
var array1 = [['1'],['2']];
var array2 = [['2'],['3'],['4']];
how can i find the matching values?
tried below but returns empty array probably because it's for normal array structure ['', '', '']
var matchingValue = array1.filter(value => array2.includes(value));
Logger.log(matchingValue);
Matching value should be ['2']
You can simply use .flat() to flatten the arrays so you only deal with the values like so :-
var array1 = [['1'],['2']];
var array2 = [['2'],['3'],['4']];
var matchingValue = array1.flat().filter((value) => array2.flat().includes(value) )
console.log(matchingValue);
First, let's have a function that tells if two arrays are equal:
let equal = (a, b) => a.length === b.length && a.every((_, i) => a[i] === b[i])
Then, use this function to find an intersection of two arrays:
var array1 = [['1'],['2']];
var array2 = [['2'],['3'],['4']];
let equal = (a, b) => a.length === b.length && a.every((_, i) => a[i] === b[i])
result = array1.filter(x => array2.some(y => equal(x, y)))
console.log(result)
In a more generic way, you can write intersectBy that would compute an intersection using a predicate callback:
let intersectBy = (a, b, predicate) =>
a.filter(x => b.some(y => predicate(x, y)))
Array.includes compares by identity, so it works with primitives and not with Array and Objects.
This is a solution that compares the first element of the internal arrays:
var matchingValue = array1.filter(value1 => {
return array2.map(value2 => value2[0]).includes(value1[0]);
});
Array.map is used to convert an Array of Arrays in Array of strings, then Array.includes is used to match the first element of first Array.
This works only with the current structure (arrays of one element arrays).
const array1 = [['1'],['2']];
const array2 = [['2'],['3'],['4']];
const array1Flat = [].concat(...array1);
const array2Flat = [].concat(...array2);
const matchingValue = array1Flat.filter((value) => array2Flat.includes(value));
console.log(matchingValue);
You don't need to use .flat() you can simply use concat and spread to flatten the array.

Convert javascript string to array

I have string like this
'10:00','13:00','12:00','15:00','08:00','12:00'
I need it in format like this
Array(3)
Array[0] ['10:00', '13:00']
Array[1] ['12:00', '15:00']
Array[2] ['08:00', '12:00']
I tried with split method but without success.
You could replace single quotes with double quotes, add brackes and parse it as JSON and get an array, which is the grouped by two elements.
var string = "'10:00','13:00','12:00','15:00','08:00','12:00'",
array = JSON
.parse('[' + string.replace(/'/g, '"') + ']')
.reduce((r, s, i) => r.concat([i % 2 ? r.pop().concat(s) : [s]]), []);
console.log(array);
.as-console-wrapper { max-height: 100% !important; top: 0; }
var str = "'10:00','13:00','12:00','15:00','08:00','12:00'";
var oldArray = str.split(',');
var newArray = [];
while(oldArray.length){
let start = 0;
let end = 2;
newArray.push(oldArray.slice(start, end));
oldArray.splice(start, end);
}
console.log(newArray);
How about:
"'10:00','13:00','12:00','15:00','08:00','12:00'"
.replace(/'/g, '').replace(/(,[^,]*),/g,"$1;")
.split(';').map(itm => itm.split(','))
In this case you want to compare 2 values.
To do this you can make a for loop that reads the current value and the last value and compares the two.
If the last value is higher than current value, the splitting logic happens.
Either you add the current value to the last item (which is an array of strings) in the results array or you add a new array of strings at the end of the results array.
One potential solution:
let S = "'10:00','13:00','12:00','15:00','08:00','12:00'";
let R = S.split(',');
let I = 0;
let A = new Array([],[],[]);
R.map((object, index) => {
A[I][index % 2] = R[index];
if (index % 2 == 1) I++;
});
console.log(A);
You can use String.split(',') to split into individual values, then group them based on their positions (result of integer division with 2).
I am using groupBy from 30 seconds of code (disclaimer: I am one of the maintainers of the project/website) to group the elements based on the integer division with 2. Short explanation:
Use Array.map() to map the values of an array to a function or property name. Use Array.reduce() to create an object, where the keys are produced from the mapped results.
The result is an object, but can be easily converted into an array using Object.values() as shown below:
var data = "'10:00','13:00','12:00','15:00','08:00','12:00'";
const groupBy = (arr, fn) =>
arr.map(typeof fn === 'function' ? fn : val => val[fn]).reduce((acc, val, i) => {
acc[val] = (acc[val] || []).concat(arr[i]);
return acc;
}, {});
var arr = data.split(',');
arr = groupBy(arr, (v, i) => Math.floor(i / 2));
arr = Object.values(arr);
console.log(arr);
.as-console-wrapper { max-height: 100% !important; top: 0; }
I think use JSON.parse is better:
var array = "'10:00','13:00','12:00','15:00','08:00','12:00'";
array = JSON.parse( '[' + array.replace(/'/g,'"') + ']' );
var array2 = [];
for(var i=0;i < array.length - 1; i++){
array2.push([array[i], array[i+1]]);
}
console.log(array2);

JavaScript to split data and calculate sums

I believe what I need are two JavaScript functions. I am receiving a comma separated string that holds two types of data: 1) device name followed by 2) numeric value. These two values are separated by a comma, and each set is also separated by a comma. Example string below:
Device_A,5,Device_C,2,Device_A,10,Device_B,8,Device_B,2,Device_C,7
What I want to do is create two separate functions. The first function finds the unique device names and returns just the names in a comma separated string. The second function would calculate the sum of the numeric values for each device. The expected results from the example string above would return:
Function 1 (Device List):
Device_A, Device_B, Device_C
Function 2 (Sums per Device List):
15,10,9
The lists do not need to return in any particular order as long at they both match up. All I have successfully done at this point is return a list of unique values (including numeric values)... I'm stuck on separating the list, but still referring to device name to sum up all of the values.
Thanks in advance. Let me know if you have any questions!
Matt
You could use an object for collecting the names and count.
This edit contains a shared function and two function for the result in equal order.
function getGrouped(data) {
var array = data.split(','),
temp = Object.create(null),
i = 0;
while (i < array.length) {
temp[array[i]] = (temp[array[i]] || 0) + +array[i + 1] || 0;
i += 2;
}
return temp;
}
function getDevices(data) {
var temp = getGrouped(data);
return Object.keys(temp).sort().join();
}
function getCounts(data) {
var temp = getGrouped(data);
return Object.keys(temp).sort().map(function (k) { return temp[k]; }).join();
}
var data = "Device_A,5,Device_C,2,Device_A,10,Device_B,8,Device_B,2,Device_C,7";
console.log(getDevices(data));
console.log(getCounts(data));
When starting out on a problem like this, I think it's wise to not worry about doing it in a single loop or in a fancy one-liner at first.
A) Start out by defining what data structures you need and how to go from one format to another:
Convert my string of data to a list of keys and values
Somehow group these keys and values based on the key
Sum the values for each group
Return a list of all unique keys
Return a list of all summed values
B) Then, try to see if any of the code you've written has the potential be re-used by other parts of your application and refactor accordingly.
C) Finally, assess if there are performance bottle necks and only if there are, optimize for performance.
A. A function for each step:
// 1. From string to array of keys and values
// You already figured this one out. Split by ","!
const namesAndValuesFromString =
str => str.split(",");
// 2. Grouping by key
// Let's first make pairs:
const deviceValuePairs = devicesAndValues => {
let pair = [];
const pairs = [];
devicesAndValues.forEach(x => {
pair.push(x);
if (pair.length === 2) {
pairs.push(pair);
pair = [];
}
});
return pairs;
};
// Key value pairs are a nice starting point for constructing a grouped object:
const kvpsToDeviceValuesObj = kvps => {
const valuesByDevice = {};
kvps.forEach(([key, value]) => {
value = Number(value);
if (!valuesByDevice[key]) {
valuesByDevice[key] = [];
}
valuesByDevice[key].push(value);
});
return valuesByDevice;
};
// 3. Now, we can get to summing the values arrays
const sumValueArrays = valuesByDevice => {
const summedValuesByDevice = {};
// Loop over the objects entries
Object.entries(valuesByDevice).forEach(
([key, values]) => {
summedValuesByDevice[key] = values
.reduce((a, b) => a + b);
}
);
return summedValuesByDevice;
};
// 4. + 5. Now that we have an object with device ids as keys, and summed values inside, we can retrieve the two lists
const getDevices = Object.keys;
const getSums = Object.values;
// Running the code:
const namesAndValues =
namesAndValuesFromString("A,5,C,2,A,10,B,8,B,2,C,7");
console.log(namesAndValues);
const kvps = deviceValuePairs(namesAndValues);
console.log(kvps);
const valuesByDevice = kvpsToDeviceValuesObj(kvps);
console.log(valuesByDevice);
const sumValues = sumValueArrays(valuesByDevice);
console.log(sumValues);
const devices = getDevices(sumValues);
console.log(devices);
const sums = getSums(sumValues);
console.log(sums);
B. Refactoring!
Once you understand each of those steps, you'll start to see things that can be generalized or combined. That's where the fun starts :)
// UTILITIES
const split = del => arr => arr.split(del);
const toPairs = arr => {
let pair = [];
return arr.reduce(
(pairs, x) => {
pair.push(x);
if (pair.length === 2) {
pairs.push(pair);
pair = [];
}
return pairs;
}, []);
};
const sum = (x, y = 0) => +x + y;
const kvpsToGroups = grouper => kvps =>
kvps.reduce(
(groups, [key, value]) => Object.assign(groups, {
[key]: grouper(value, groups[key])
}), {});
// YOUR APP
const sumGrouper = kvpsToGroups(sum);
const dataSplitter = split(",");
const parseData = str => sumGrouper(toPairs(dataSplitter(str)));
// MAIN
const result = parseData("A,5,C,2,A,10,B,8,B,2,C,7");
console.log("devices:", Object.keys(result));
console.log("sums:", Object.values(result));
another way by regexs
let str = "Device_A,5,Device_C,2,Device_A,10,Device_B,8,Device_B,2,Device_C,7", obj = {}
str.match(/(\w+,[0-9]+)/g).forEach((s) => {
s = s.split(',')
obj[s[0]] = (obj[s[0]] || 0) + (Number(s[1]) || 0)
})
console.log(obj)
Something like this should do it:
var input = "Device_A,5,Device_C,2,Device_A,10,Device_B,8,Device_B,2,Device_C,7";
var output = input.split(',').reduce((accumulator, currentValue, currentIndex, array) => {
accumulator[currentValue] = (accumulator[currentValue] || 0)
+ parseInt(array[currentIndex + 1]);
array.splice(0,1);
return accumulator;
}, {});
console.log(Object.keys(output));
console.log(Object.keys(output).map(k => output[k]));

Categories