Js secret santa claus algoritm - javascript

I wanted to make a small script in js that having a list of users, one user has to make a gift to another.
By applying the following constraints:
If "a" is the santa claus and gives a gift to "c" it cannot be the other way around.
So "c" cannot be the santa claus of "a".
It must work with both an even and an odd number of users.
In your opinion, what could be the right approach to use to try to minimize the number of comparisons, that is, speed up the script.
I was thinking something like this to start, but afterwards I'm not sure how to proceed:
let name = ['a', 'b', 'c', 'd', 'e', 'f', 'g'];
let a = [...name];
let group1 = [];
let groupSanta = [];
let groupUser = [];
for (var i = 0; i < name.length / 2 - 1; i++) {
let santaClaus = a[Math.floor(Math.random() * a.length)];
a = a.filter(item => item !== santaClaus);
let user = a[Math.floor(Math.random() * a.length)];
a = a.filter(item => item !== user);
group1.push({ santaClaus, user });
}
console.log(a, group1);

You can just randomly sort the array and assign each person to the next one. Then assign the first person to the last one in the array
// Define names
const names = ['a', 'b', 'c', 'd', 'e', 'f', 'g'];
// Function to shuffle array
const shuffle = (arr) => {
for (let i = arr.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[arr[i], arr[j]] = [arr[j], arr[i]];
}
return arr;
}
const randomNames = shuffle(names);
// Match each person with the next one, folding over at the end
const matches = randomNames.map((name, index) => {
return {
santa: name,
receiver: randomNames[index + 1] || randomNames[0],
}
});
console.log(matches);

let players = ['a', 'b', 'c', 'd', 'e', 'f', 'g'];
players = shuffleArray(players)
const matches = players.map((name, index) => {
return {
santa: name,
receiver: players[index + 1] || players[0],
}
});
function shuffleArray(array) {
let currentIndex = array.length, randomIndex
while (currentIndex != 0) {
randomIndex = Math.floor(Math.random() * currentIndex)
currentIndex--
[array[currentIndex], array[randomIndex]] = [array[randomIndex], array[currentIndex]]
}
return array
}
console.log(matches)

Related

frequentVowelCounter(word, count = {}) using recursion

I can do this problem using a for loop, but I'm having trouble using the recursive method.
I want to count how many times each vowel is called and return the most frequent vowel in an array.
If the array does not have a vowel to return is a string.
const vowel = ['a', 'e', 'i', 'o', 'u'];
frequentVowelCounter(word, count = {})
frequentVowelCounter(['cat', 'rain', 'dorm', 'apple', 'sun'])); // 'a'
I tried writing a base case:
if (word.length != vowel)
return ''
I don't think it's right, I'm just stuck.
Why would you want to make this recursive? An iterative approach works well.
You will need to loop over each word, and for each word, its letters. As you loop over each letter, you check to see if it is a vowel. If it is a vowel, you increment the frequency map i.e byLetter.
After you build the letter frequency map, you will need to invert the map into a count map i.e. byCount.
Finally, you can get the keys of the byCount map to find the max count and return the value for the corresponding entry in the map.
Note: You may have more than one letter share the same max occurrence count. This is why the result of the function in an array.
const maxFrequency = (words, letters) => {
const
letterSet = new Set(letters),
byLetter = new Map(),
byCount = new Map();
for (let word of words) {
for (let letter of word.toLowerCase().split('')) {
if (letterSet.has(letter)) {
byLetter.set(letter, (byLetter.get(letter) ?? 0) + 1);
}
}
}
for (let [letter, count] of byLetter) {
const letters = byCount.get(count) ?? new Set();
byCount.set(count, letters.add(letter));
}
return [...byCount.get(Math.max(...byCount.keys()))];
};
const
vowels = ['a', 'e', 'i', 'o', 'u'],
words = ['cat', 'rain', 'dorm', 'apple', 'sun'],
mostFrequent = maxFrequency(words, vowels);
console.log(...mostFrequent); // ['a']
If you want to do this recursively, just join all the words together and iterate through the string starting at index 0. The helper function i.e. __helperFn should never be called directly.
Note: You will need a helper function to set up and perform the recursion.
const __helperFn = (str, letterSet, frequency) => {
if (str.length === 0) {
const max = Math.max(...Object.values(frequency));
return Object.entries(frequency)
.filter(([letter, count]) => count === max)
.map(([letter]) => letter);
}
const letter = str.charAt(0);
if (letterSet.has(letter)) {
frequency[letter] = (frequency[letter] ?? 0) + 1;
}
return __helperFn(str.slice(1), letterSet, frequency);
}
const maxFrequency = (words, letters) =>
__helperFn(words.join('').toLowerCase(), new Set(letters), {});
const
vowels = ['a', 'e', 'i', 'o', 'u'],
words = ['cat', 'rain', 'dorm', 'apple', 'sun'],
mostFrequent = maxFrequency(words, vowels);
console.log(...mostFrequent); // ['a']
This should work:
let w = ["cat", "rain", "dorm", "apple", "sun"];
const vowelCounter = (words) => {
let vowels = {
a: 0,
e: 0,
i: 0,
o: 0,
u: 0,
};
const count = (char) => {
if (vowels.hasOwnProperty(char)) {
vowels[char]++;
}
};
const rekursive = (words, index = 0) => {
if (index === words.length) {
return;
}
let i = 0;
while (true) {
if (i >= words[index].length) {
break;
}
try {
count(words[index].charAt(i));
} catch (error) {}
i++;
}
rekursive(words, ++index);
};
rekursive(words);
console.log(vowels);
};
vowelCounter(w);

How to modify my code to get all non-unique values?

With duplicates, this code worked fine
const findDuplicates = (word) => {
let arr = word.toLowerCase();
let sorted_arr = [...arr].slice().sort();
console.log(sorted_arr);
let results = [];
for (let i = 0; i < sorted_arr.length - 1; i++) {
if (sorted_arr[i + 1] == sorted_arr[i]) {
results.push(sorted_arr[i]);
}
}
return results;
}
console.log(findDuplicates('piccdda123dd'));
Output
[
'1', '2', '3', 'a',
'c', 'c', 'd', 'd',
'd', 'd', 'i', 'p'
]
[ 'c', 'd', 'd', 'd' ]
How to modify if condition to deal with multiple non-unique values?
For something like this we should be gunning for constant time 0(n). Sorting is going to give us O(nlogn) at best and is not required.
function findDuplicates(s) {
const seen = {};
const result = [];
for (let c of s.toLowerCase()) {
if(c in seen) {
result.push(c);
}
seen[c] = true; // could be any value doesn't have to be boolean.
}
return result;
}
console.log(findDuplicates('piccdda123dd'));
result
[ 'c', 'd', 'd', 'd' ]
We just need to iterate over the string once and keep a map object (could use es6 Set) that tells us if we've seen this value before. If we see it again and it's already been seen we can append it to the result array.
If you wanted to see each duplicated character only once try
function findDuplicates2(s) {
const seen = {};
for (let c of s.toLowerCase()) {
const ent = seen[c];
seen[c] = (ent === undefined) ? 1 : ent + 1;
}
return Object.entries(seen).filter(([k,v]) => v > 1).map(([k,v]) => k);
}
console.log(findDuplicates2('piccdda123dd'));
// prints ['c', 'd']
SOLVED.
const findDuplicates = (word) => {
let arr = word.toLowerCase();
let sorted_arr = [...arr].slice().sort();
console.log(sorted_arr);
let results = [];
for (let i = 0; i < sorted_arr.length - 1; i++) {
if (sorted_arr[i + 1] == sorted_arr[i]) {
if (!results.includes(sorted_arr[i]))
results.push(sorted_arr[i]);
}
}
return results;
}
ES6 includes IS REALLY powerfull.

Combination of all possible values from first array?

How do a combine the array of arrays based on the first array1 or basically group by array1.
Below is the four Array, where i have to form objects based on A and then based on B.
var array1=["A","B"];
var array2=["1","2","3", "4"];
var array3=["N","O","P", "Q"];
var array4=["R"];
Below is how i need :
[ {
'take': 'A',
'take2': '1',
'take3': 'N',
'take4': 'R'
}, {
'take': 'A',
'take2': '2',
'take3': 'N',
'take4': 'R'
}, {
'take': 'A',
'take2': '3',
'take3': 'N',
'take4': 'R'
}, {
'take': 'A',
'take2': '4',
'take3': 'N',
'take4': 'R'
}, {
'take': 'A',
'take2': '1',
'take3': 'O',
'take4': 'R'
}]
This is something i have tried, but not sure how can i loop n number of n arrays
var result = array1.reduce( (a, v) =>
[...a, ...array2.map(x=>v+x)],
[]);
here is a keep it simple solution (if you know how many arrays do you have) :
const possibilities = [];
const ar1length = array1.length;
const ar2length = array2.length;
const ar3length = array3.length;
const ar4length = array4.length;
// Not cleanest solution available but it does the job
for ( let i = 0; i < ar1length; i++) {
for (let j = 0; j < ar2length; j++) {
for (let k = 0; k < ar3length; k++) {
for (let l = 0; l < ar4length; l++) {
possibilities.push({
"take": array1[i],
"take1": array2[j],
"take2": array3[k],
"take3": array4[l]
});
}
}
}
}
Oh and if you want an unknown number of arrays, you may add all of these arrays to an array in order to iterate over it I guess
I've written a function for this task a while ago, takes an arbitrary amount of Arrays and non-arrays and computes all possible combinations
var array1 = ["A", "B"];
var array2 = ["1", "2", "3", "4"];
var array3 = ["N", "O", "P", "Q"];
var array4 = ["R"];
console.log(combinations(array1, array2, array3, array4).join("\n"));
function combinations(...columns) {
const state = [], combinations = [state];
let head = null;
for (let column = 0; column < columns.length; ++column) {
let value = columns[column];
if (Array.isArray(value)) {
if (value.length > 1) {
head = {
next: head,
column,
row: 0
};
}
value = value[0];
}
state[column] = value;
}
let todo = head;
while(todo) {
if (++todo.row === columns[todo.column].length) {
todo.row = 0;
state[todo.column] = columns[todo.column][todo.row];
todo = todo.next;
} else {
state[todo.column] = columns[todo.column][todo.row];
combinations.push(state.slice());
todo = head;
}
}
return combinations;
}
.as-console-wrapper{top:0;max-height:100%!important}
Here's a recursive approach which works for every number of arrays, you just have to call combine(array1, array2, ..., arrayn):
var array1=["A","B"];
var array2=["1","2","3", "4"];
var array3=["N","O","P", "Q"];
var array4=["R"];
function combine(arr1, ...arr2) {
if(arr2.length === 0) return Array.from(arr1, (x) => x.reduce((obj, y, i) => (obj[`take${i}`] = y, obj), {}));
return combine(arr1.flatMap(d => arr2[0].map(v => {
return [...Object.values(d), ...Object.values(v)]
})), ...arr2.slice(1));
}
console.log(combine(array1, array2, array3, array4));

Convert a single dimensional array into a multidimensional array

In Javascript, how to convert a single dimensional array into a multidimensional array of unspecified depth or length.
Example:
let input = ['a','b','b','b','a','a','b','b','b','c','c','a','a','b','b'];
const makeMatrix = () => {}
let output = makeMatrix(input);
// output: ['a',['b','b','b'],'a','a',['b','b','b',['c','c']],'a','a',['b','b']]
What should the makeMatrix function look like to accomplish this task? Assume that values always move in a linear forward direction, but might possibly cut backward. So a always leads to b. An a would never hop to c. But c might drop back down to a.
This is to try to convert heading elements into a table of contents. Making a simple single tier toc is easy, but making a multi tiered one is wracking my brain. I have looked through a number of solutions, but have not seen anything that solves this particular problem.
You could take a level variable and a levels array for pushing unknown elements.
var input = ['a', 'b', 'b', 'b', 'a', 'a', 'b', 'b', 'b', 'c', 'c', 'a', 'a', 'b', 'b'],
levels = [[]],
level = 0,
result;
input.forEach(v => {
var l = level;
do {
if (levels[l][0] === v) {
level = l;
levels[level].push(v);
return;
}
} while (l--)
levels[level].push(levels[level + 1] = [v]);
level++;
});
result = levels[0][0];
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
The dumb eval solution I had in mind, if it's what you wanted it can be made neat...
function toMulti(arr) {
let str = "[";
let level = 1;
const charLevels = { a: 1, b: 2, c: 3 };
arr.forEach(char => {
const charLevel = charLevels[char];
if (level < charLevel) {
for (let i = 0; i < charLevel - level; i++) {
str += "[";
}
}
if (level > charLevel) {
for (let i = 0; i < level - charLevel; i++) {
str += "],";
}
}
level = charLevel;
str += `'${char}',`;
});
for (let i = 0; i < level; i++) {
str += "]";
}
return eval(str);
}
Alternative version, using JSON building/parsing:
const input = ['a', 'b', 'b', 'b', 'a', 'a', 'b', 'b', 'b', 'c', 'c', 'a', 'a', 'b', 'b'];
const result = JSON.parse(Object.entries(input).reduce((json, [key, val]) => {
const jsonVal = JSON.stringify(val);
const diff = key > 0 ? val.charCodeAt(0) - input[key - 1].charCodeAt(0) : 0;
if (diff > 0) {
json += ',['.repeat(diff) + jsonVal;
} else if (diff < 0) {
json += ']'.repeat(-diff) + ',' + jsonVal;
} else {
json += (key > 0 ? ',' : '') + jsonVal;
}
return json;
}, '[') + ']'.repeat(input.slice(-1)[0].charCodeAt(0) - input[0].charCodeAt(0) + 1));
console.log(result);
This basically builds a JSON string using Array.reduce on the input array, adding each item and comparing key codes to include the right amount of opening/closing brackets in the process.

there is an string array A , and an string array B . I want to delete elements in A which are not in B

I think i messed somewhere, Here is my code.
var flag;
for (i = 0; i < A.length; i++)
{
flag = 0;
for (j = 0; j < B.length; j++)
{
if (A[i].indexOf(B[j]) != -1)
{
flag = 1;
}
}
if (flag == 0)
{
A.splice(i, 1);
}
}
It gives output not as per my need
Someone please Help me out
I would do the job like this;
//returns intersection of multiple arrays
Array.prototype.intersect = function(...a) {
return [this,...a].reduce((p,c) => p.filter(e => c.includes(e)));
};
var a = [0,1,2,3,4,5],
b = [4,5,6,7,8,9];
a = a.intersect(b);
console.log(a);
You could use a function which generates first an object with all characters as properties and take it as hashtable for the filtering of array1.
function deleteSome(array1, array2) {
var o = Object.create(null);
array2.forEach(function (a) {
o[a] = true;
});
return array1.filter(function (a) {
return this[a];
}, o);
}
var a = 'abcdefgh'.split(''),
b = 'banana'.split('');
console.log(deleteSome(a,b));
Technically, array "a" should have only elements which are present in array "b".
var a = [1,2,3,4];
var b = [4,5,6];
var new_a = [];
a.map(function(v,i,a){
if(b.indexOf(v) !== -1){
new_a.push(v);
}
});
console.log(new_a); //[4]
By this way i can filter as many arrays as you want.
var a = ['A', 'A', 'R', 'S', 'M', 'D', 'E']
var b = ['C', 'X', 'D', 'F']
//you can add as many arrays as you want
/*var c = ['O', 'P', 'D', 'Q']
var d = ['R', 'D', 'D', 'Z']*/
var arrays = [a,b, /*c , d */];
var result = arrays.shift().filter(function(v) {
return arrays.every(function(a) {
return a.indexOf(v) !== -1;
});
});
console.log(JSON.stringify(result));

Categories

Resources