Return all possible combinations of array with optional strings - javascript

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(' ')));

Related

I have a array of string have to find all the common character present from all strings

I have a array of string.
let arr=["robin","rohit","roy"];
Need to find all the common character present in all the strings in array.
Output Eg: r,o
I have tried to create a function for above case with multiple loops but i want to know what should be the efficient way to achive it.
Here's a functional solution which will work with an array of any iterable value (not just strings), and uses object identity comparison for value equality:
function findCommon (iterA, iterB) {
const common = new Set();
const uniqueB = new Set(iterB);
for (const value of iterA) if (uniqueB.has(value)) common.add(value);
return common;
}
function findAllCommon (arrayOfIter) {
if (arrayOfIter.length === 0) return [];
let common = new Set(arrayOfIter[0]);
for (let i = 1; i < arrayOfIter.length; i += 1) {
common = findCommon(common, arrayOfIter[i]);
}
return [...common];
}
const arr = ['robin', 'rohit', 'roy'];
const result = findAllCommon(arr);
console.log(result);
const arr = ["roooooobin","rohit","roy"];
const commonChars = (arr) => {
const charsCount = arr.reduce((sum, word) => {
const wordChars = word.split('').reduce((ws, c) => {
ws[c] = 1;
return ws;
}, {});
Object.keys(wordChars).forEach((c) => {
sum[c] = (sum[c] || 0) + 1;
});
return sum;
}, {});
return Object.keys(charsCount).filter(key => charsCount[key] === arr.length);
}
console.log(commonChars(arr));
Okay, the idea is to count the amount of times each letter occurs but only counting 1 letter per string
let arr=["robin","rohit","roy"];
function commonLetter(array){
var count={} //object used for counting letters total
for(let i=0;i<array.length;i++){
//looping through the array
const cache={} //same letters only counted once here
for(let j=0;j<array[i].length;j++){
//looping through the string
let letter=array[i][j]
if(cache[letter]!==true){
//if letter not yet counted in this string
cache[letter]=true //well now it is counted in this string
count[letter]=(count[letter]||0)+1
//I don't say count[letter]++ because count[letter] may not be defined yet, hence (count[letter]||0)
}
}
}
return Object.keys(count)
.filter(letter=>count[letter]===array.length)
.join(',')
}
//usage
console.log(commonLetter(arr))
No matter which way you choose, you will still need to count all characters, you cannot get around O(n*2) as far as I know.
arr=["robin","rohit","roy"];
let commonChars = sumCommonCharacters(arr);
function sumCommonCharacters(arr) {
data = {};
for(let i = 0; i < arr.length; i++) {
for(let char in arr[i]) {
let key = arr[i][char];
data[key] = (data[key] != null) ? data[key]+1 : 1;
}
}
return data;
}
console.log(commonChars);
Here is a 1 liner if anyone interested
new Set(arr.map(d => [...d]).flat(Infinity).reduce((ac,d) => {(new RegExp(`(?:.*${d}.*){${arr.length}}`)).test(arr) && ac.push(d); return ac},[])) //{r,o}
You can use an object to check for the occurrences of each character. loop on the words in the array, then loop on the chars of each word.
let arr = ["robin","rohit","roy"];
const restWords = arr.slice(1);
const result = arr[0].split('').filter(char =>
restWords.every(word => word.includes(char)))
const uniqueChars = Array.from(new Set(result));
console.log(uniqueChars);

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'))

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

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);
})

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]]

Group and aggregate an array of javascript objects

I am having some trouble rationalising and aggregating an array of objects in javascript.
Given the array
[{"description":"Bright","size":"2XL","price":10.99},{"description":"Bright","size":"XL","price":10.99},{"description":"Bright","size":"L","price":9.99},{"group":"Foos","description":"Dull","size":"XL","price":9.99},{"description":"Dull","size":"L","price":8.99},{"description":"Dull","size":"2XL","price":9.99},{"description":"Shiny","size":"XL","price":9.99},{"description":"Shiny","size":"S","price":8.99},{"description":"Shiny","size":"3XL","price":10.3},{"description":"Shiny","size":"2XL","price":9.99}]
I am trying to convert it to an array in the format (actual values may be wrong here).
[{"descriptions":"Shiny, Bright, Dull","sizeRange":"S - L","price":8.99},{"descriptions":"Shiny, Bright, Dull","sizes":"XL - 2XL","price":9.99},{"descriptions":"Dark","sizes":"S - 2XL","price":10.99}]
That is - I wish to group each set of items by price, showing the descriptions and size ranges for them.
So far this is what I have, and it seems to be working, but it just seems very cumbersome. Really I'd be more than happy to use something like lodash or underscore if it would help to rationalise the code a bit rather than using native JS.
function groupBy (array, key) {
return array.reduce(function(value, property) {
(value[property[key]] = value[property[key]] || []).push(property);
return value;
}, {});
};
function unique(array) {
return Array.from(new Set(array));
};
function getRanges(data)
{
var result = [];
// simple map of sizes from smallest to largest to use for sorting
var sizeSort = {'S':1, 'M':2, 'L':3, 'XL':4, '2XL':5, '3XL':6, '4XL':7, '5XL':8};
// group the remaining variants by price
var group = groupBy(data, 'price');
// for each variant price group
for(var price in group) {
var item = {};
item.price = price;
// get the range of sizes sorted smallest to largest
var sizes = unique(group[price].map(function(i) {
return i.size;
})).sort(function(a, b) {
return sizeSort[a] - sizeSort[b];
});
// Add single size, or first and last size.
item.sizes = (sizes.length === 1) ?
sizes.shift() :
sizes.shift() + ' - ' + sizes.pop();
// Add the descriptions as alphabetically sorted CSV
item.description = unique(group[price].map(function(i) {
return i.description;
})).sort().join(", ");
result.push(item);
}
return result;
}
Here is a version using lodash..
I think it looks more rational..
function calc(data) {
var sizeSort = {'S':1, 'M':2, 'L':3, 'XL':4, '2XL':5,
'3XL':6, '4XL':7, '5XL':8};
return _.chain(data).
groupBy('price').
map(function(f){
var sizes = _.chain(f).map('size').uniq().
sortBy(function (a) { return sizeSort[a] }).value();
return {
price: _.head(f).price,
description: _.chain(f).map('description').uniq().join(',').value(),
size: sizes.length === 1 ? _.first(sizes) : _.join([_.first(sizes),_.last(sizes)], ' - ')
}
}).
sortBy(['price']).
value();
}
//put data at end, so not having to scroll down to see code
var data = [{"description":"Bright","size":"2XL","price":10.99},{"description":"Bright","size":"XL","price":10.99},{"description":"Bright","size":"L","price":9.99},{"group":"Foos","description":"Dull","size":"XL","price":9.99},{"description":"Dull","size":"L","price":8.99},{"description":"Dull","size":"2XL","price":9.99},{"description":"Shiny","size":"XL","price":9.99},{"description":"Shiny","size":"S","price":8.99},{"description":"Shiny","size":"3XL","price":10.3},{"description":"Shiny","size":"2XL","price":9.99}];
console.log(calc(data));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.16.3/lodash.js"></script>
A vanilla JavaScript solution (with ES6 template strings)
/*
Some boilerplate functions. Listed underscore/lodash functions that
could replace them above
*/
// _.mapObject(object, reducer)
function reduceValues(object, reducer) {
let newObject = {}
for (var property in object) {
if (object.hasOwnProperty(property)) {
newObject[property] = reducer(object[property])
}
}
return newObject
}
// _.groupBy
function groupBy(arr, key) {
let reducer = (grouped, item) => {
let group_value = item[key]
if (!grouped[group_value]) {
grouped[group_value] = []
}
grouped[group_value].push(item)
return grouped
}
return arr.reduce(reducer, {})
}
// _.values
function objectValues(object) {
let values = []
for (var property in object) {
if (object.hasOwnProperty(property)) {
values.push(object[property])
}
}
return values
}
/*
Shirt specific functions and data
*/
// Mapping of shirts to their order.
let sizesToNumbers = {'S':1, 'M':2, 'L':3, 'XL':4, '2XL':5, '3XL':6, '4XL':7, '5XL':8};
// Create an intermediate summary with data instead of strings.
// This makes processing easier to write and reason about
function reduceShirtsToSummary(shirts) {
let reducer = (summary, shirt) => {
summary['descriptions'].add(shirt['description'])
let shirtSize = shirt['size']
if (!summary['smallestSize'] || sizesToNumbers[shirtSize] < sizesToNumbers[summary['smallestSize']]) {
summary['smallestSize'] = shirtSize
}
if (!summary['largestSize'] || sizesToNumbers[shirtSize] > sizesToNumbers[summary['largestSize']]) {
summary['largestSize'] = shirtSize
}
summary['prices'].push(shirt['price'])
return summary
}
return shirts.reduce(reducer, {'descriptions': new Set(), 'prices': []})
}
// Convert the shirt summary data into the "labelized" version with strings in the example
function labelizeShirtSummary(shirtSummary) {
let labelizedShirtSummary = {}
labelizedShirtSummary['descriptions'] = Array.from(shirtSummary['descriptions']).join(', ')
labelizedShirtSummary['price'] = shirtSummary['prices'][0]
labelizedShirtSummary['sizes'] = `${shirtSummary['smallestSize']} - ${shirtSummary['largestSize']}`
return labelizedShirtSummary
}
let grouped = groupBy(shirts, 'price')
let groupedAndSummarized = reduceValues(grouped, reduceShirtsToSummary)
let labelizedSummaries = objectValues(groupedAndSummarized).map(labelizeShirtSummary)
// Gives desired output
console.log(labelizedSummaries)

Categories