Related
I'm trying to write a function that has the arguments of the array to sample arr and the number of samples, size (which is sqaured) and randomly samples from the original array:
arr = [1,2,3,4]
single_number = (x) => {
return x[Math.floor(Math.random()*x.length)];
}
randomize = (arr, size) => {
return Array(size*size).fill().map(x => single_number(arr))
}
randomize(arr, 5)
I want to add the additional requirements to my randomize function:
no number shows up twice in a row
make sure every sizeth item is not the same as the one before it
For example
randomize([1,2,3,4], 2)
[2,4,3,2,4,1,1,2,2,1,4,1,1,1,3,1,1,4,4,1,3,3,2,2,3]
CASE (1)
[
2,4,3,2,4,
2,1,
2,2, // illegal!
1,4,
1,1,1, // illegal!
3,
1,1, // illegal!
4,4, // illegal!
1,
3,3, // illegal!
2,2, // illegal!
3
]
CASE (2)
[
2,4,3,2,4, [0] === 2
2,1,2,2,1, [5] === 2 // illegal!
4,1,1,1,3,
1,1,4,4,1,
3,3,2,2,3
]
I'm trying to use functional programming and avoid a for loop if possible since I think I can do this with a nested for loop?
Well, this isn't as pretty as one would hope, but I think it accomplishes the objective: Iterate size^2 times and choose random elements from the input, taking care to exclude the last value and last nth value chosen...
const randomize = (array, size) => {
const rand = () => Math.floor(Math.random() * array.length);
const randExcept = exclude => {
let v = array[rand()];
while (exclude.includes(v)) v = array[rand()];
return v;
}
const indexes = Array.from(Array(size*size).keys());
let lastV = null, nthV = null;
return indexes.map(i => {
let exclude = nthV!==null && i%size===1 ? [lastV, nthV] : [lastV];
let v = randExcept(exclude);
lastV = v;
if (i%size===1) nthV = v;
return v;
})
}
console.log( JSON.stringify(randomize([1,2,3,4], 2)) )
This defines nth values by the count into the array, so for size===2, the constraint is that every second element (indexes 1,3,5...) can't be equal to the prior second element.
I'd probably do something like this:
const values = [1,2,3,4]
function randomize(values, size) {
let prev;
let prevNth;
return Array(size*size).fill().map( randomNumber );
function randomNumber(_,i) {
let value, ok;
do {
value = values[ Math.floor( Math.random() * values.length ) ];
ok = value != prev;
if ( i % size === 0) {
ok = ok && value != prevNth;
prevNth = value;
}
prev = value;
} while (!ok);
return value;
}
}
arr = randomize(values, 5)
console.log(JSON.stringify(arr));
Or this, using a generator to generate the appropriately sized stream of randomness:
const values = [1,2,3,4];
const arr1 = Array.from( randomValues(5,values) );
console.log(JSON.stringify(arr1));
function *randomValues(n, values) {
const limit = n*n;
let prev, prevNth;
for ( let i = 0 ; i < limit ; ++i ) {
const isNthValue = i % n === 0;
const value = randomValue( values, prev, isNthValue ? prevNth : undefined );
yield value;
prev = value;
prevNth = isNthValue ? value : prevNth;
}
}
function randomValue(values, test1, test2 ) {
let value;
do {
value = values[ Math.floor( Math.random() * values.length ) ];
} while (value === test1 || value === test2 );
return value;
}
I'm doing some exercises. the question is to find the first none-repeated character in a string.
My idea is: turn string to array. assign array[0] to a new variable, and remove this array[0] from array. Check if this new array contain this variable, if not, return this variable. Else, use filter to remove same value elements and get a new array. repeat the process. the code as following.
const NoneReChar = (str) => {
let tempArr = str.split('');
let start = tempArr[0];
while (true) {
tempArr.shift();
if (!tempArr.includes(start)) {
return start;
} else {
tempArr.filter(char => char !== start);
start = tempArr[0];
}
}
}
console.log(NoneReChar("aaaabbbeccc"))
I was expect output 'e', but I keep getting 'a'...where are the mistakes I made here?
The Array.filter() method doesn't mutate the original array. You need to assign the result of filter to tempArr:
tempArr = tempArr.filter(char => char !== start);
Example:
const NoneReChar = (str) => {
let tempArr = str.split('');
let start = tempArr[0];
while (true) {
tempArr.shift();
if (!tempArr.includes(start)) {
return start;
} else {
tempArr = tempArr.filter(char => char !== start);
start = tempArr[0];
}
}
}
console.log(NoneReChar("aaaabbbeccc"))
However, you don't handle the not found case. To handle it instead of true, the while clause should stop when the array is empty:
const NoneReChar = (str) => {
let tempArr = str.split('');
let start = tempArr[0];
while (tempArr.length) {
tempArr.shift();
if (!tempArr.includes(start)) {
return start;
} else {
tempArr = tempArr.filter(char => char !== start);
start = tempArr[0];
}
}
return null;
}
console.log(NoneReChar("aabbcc"))
Another option is comparing the length of the array before and after filtering. If the length is the same, the item was not repeated:
const NoneReChar = (str) => {
let tempArr = str.split('');
while (tempArr.length) {
const [start, ...rest] = tempArr; // take the 1st item and the rest
tempArr = rest.filter(char => char !== start); // filter out start
if(tempArr.length === rest.length) { // check current and previous arrays, and if the length still matches, start didn't appear again
return start;
}
}
return null;
}
console.log(NoneReChar("aabzbcc"))
const NoneReChar = (str) => {
const tempArr = str.split('');
let result = 'Not Found';
for (let index = 0; index < tempArr.length; index++) {
const firstIndex = tempArr.indexOf(tempArr[index]);
const lastIndex = tempArr.lastIndexOf(tempArr[index]);
if (firstIndex === lastIndex) {
result = tempArr[index];
break;
}
}
return result;
}
console.log(NoneReChar("aaaabbbeccc"));
I'm trying to create an algorithm to find duplicate values in a list and return their respective indexes, but the script only returns the correct value, when I have 2 equal elements:
array = [1,2,0,5,0]
result -> (2) [2,4]
Like the example below:
array = [0,0,2,7,0];
result -> (6) [0, 1, 0, 1, 0, 4]
The expected result would be [0,1,4]
Current code:
const numbers = [1,2,0,5,0];
const checkATie = avgList => {
let averages, tie, n_loop, currentAverage;
averages = [... avgList];
tie = [];
n_loop = 0;
for(let n = 0; n <= averages.length; n++) {
currentAverage = parseInt(averages.shift());
n_loop++
for(let avg of averages) {
if(avg === currentAverage) {
tie.push(numbers.indexOf(avg),numbers.indexOf(avg,n_loop))
};
};
};
return tie;
}
console.log(checkATie(numbers));
if possible I would like to know some way to make this code more concise and simple
Use a Set
return [...new Set(tie)]
const numbers1 = [1,2,0,5,0];
const numbers2 = [0,0,2,7,0];
const checkATie = avgList => {
let averages, tie, n_loop, currentAverage;
averages = [... avgList];
tie = [];
n_loop = 0;
for(let n = 0; n <= averages.length; n++) {
currentAverage = parseInt(averages.shift());
n_loop++
for(let avg of averages) {
if(avg === currentAverage) {
tie.push(avgList.indexOf(avg),avgList.indexOf(avg,n_loop))
};
};
};
return [...new Set(tie)]
}
console.log(checkATie(numbers1));
console.log(checkATie(numbers2));
I hope this help you.you can use foreach function to check each item of array
var array = [0,0,2,7,0];
var result = [] ;
array.forEach((item , index)=>{
if(array.findIndex((el , i )=> item === el && index !== i ) > -1 ){
result.push(index)
}
})
console.log(result);
//duplicate entries as an object
checkDuplicateEntries = (array) => {
const duplicates = {};
for (let i = 0; i < array.length; i++) {
if (duplicates.hasOwnProperty(array[i])) {
duplicates[array[i]].push(i);
} else if (array.lastIndexOf(array[i]) !== i) {
duplicates[array[i]] = [i];
}
}
console.log(duplicates);
}
checkDuplicateEntries([1,2,0,5,0]);
// hope this will help
Create a lookup object with value and their indexes and then filter all the values which occurred more than once and then merge all indexes and generate a new array.
const array = [1, 2, 0, 5, 0, 1, 0, 2],
result = Object.values(array.reduce((r, v, i) => {
r[v] = r[v] || [];
r[v].push(i);
return r;
}, {}))
.filter((indexes) => indexes.length > 1)
.flatMap(x => x);
console.log(result);
I am trying to find values that commonly appear next to each other in an array.
E.G. given the array:
["dog","cat","goat","dog","cat","elephant","dog","cat","pig","seal","dog","cat","pig","monkey"]
it should return something similar to:
[[["dog","cat"],4],[["cat","pig"],2],[["dog","cat","pig"],2]]
Here is some better data: https://pastebin.com/UG4iswrZ
Help would be greatly appreciated. Here is my current failed attempt at doing something similar:
function findAssociations(words){
var temp = [],tempStore = [],store = [],found = false;
//loop through the words counting occurrances of words together with a window of 5
for(var i = 0;i<words.length-1;i++){
if(i % 5 == 0){
//on every fith element, loop through store attempting to add combinations of words stored in tempStore
for(var j = 0;j<5;j++){
temp = []
//create the current combination
for(var k = 0;k<j;k++){
temp.push(tempStore[k]);
}
//find if element is already stored, if it is, increment the occurrence counter
for(var k = 0;k<store.length;k++){
if(store[k][0]===temp){
found = true;
store[k][1] = store[k][1]+1;
}
}
//if it isn't add it
if(found == false){
store.push([temp,1]);
}
found == false;
}
tempStore = [];
} else {
//add word to tempStore if it i isnt a multiple of 5
tempStore.push(words[i]);
}
}
}
This script is doesn't remove combinations that appear once,it doesn't sort the output by occurrences, nor does it work. It is just an outline of how a possible solution might work (as suggested by benvc).
Here is a generic solution working with multiple group sizes.
You specify a range of group sizes, for example [2,4] for groups of 2 to 4 elements and a minimum number of occurrences.
The function then generates all groups of neighbours of the given sizes, sorts each group and counts the duplicates. The sorting step can be removed is the order in the groups matters.
The duplicates are counted by creating a dictionary whose keys are the group elements sorted and jointed with a special marker. The values in the dictionary are the counts.
It then returns the groups sorted by occurences and then by group size.
const data = ["dog","cat","goat","dog","cat","elephant","dog","cat","pig","seal","dog","cat","pig","monkey"];
function findSimilarNeighbors(groupSizeRange, minOccurences, data) {
const getNeighbors = (size, arr) => arr.reduce((acc, x) => {
acc.push([]);
for (let i = 0; i < size; ++ i) {
const idx = acc.length - i - 1;
(acc[idx] || []).push(x);
}
return acc;
}, []).filter(x => x.length === size);
const groups = [];
for (let groupSize = groupSizeRange[0]; groupSize <= groupSizeRange[1]; ++groupSize) {
groups.push(...getNeighbors(groupSize, data));
}
const groupName = group => group.sort().join('###'); // use a separator that won't occur in the strings
const groupsInfo = groups.reduce((acc, group) => {
const name = groupName(group);
acc[name] = acc[name] || {};
acc[name] = { group, count: (acc[name].count || 0) + 1 };
return acc;
}, {});
return Object.values(groupsInfo)
.filter(group => group.count >= minOccurences)
.sort((a, b) => {
const countDiff = b.count - a.count;
return countDiff ? countDiff : b.group.length - a.group.length;
})
.map(({ group, count }) => [group, count]);
};
console.log(findSimilarNeighbors([2, 4], 2, data));
console.log(findSimilarNeighbors([4, 4], 2, data));
Here is what I came up with. It only finds pairs, but you could modify it to find sets of 3, 4, etc, based on what you % by
const animals = ['dog','cat','goat','dog','cat','elephant','dog','cat','pig','seal','dog','cat','pig','monkey'];
let pairs = ',';
animals.forEach((animal, i) => {
let separator = ',';
if (i % 2 === 0) {
separator = ';'
}
pairs += animal + separator;
});
const evenPairs = pairs.split(',');
const oddPairs = pairs.split(';');
const allPairs = evenPairs.concat(oddPairs).map(pair => pair.replace(/[;,]/, ' '));
let result = {}
allPairs.forEach(pair => {
if (pair.length) {
if (result[pair] === undefined) {
result[pair] = 1;
} else {
result[pair]++;
}
}
});
results in:
dog: 1
cat elephant: 1
cat goat: 1
cat pig: 2
dog cat: 4
elephant dog: 1
goat dog: 1
monkey : 1
pig monkey: 1
pig seal: 1
seal dog: 1
https://stackblitz.com/edit/typescript-wvuvnr
You need to be clear what you mean by close and how close. Just looking at first neighbours you could try:
const findAssociations = words => {
const associations = {}
for (let i = 0; i < words.length - 1; i++) {
const word = words[i]
const wordRight = words[i+1]
const wordOne = word < wordRight ? word : wordRight;
const wordTwo = word < wordRight ? wordRight : word;
const keys = Object.keys(associations)
const key = `${wordOne}:${wordTwo}`
if (keys.indexOf(key) >= 0) {
associations[key]++
} else {
associations[key] = 1
}
}
const keys = Object.keys(associations)
const values = Object.values(associations)
const zipped = keys.map((key, index) => [key, values[index]])
zipped.sort((a, b) => a[1] < b[1] ? 1 : -1);
return zipped;
}
https://stackblitz.com/edit/js-3ppdit
You can use this function inside another function and add every time an element to ["dog", "cat"]
const arr = ["dog", "cat", "goat", "dog", "cat", "dog", "cat", "elephant", "dog", "cat", "pig", "seal", "dog", "cat", "pig", "monkey"]
const findArrayInArray = (arr1, arr2) => {
let count = 0,
arrString1 = arr1.join(""),
arrString2 = arr2.join("");
while (arrString2.indexOf(arrString1) > -1) {
count += 1;
arrString2 = arrString2.replace(arrString1, '');
}
return count;
}
console.log(`["dog", "cat"] exist ${findArrayInArray(["dog", "cat"], arr)} times`)
Assuming each item in the list is a delimiter of a set, and each set counts once for each item (i.e. ["dog", "cat", "goat"] counts as ["dog", "cat"] and ["dog", "cat", "goat"], and assuming you don't want any single occurrences, then here's one way:
const full_list = ["dog","cat","goat","dog","cat","dog","cat","elephant","dog","cat","pig","seal","dog","cat","pig","monkey"];
// create list of unique items
const distinct = (value, index, self) => {
return self.indexOf(value) ===index;
}
const unique_items = full_list.filter(distinct);
// get all patterns
var pre_report = {};
for (var i in unique_items) {
item = unique_items[i];
var pattern = [item];
var appending = false;
for (var j = full_list.indexOf(item) + 1; j < full_list.length; ++j) {
const related_item = full_list[j];
if (item == related_item) {
pattern = [item]
continue;
}
pattern.push(related_item);
if (pattern in pre_report) {
++pre_report[pattern];
} else {
pre_report[pattern] = 1;
}
}
}
// filter out only single occurring patterns
var report = {};
for (key in pre_report) {
if (pre_report[key] > 1) {
report[key] = pre_report[key];
}
}
console.log(report);
produces:
{ 'dog,cat': 5, 'dog,cat,pig': 2, 'cat,pig': 2 }
So I have a series of arrays, each of which are 2500 long, and I need to serialize and store all them in very limited space.
Since I have many duplicates, I wanted to cut them down to something like below.
[0,0,0,0,2,7,3,3,0,0,0,0,0,0,0,0,0]
// to
[0x4,2,7,3x2,0x9]
I wrote a couple one-liners (utilising Lodash' _.repeat) to convert to and from this pattern, however converting to doesn't seem to work in most/all cases.
let serialized = array.toString().replace(/((?:(\d)+,?)((?:\2+,?){2,}))/g, (m, p1, p2) => p2 + 'x' + m.replace(/,/g, '').length);
let parsed = serialized.replace(/(\d+)x(\d+),?/g, (z, p1, p2) => _.repeat(p1 + ',', +p2)).split(',');
I don't know why it doesn't work. It may be due to some of the numbers in the array. Eye-balling, the largest one is 4294967295, however well over 90% is just 0.
What am I missing in my RegEx that's preventing it from working correctly? Is there a simpler way that I'm too blind to see?
I'm fairly confident with converting it back from the serialized state, just need a hand getting it to the state.
Straight forward and simple serialization:
let serialize = arr => {
const elements = [];
const counts = []
let last = undefined;
[0,0,0,0,2,7,3,3,0,0,0,0,0,0,0,0,0].forEach((el,i,arr)=>{
if (el!==last) {
elements.push(el);
counts.push(1);
} else {
counts[counts.length-1]++;
}
last = el;
})
return elements.map((a,i)=>counts[i]>1?`${a}x${counts[i]}`:a).join(",");
};
console.log(serialize([0,0,0,0,2,7,3,3,0,0,0,0,0,0,0,0,0]));
UPDATE
Pure functional serialize one:
let serialize = arr => arr
.reduce((memo, element, i) => {
if (element !== arr[i - 1]) {
memo.push({count: 1, element});
} else {
memo[memo.length - 1].count++;
}
return memo;
},[])
.map(({count, element}) => count > 1 ? `${count}x${element}` : element)
.join(",");
console.log(serialize([0,0,0,0,2,7,3,3,0,0,0,0,0,0,0,0,0]));
Pure functional deserialize:
const deserialize = str => str
.split(",")
.map(c => c.split("x").reverse())
.reduce((memo, [el, count = 1]) => memo.concat(Array(+count).fill(+el)), []);
console.log(deserialize("4x0,2,7,2x3,9x0"))
In order to avoid using .reverse() in this logic, I'd recommend to change serialization from 4x0 to 0x4
Try this
var arr = [0,0,0,0,2,7,3,3,0,0,0,0,0,0,0,0,0];
var finalArray = []; //array into which count of values will go
var currentValue = ""; //current value for comparison
var tmpArr = []; //temporary array to hold values
arr.forEach( function( val, index ){
if ( val != currentValue && currentValue !== "" )
{
finalArray.push( tmpArr.length + "x" + tmpArr[0] );
tmpArr = [];
}
tmpArr.push(val);
currentValue = val;
});
finalArray.push( tmpArr.length + "x" + tmpArr[0] );
console.log(finalArray);
Another version without temporary array
var arr = [0, 0, 0, 0, 2, 7, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0];
var finalArray = []; //array into which count of values will go
var tmpCount = 0; //temporary variable to hold count
arr.forEach(function(val, index) {
if ( (val != arr[ index - 1 ] && index !== 0 ) )
{
finalArray.push(tmpCount + "x" + arr[ index - 1 ] );
tmpCount = 0;
}
tmpCount++;
if ( index == arr.length - 1 )
{
finalArray.push(tmpCount + "x" + arr[ index - 1 ] );
}
});
console.log(finalArray);
Do not use RegEx. Just use regular logic. I recommend array.reduce for this job.
const arr1 = [0,0,0,0,2,7,3,3,0,0,0,0,0,0,0,0,0]
const arr2 = ['0x4','2','7','3x2','0x9'];
const compact = arr => {
const info = arr.reduce((c, v) =>{
if(c.prevValue !== v){
c.order.push(v);
c.count[v] = 1;
c.prevCount = 1;
c.prevValue = v;
} else {
c.prevCount = c.prevCount + 1;
c.count[v] = c.count[v] + 1;
};
return c;
},{
prevValue: null,
prevCount: 0,
count: {},
order: []
});
return info.order.map(v => info.count[v] > 1 ? `${v}x${info.count[v]}` : `${v}`);
}
const expand = arr => {
return arr.reduce((c, v) => {
const split = v.split('x');
const value = +split[0];
const count = +split[1] || 1;
Array.prototype.push.apply(c, Array(count).fill(value));
return c;
}, []);
}
console.log(compact(arr1));
console.log(expand(arr2));
This is a typical reducing job. Here is your compress function done in just O(n) time..
var arr = [0,0,0,0,2,7,3,3,0,0,0,0,0,0,0,0,0],
compress = a => a.reduce((r,e,i,a) => e === a[i-1] ? (r[r.length-1][1]++,r) : (r.push([e,1]) ,r),[]);
console.log(JSON.stringify(compress(arr)));
since the motivation here is to reduce the size of the stored arrays, consider using something like gzip-js to compress your data.