Issue with random sampling - javascript

I'm trying to pick a random element from an array without picking the same element twice.
After a few iterations it is undefined.
It works with return(pick_random_letter()). But WHY?
Why does pick_random_letter() not go back and return later?
let letters = [
{ name: 'A' },
{ name: 'B' },
{ name: 'C' },
{ name: 'D' },
]
let used_letters = [];
function pick_random_letter(){
let random = Math.floor(Math.random() * letters.length);
let next_letter = letters[random].name;
if (used_letters.includes(next_letter)){
if (used_letters.length == letters.length) used_letters = [];
pick_random_letter(); // return(pick_random_letter()) works
}else{
used_letters.push(next_letter);
return next_letter;
}
}
for (let i = 0; i < 4; i++){
let random_letter = pick_random_letter();
console.log(random_letter);
console.log(used_letters);
}
Working example:
let letters = [
{ name: 'A' },
{ name: 'B' },
{ name: 'C' },
{ name: 'D' },
]
let used_letters = [];
function pick_random_letter(){
let random = Math.floor(Math.random() * letters.length);
let next_letter = letters[random].name;
if (used_letters.includes(next_letter)){
if (used_letters.length == letters.length) used_letters = [];
return(pick_random_letter());
}else{
used_letters.push(next_letter);
return next_letter;
}
}
for (let i = 0; i < 4; i++){
let random_letter = pick_random_letter();
console.log(random_letter);
console.log(used_letters);
}
Thank you

I think you would be better off using a shuffling approach. Create a shuffled list of your letters and pick from these. This avoids any need to check if we've returned a letter previously.
We'd start by creating a shuffledLetters array, then each time we call pick_random_letter() we return the next value.
The shuffle() function here is a basic Fisher–Yates / Knuth shuffle.
function 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;
}
let letters = [
{ name: 'A' },
{ name: 'B' },
{ name: 'C' },
{ name: 'D' },
]
// Create a shuffled copy of the letters array
let shuffledLetters = shuffle([...letters]);
function pick_random_letter() {
return shuffledLetters.pop();
}
for (let i = 0; i < letters.length; i++) {
let random_letter = pick_random_letter();
console.log(random_letter);
}
.as-console-wrapper { max-height: 100% !important; }

Related

Pass data to a function untill array length

So I have a function that takes multiple arrays as arguments but the data I have is already in an array (say mainArr). so mainArr is an array that contains multiple array items that I need to put them in the function arguments.
here's the code:
// NOTE: YOU DON'T NEED TO UNDERSTAND THIS FUNCTION
function getCombinations(...args) {
let r = [], max = args.length - 1;
function helper(arr, i) {
for (let j = 0; j < args[i].length; j++) {
let a = arr.slice(0); // clone arr
a.push(args[i][j].value);
if (i === max) {
r.push(a);
} else {
helper(a, i + 1);
}
}
}
helper([], 0);
return r;
}
I want something like this:
const mainArr = [
{
values: [
{ value: 1 },
{ value: 2 }
]
}, {
values: [
{ value: 2 },
{ value: 1 }
]
}
]
getCombinations(mainArr[0].values, mainArr[1].values)
actually, I want to get the combinations of arrays and the function I made to get the combination, takes multiple arrays as arguments. for example, if I want to get the combination of two arrays, it takes two arrays as two different arguments. but the data I have is inside another array so I just want to strip the arrays from that single array.
Make the parameter of function getCombinations an array.
function getCombinations(args) {
let r = [], max = args.length - 1;
function helper(arr, i) {
for (let j = 0; j < args[i].length; j++) {
let a = arr.slice(0); // clone arr
a.push(args[i][j].value);
if (i === max) {
r.push(a);
} else {
helper(a, i + 1);
}
}
}
helper([], 0);
return r;
}
const mainArr = [
{
values: [
{ value: 1 },
{ value: 2 }
]
}, {
values: [
{ value: 2 },
{ value: 1 }
]
}
]
const x = getCombinations([mainArr[0].values, mainArr[1].values]);
console.log(x);
const list = mainArr.map(node => node.values);
const y = getCombinations(list);
console.log(y);

How to get all combinations of some arrays in Javascript

I see there's too many questions like this , but I did not find answer for my specific case
I need all the possible combinations for my input arrays
example
if we assume the input is [1,2];
the output should be : ["11","12","21","22"]
after research , I reached to this code
function perm(xs) {
let ret = [];
for (let i = 0; i < xs.length; i = i + 1) {
let rest = perm(xs.slice(0, i).concat(xs.slice(i + 1)));
if(!rest.length) {
ret.push([xs[i]])
} else {
for(let j = 0; j < rest.length; j = j + 1) {
ret.push([xs[i]].concat(rest[j]).join(""))
}
}
}
return ret;
}
it finds most of the combinations , but not all
the above code return only ["12","21"] for input [1,2]
however all the possible combinations should be > ["11","12","21","22"]
another example for input [1,2,3] , should have this output
["111","112","121","211","113","131","311","123","321","312","213","132" , "223" , "331" , "313" , "232" .. and so on
If the length of the generated strings should be equal to the size of the input array, then this recursive generator could be used:
function combiWithRepetitions(chars) {
function * recur(len, str) {
if (len) for (let chr of chars) yield * recur(len - 1, str + chr);
else yield str;
}
return [...recur(chars.length, "")];
}
let result = combiWithRepetitions([1, 2]);
console.log(result);
You are looking for permutation with repetitions that are of the same length as the input array. This code (adapted from: https://stackoverflow.com/a/30340475/9560885) gives you the output you want:
function perm(arr) {
var holdingArr = [];
var recursiveABC = function(singleSolution) {
if (singleSolution.length == arr.length) {
holdingArr.push(singleSolution);
return;
}
for (var i=0; i < arr.length; i++) {
recursiveABC(singleSolution.concat([arr[i]]).toString());
}
};
recursiveABC([]);
return holdingArr;
};
Example:
> perm([1,2])
[ '11', '12', '21', '22' ]
Another example:
> perm([1,2])
[ '111',
'112',
'113',
'121',
'122',
'123',
'131',
'132',
'133',
'211',
'212',
'213',
'221',
'222',
'223',
'231',
'232',
'233',
'311',
'312',
'313',
'321',
'322',
'323',
'331',
'332',
'333' ]
You could build a cartesian product of the wanted length with a generato function.
function getP(array, length) {
function* p(right = []) {
if (right.length === length) {
yield right.join('');
return;
}
for (const v of array) yield* p([...right, v])
}
return p();
}
console.log([...getP([1, 2, 3], 3)]);
Here's a one liner:
const f = (xs, l=xs.length) => l ? xs.flatMap(x => f(xs, l-1).flatMap(y => x + y)) : [''];
console.log(f([1, 2, 3]));

javascript leave max 3 occurrences of the same obj in an array

If I populate an array of pairs (inside a get json) like so:
var arrItems = [];
for (let y=0; y<50 ;y++){
var titl = data.item[y].name;
var img = data.item[y].image[0].url;
arrItems.push({t:titl,i:img});
}
How can I then filter it to leave only 3 pairs where value is the same?
Example:
arrItems = [
{t:one,i:square.jpg},
{t:two,i:square.jpg},
{t:three,i:square.jpg},
{t:four,i:square.jpg},
{t:five,i:triangle.jpg}
];
Becomes
arrItems = [
{t:one,i:square.jpg},
{t:two,i:square.jpg},
{t:three,i:square.jpg},
{t:five,i:triangle.jpg}
];
Both JavaScript or jQuery are OK.
You could take a hash table and count the occurences of the wanted property and filter with a max value.
var items = [{ t: 'one', i: 'square.jpg' }, { t: 'two', i: 'square.jpg' }, { t: 'three', i: 'square.jpg' }, { t: 'four', i: 'square.jpg' }, { t: 'five', i: 'triangle.jpg' }],
count = {},
result = items.filter(({ i }) => {
count[i] = (count[i] || 0) + 1;
return count[i] <= 3;
});
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
There might be some more efficient ways to write it, but I would think the easiest to understand is the straightforward iteration with counting:
var counts = {};
for (var i = 0; i < arrItems.length; i++) {
var item = arrItems[i];
var count = counts[item.i] || 0;
if (count >= 3) {
arrItems.splice(i, 1);
i--;
} else {
counts[item.i] = ++count;
}
}
You can use reduce function
var arrItems = [
{t:'one',i:'square.jpg'},
{t:'two',i:'square.jpg'},
{t:'three',i:'square.jpg'},
{t:'four',i:'square.jpg'},
{t:'five',i:'triangle.jpg'}
];
var output = arrItems.reduce((acc, {t,i})=>{
acc['mem'][i] = (acc['mem'][i] || 0) + 1;
acc['mem'][i] <= 3 ? acc['output'].push({t, i}) : '';
return acc;
}, {'mem':{}, 'output':[]});
console.log(output);

Group array by nested dependent array element

I have an array store Task information. Each task has also an array of taskId that it is depending on.
Input
let inputArr = [
{
id: 1,
dependOnTasks: [2, 3]
},
{
id: 2,
dependOnTasks: [3]
},
{
id: 3,
dependOnTasks: []
},
{
id: 4,
dependOnTasks: [5]
},
{
id: 5,
dependOnTasks: []
},
{
id: 6,
dependOnTasks: [5]
}
]
The expected output is grouping all the depending task into one array for displaying into the UI.
Output
[
[
{
id: 1,
dependOnTasks: [2, 3]
},
{
id: 2,
dependOnTasks: [3]
},
{
id: 3,
dependOnTasks: []
}
],
[
{
id: 4,
dependOnTasks: [5]
},
{
id: 5,
dependOnTasks: []
},
{
id: 6,
dependOnTasks: [5]
}
]
]
I have made a function to do it but seem I was thinking wrong by hard-coded it. Hope someone can help me archive it right way using pure JavaScript/TypeScript or Underscore since we have already used in the project.
Noted: TaskId will be random string like "5878465507b36e1f9c4c46fe"
// will contain the groups (results).
var result = [];
// will serve as a holder of the already treated indexes of inputArr.
var indexCache = [];
// insert obj into a group, insert its dependencies and the object that depend on it as well.
function insertWithDependencies(obj, group){
// insert this obj into this group
group.push(obj);
// First: look for the objects it depends on
obj.dependOnTasks.forEach(function(id){
for(var i = 0; i < inputArr.length; i++){
// if the object in this index is already treated, then ignore it
if(indexCache.indexOf(i) != -1) continue;
// if this object is a dependency of obj then insert it with its own dependencies.
if(inputArr[i].id == id){
var o = inputArr[i];
indexCache.push(i); // cache this i as well
insertWithDependencies(o, group);
}
}
});
// Then: look for the objects that depends on it
for(var i = 0; i < inputArr.length; i++){
// if the object in this index is already treated, then ignore it
if(indexCache.indexOf(i) != -1) continue;
// if this object depends on obj then insert it with ...
if(inputArr[i].dependOnTasks.indexOf(obj.id) != -1){
var o = inputArr[i];
indexCache.push(i); // cache i
insertWithDependencies(o, group);
}
}
};
// while there is element in the inputArr that haven't been treated yet
while(inputArr.length != indexCache.length){
// the group that will hold the depending tasks all together
var group = [];
// look for the first untreated object in inputArr
var i;
for(i = 0; i < inputArr.length; i++)
if(indexCache.indexOf(i) == -1)
break;
var obj = inputArr[i];
// cache its index
indexCache.push(i)
// insert it along its dependencies
insertWithDependencies(obj, group);
// push the group into the result array
result.push(group);
}
ANOTHER WAY:
Here is an optimised way to do it, but the data inside inputArr will be lost afterwards. It won't use the indexCache to see if an index is already treated or not but instead it will make all treated items null in inputArr. So if you don't care or won't use inputArr afterwards, use this instead:
var result = [];
function insertWithDependencies(obj, group){
group.push(obj);
obj.dependOnTasks.forEach(function(id){
for(var i = 0; i < inputArr.length; i++){
if(!inputArr[i]) continue;
if(inputArr[i].id == id){
var o = inputArr[i];
inputArr[i] = null;
insertWithDependencies(o, group);
}
}
});
for(var i = 0; i < inputArr.length; i++){
if(!inputArr[i]) continue;
if(inputArr[i].dependOnTasks.indexOf(obj.id) != -1){
var o = inputArr[i];
inputArr[i] = null;
insertWithDependencies(o, group);
}
}
};
function findNotNull(){
for(var i = 0; i < inputArr.length; i++)
if(inputArr[i]) return i;
return -1;
}
var index;
while((index = findNotNull()) != -1){
var group = [];
var obj = inputArr[index];
inputArr[index] = null;
insertWithDependencies(obj, group);
result.push(group);
}
console.log(result);
The solution is straightforward,
Initialize empty group list
For each task find if there is a group in the group list with id or id of any dependent tasks
If not add a new group with task id and dependent tasks
var input = [
{ id: 1, dependOnTasks: [2, 3] },
{ id: 2, dependOnTasks: [3] },
{ id: 3, dependOnTasks: [] },
{ id: 4, dependOnTasks: [5] },
{ id: 5, dependOnTasks: [] },
{ id: 6, dependOnTasks: [5] }
];
var groups = [];
for (var i = 0; i < input.length; i++){
var group = findGroup(groups,input[i]);
if (!group){
group = {ids : []};
group.ids.push(input[i].id);
groups.push(group);
}
if (group.ids.indexOf(input[i].id) === -1){
group.ids.push(input[i].id);
}
for (var j = 0; j < input[i].dependOnTasks.length; j++){
if (group.ids.indexOf(input[i].dependOnTasks[j]) === -1){
group.ids.push(input[i].dependOnTasks[j]);
}
}
}
document.write(groups[0].ids + '</br>');
document.write(groups[1].ids + '</br>');
function findGroup(groups,task){
for (var i = 0; i < groups.length; i++){
var group = groups[i];
if (group.ids.indexOf(task.id) !== -1){
return group;
}
for (var j = 0; j < task.dependOnTasks.length; j++){
if (group.ids.indexOf(task.dependOnTasks[j]) !== -1){
return group;
}
}
}
return null;
}
If you don't care about the order of the tasks in the same group. Using union and find to implement a disjoint set might be an option.
Util data structure:
function UnionFind(n) {
this.parent = [...Array(n+1).keys()]
}
UnionFind.prototype.find = function(x) {
if (this.parent[x] === x) {
return x
}
const ret = this.find(this.parent[x])
this.parent[x] = ret
return ret
}
UnionFind.prototype.union = function(x, y) {
let x_rep = this.find(x)
let y_rep = this.find(y)
if (x_rep !== y_rep) {
this.parent[x_rep] = y_rep
}
}
Dumb data source:
let inputArr = [
{
id: 1,
dependOnTasks: [2, 3]
},
{
id: 2,
dependOnTasks: [3]
},
{
id: 3,
dependOnTasks: []
},
{
id: 4,
dependOnTasks: [5]
},
{
id: 5,
dependOnTasks: []
},
{
id: 6,
dependOnTasks: [5]
}
]
Driver program:
let len = inputArr.length
let uf = new UnionFind(len)
// iterate through all tasks to group them
inputArr.forEach(entry => {
entry.dependOnTasks.forEach(depsId => {
uf.union(entry.id, depsId)
})
})
// reiterate to retrieve each task's group and group them using a hash table
let groups = {}
inputArr.forEach(entry => {
const groupId = uf.find(entry.id)
if (!groups.hasOwnProperty(groupId)) {
groups[groupId] = [entry]
return
}
groups[groupId].push(entry)
})
let result = Object.keys(groups).map(groupId => groups[groupId])
console.log(JSON.stringify(result, null, 2))
note: if id is random string in your case, simply change this.parent to hash map, and if you care about the order(as there're dependency trees), consider using topological sort instead.
You can try with my code
var inputArr = [
{
id: 1,
dependOnTasks: [2, 3]
},
{
id: 2,
dependOnTasks: [3]
},
{
id: 3,
dependOnTasks: []
},
{
id: 4,
dependOnTasks: [5]
},
{
id: 5,
dependOnTasks: []
},
{
id: 6,
dependOnTasks: [5]
}
]
// make matrix graph
var map = {};
for (var i = 0; i < inputArr.length; i++) {
var task = inputArr[i];
map[task.id] = map[task.id] || {};
for (var j = 0; j < task.dependOnTasks.length; j++) {
var dependId = task.dependOnTasks[j];
map[dependId] = map[dependId] || {};
map[task.id][dependId] = true;
map[dependId][task.id] = true;
}
}
var groupTasks = [];
for (var key in map) {
var group = groupTasks.filter(function(e) {
return e.indexOf(key) >= 0;
})[0]
if (!group) {
group = [key];
groupTasks.push(group);
}
for (var dependKey in map[key]) {
if (group.indexOf(dependKey) == -1) {
group.push(dependKey);
}
}
}
var result = groupTasks.map(function(group) {
var tasks = [];
group.forEach(function(id) {
var task = inputArr.filter(function(e) { return e.id == id })[0];
tasks.push(task);
});
return tasks;
})
console.log(JSON.stringify(result, null, 4));

Splicing Array Based on For Loop Iteration

I am trying to take an array ('selectedWorkshops'), and move objects in it to 'registeredWorkshops'. Then, I also want to remove those objects from both 'selectedWorkshops' and another array simply called 'workshops'.
See my codepen here: http://codepen.io/trueScript/pen/wBVqNN
Arrays:
var workshops = [
{
name: 'apples',
WorkshopId: '19'
},
{
name: 'oranges',
WorkshopId: '3b'
},
{
name: 'pears',
WorkshopId: 'x6'
},
{
name: 'pineapples',
WorkshopId: '55'
},
{
name: 'watermelons',
WorkshopId: '8v'
}
];
var selectedWorkshops = [
{
name: 'oranges',
WorkshopId: '3b'
},
{
name: 'watermelons',
WorkshopId: '8v'
},
{
name: 'pears',
WorkshopId: 'x6'
}
];
var registeredWorkshops = [];
Function that is supposed to move workshops to 'registeredWorkshops' and remove them from 'selectedWorkshops' and 'workshops':
flipWorkshops = function(){
var numberOfWorkshops = selectedWorkshops.length;
var X = 1;
for(var i = 0; i < numberOfWorkshops; i++ ){
registeredWorkshops.push(selectedWorkshops[i]);
for(var j = 0, arrayLength = workshops.length; j < arrayLength; j++) {
var selectedWorkshop = selectedWorkshops[i];
var originalWorkshop = workshops[j];
if(selectedWorkshop == originalWorkshop){
var matchingWorkshop = j;
workshops = workshops.splice(j, 1);
selectedWorkshops = selectedWorkshops.splice(i, 1);
}
}
}
};
flipWorkshops();
Why aren't the objects being properly spliced out of the 'workshop' and 'selectedWorkshops' arrays like they should be? What am I doing wrong?
"The splice() method adds/removes items to/from an array, and returns the removed item(s)." http://w3schools.com/jsref/jsref_splice.asp So, basically, you're reducing workshops to a single object, the removed array element.
Instead, change:
workshops = workshops.splice(j, 1);
selectedWorkshops = selectedWorkshops.splice(i, 1);
...to:
workshops.splice(j, 1);
selectedWorkshops.splice(i, 1);
JSFiddle
Maybe this is what you want:
var registeredWorkshops = [];
var flipWorkshops = function(){
var numberOfWorkshops = selectedWorkshops.length;
var X = 1;
for(var i = numberOfWorkshops - 1; i >= 0; i-- ){
registeredWorkshops.push(selectedWorkshops[i]);
var selectedWorkshop = selectedWorkshops[i];
for(var j = workshops.length - 1; j >= 0; j--) {
var originalWorkshop = workshops[j];
if(selectedWorkshop.name == originalWorkshop.name &&
selectedWorkshop.WorkshopId == originalWorkshop.WorkshopId){
var matchingWorkshop = j;
workshops.splice(j, 1);
selectedWorkshops.splice(i, 1);
}
}
}
};
flipWorkshops();
Fixed the splice, reversed the loop, as suggested by jwatts1980, moved the selectedWorkshop var out of the loop, and compared each object item, rather than comparing objects, since objects don't compare.
JSFiddle Tweaked
JSFiddle Object Compare Example

Categories

Resources