How to test if an array contains arrays? (Jest) - javascript

Given the following function:
chunk = (arr, n) => {
return arr.reduce(function(p, cur, i) {
(p[i/n|0] = p[i/n|0] || []).push(cur);
return p;
}, []);
}
I want to test if the output array contains arrays:
test("Splits a given array in N arrays", () => {
let _input = Array.from({length: 999}, () => Math.floor(Math.random() * 999));;
let int = mockService.getRandomArbitrary();
expect(generalService.chunk(_input, int)).toContain(???????);
})
Considering matchers, how can one test if an array contains arrays?

Possibly something like this?
We use Array.isArray() (docs here) to test each element
let output = generalService.chunk(_input, int));
let containArrays = true;
if (output.length) {
output.forEach(element => {
// containArrays is false if one of the element is *not* an array
if (Array.isArray(element) === false) containArrays = false;
});
}
// if output is an empty array it doesn't contain arrays
else containArrays = false;
expect(containArrays).toBeTruthy();

If you use lodash it's very easy
_.any(arr,function(a){
return Array.isArray(a);
})

Related

Return all possible combinations of array with optional strings

Lets say I have an array keys = ["the?", "orange", "van", "s?"], with '?' at the end of strings to represent that it is optional.
I want a function in javascript generateCombinations(keys) that returns the possible combinations such as :
[["orange","van"],["the","orange","van"],["orange","van","s"],["the","orange","van","s"]]
One possible way of removing '?' is to simply do a replace("?',"").
I have a feeling it might require a recursive function, which I am not yet quite strong in. Help is appreciated!
So far I've tried this:
function isOptionalKey(key) {
return key.endsWith('?');
}
function hasOptionalKey(keys) {
return keys.some(isOptionalKey);
}
function stripOptionalSyntax(key) {
return key.endsWith('?') ? key.slice(0, -1) : key;
}
function generateCombinations(keys) {
if (keys.length === 1) {
return keys;
}
const combinations = [];
const startKey = keys[0];
const restKeys = keys.slice(1);
if (hasOptionalKey(restKeys)) {
const restCombinations = isOptionalKey(startKey)
? generateCombinations(restKeys)
: restKeys;
if (isOptionalKey(startKey)) {
combinations.push(restCombinations);
}
combinations.push(
restCombinations.map((c) => [stripOptionalSyntax(startKey), ...c])
);
} else {
if (isOptionalKey(startKey)) {
combinations.push(restKeys);
}
combinations.push([stripOptionalSyntax(startKey), ...restKeys]);
}
return combinations;
}
You could take a recursive approach by using only the first item of the array and stop if the array is empty.
const
getCombinations = array => {
if (!array.length) return [[]];
const
sub = getCombinations(array.slice(1)),
optional = array[0].endsWith('?'),
raw = optional ? array[0].slice(0, -1) : array[0],
temp = sub.map(a => [raw, ...a]);
return optional
? [...temp, ...sub]
: temp;
};
keys = ["the?", "orange", "van", "s?"],
result = getCombinations(keys);
console.log(result.map(a => a.join(' ')));

Looping data from json using Array

I'm trying to write a function but I doesn't make it. This function works like that
Input: changeSetting("a>b>c","hello")
After that "setting" named value change from {} to {"a":{"b":{"c":"hello"}}}
If input is changeSetting("a","hello") json become {} to {"a":"hello"}
My last code attempt:
function changeSetting(name,val) {
if (name.includes(">")) {
name = name.split('>')
let json = {}
name.map((el,i)=>{
let last = ""
name.filter(el=>!name.slice(i+1).includes(el)).map(el=> {
if(last!="") {
json[el] = {}
}})
})
}
}
How can we make this ? (Optimization not important but if is it good for me)
const changeSetting = (setting, target) => {
if (setting.length < 2) {
return {
[setting]: target
}
} else {
const keys = setting.split('>');
return keys.reduceRight((acc, curr, i) => {
console.log(acc);
if(i === keys.length - 1) {
return acc = {[curr] : target}
}
return acc = { [curr]: acc };
}, {})
}
}
console.log(changeSetting('a', 'hello'));
console.log(changeSetting('a>b>c', 'hello'));
function changeSetting(inputProperties, value) {
let result;
const properties = inputProperties.split(">");
result = `{${properties
.map((property) => `"${property}":`)
.join("{")}"${value}"${"}".repeat(properties.length)}`;
return result;
}
changeSetting("a>b>c", "hello");
changeSetting("a", "hello");
As you work with strings - you may try to use JSON like this:
function changeSetting(name, val) {
const keys = name.split(">");
return JSON.parse(
[
"{",
keys.map((key) => `"${key}"`).join(":{"),
":",
`"${val}"`,
"}".repeat(keys.length),
].join("")
);
}
There's multiple ways to do this, I've commented the snippet
const changeSetting = (name, val) => {
// Split and reverse the name letters
const nameSplit = name.split('>').reverse();
// Set up the inner most object
let newObj = {[nameSplit[0]]:val}
// Now remove the first letter and recurse through the rest
nameSplit.slice(1).forEach((el, idx) => newObj = {[el]: newObj});
console.log(newObj);
}
changeSetting("a>b>c", "hello")
changeSetting("a", "hello")
changeSetting("a>b>c>d>e>f>g", "hello")
You can create an array by splitting name on all > with String.prototype.split(), and then Array.prototype.reduceRight() the created array of elements with an object initial value {} and adding key value pairs but on the last element the value should be variable val.
Code:
const changeSetting = (name, val) => name
.split('>')
.reduceRight((a, c, i, arr) => ({
[c]: i === arr.length - 1 ? val : a
}), {})
console.log(changeSetting('a>b>c', 'hello'))
console.log(changeSetting('a', 'hello'))
console.log(changeSetting('a>b>c>d>e>f>g', 'hello'))

filtering 2 arrays for unique elements

I write a function that receives 2 arrays and returns an array that has elements that exist in both arrays.
For example, if I pass [6,7,8,9] and [1,8,2,6], it should return [6,8].
My aim is not to use loops here.
I use this code:
const uniqueElements= (arr1, arr2) => {
return arr1.filter(it1=> arr2.filter((it2) => it2===it1).length>0)
}
However, if there are duplicate elements in arrays (e.g. [6,7,8,9,6] and [1,8,2,6,6]), it returns [6, 8, 6].
How should I mend my code so that it would return only unique elements without duplicates? Is it possible without using loops?
If you just want to get unique value which appears on both of the array, you just first change both of the array's to Set, loop through one Set and check if it's present on other or not, if it present return true from filter else return false,
const uniqueElements= (arr1, arr2) => {
let set1 = new Set(arr1)
let set2 = new Set(arr2)
return [...set1].filter(it1=> set2.has(it1))
}
console.log(uniqueElements([6,7,8,9],[1,8,2,6]))
console.log(uniqueElements([6,7,8,9,6],[1,8,2,6,6]))
Ref to read about Set
Set MDN
For your scenario use this -
constant uniqueElements= (arr1, arr2) => {
return Array.from(new Set(arr1.filter(it1=> arr2.filter((it2) => it2===it1).length>0)))
}
Hope this helps
Solution using Set from https://2ality.com/2015/01/es6-set-operations.html:
const uniqueElements = (arr1, arr2) => {
const a = new Set(arr1);
const b = new Set(arr2);
const intersection = new Set(
[...a].filter(x => b.has(x)));
return Array.from(intersection);
}
Simply you can just use Array#some() method to write the condition inside your Array#filter() method's callback:
const uniqueElements = (arr1, arr2) => {
let viewed = [];
return arr1.filter(it1 => {
let found = arr2.some((it2) => it2 === it1) && viewed.indexOf(it1) == -1;
viewed.push(it1);
return found;
});
}
Note:
This doesn't take duplicates, with the use of viewed array and viewed.indexOf(it1) == -1 condition.
Demo:
const uniqueElements = (arr1, arr2) => {
let viewed = [];
return arr1.filter(it1 => {
let found = arr2.some((it2) => it2 === it1) && viewed.indexOf(it1) == -1;
viewed.push(it1);
return found;
});
}
let a1 = [6,7,8,9,6];
let a2 = [1,8,2,6,6];
console.log(uniqueElements(a1, a2));

Counting letters and numbers in string

I write a code that works with letter but not with numbers
I know it maybe a little complicated but this is how I could do it;
with numbers it produce ordered Array and I don't know why
var orderedCount = function(text) {
let splitted = text.split('');
let countedLetters = splitted.reduce((AllLetters, letter) => {
(letter in AllLetters) ? AllLetters[letter]++: AllLetters[letter] = 1;
return AllLetters
}, {})
let result = Object.keys(countedLetters).map((key) => {
return [(key), countedLetters[key]]
})
return result;
};
console.log(orderedCount("abracadabra")); //[['a',5], ['b',2], ['r',2], ['c',1], ['d',1]]
console.log(orderedCount("212")); //[['1',1], ['2',2]]
[['1',1],['2',2]]
should be
[['2',2],['1',1]]
You can use Object.entries to convert the object into an array and use sort to sort the element 1
var orderedCount = function(text) {
let splitted = text.split('');
let countedLetters = splitted.reduce((AllLetters, letter) => {
(letter in AllLetters) ? AllLetters[letter]++: AllLetters[letter] = 1;
return AllLetters
}, {})
let result = Object.entries(countedLetters).sort((a, b) => {
return b[1] - a[1];
})
return result
};
console.log(orderedCount("abracadabra"));
console.log(orderedCount("212"));
Shorter Version:
var orderedCount = function(text) {
return Object.entries(text.split('').reduce((c, v) => {
c[v] = (c[v] || 0) + 1;
return c;
}, {})).sort((a, b) => b[1] - a[1]);
};
console.log(orderedCount("abracadabra"));
console.log(orderedCount("212"));
You can create the array directly using Array#reduce method where use a reference object which keeps object reference based on letter value.
var orderedCount = function(text) {
const ref = {};
return text.split('').reduce((arr, letter) => {
(letter in ref) ? ref[letter][1]++: arr.push(ref[letter] = [letter, 1]);
return arr;
}, []);
};
console.log(orderedCount("abracadabra")); //[['a',5], ['b',2], ['r',2], ['c',1], ['d',1]]
console.log(orderedCount("212")); //[['1',1], ['2',2]]
Refer : Does JavaScript Guarantee Object Property Order?
Since es2015 onwards non-integer keys are kept inserting order and integer keys are sorted numerically.
You need to sort you result by count, because by default numeric keys in Object will be in ascending order
var orderedCount = function(text) {
let splitted = text.split('');
let countedLetters = splitted.reduce((AllLetters, letter) => {
(letter in AllLetters) ? AllLetters[letter]++: AllLetters[letter] = 1;
return AllLetters
}, {})
let result = Object.keys(countedLetters).map((key) => {
return [(key), countedLetters[key]]
})
return result.sort((a,b)=>b[1] - a[1]);
};
console.log(orderedCount("abracadabra")); //[['a',5], ['b',2], ['r',2], ['c',1], ['d',1]]
console.log(orderedCount("212")); //[['1',1], ['2',2]]

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