Find elements that commonly appear next to each other in an array - javascript

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 }

Related

Reorder array by their last letter starting with the first array and so on

So Im trying to create a function to reorder the array by their last letter starting with the first array and so on
The first array TEAM last letter M then it will find and match with the array that starts with letter M which is MOIST so letter T will match TOKEN
Example: ["team", "token", "moist"] to ["team", "moist", "token"]
Issue is im getting only the result of ["team", "moist"]
let array = ["team", "token", "moist"];
let newArr = [];
const reorderArr = () => {
// reorder array by their last letter starting with the first array and so on
console.log("ARRAY:", array);
array.forEach((arr, index) => {
let currentWord = arr;
console.log("LOOP: ", index);
if (!newArr.includes(currentWord)) {
if (index === 0) {
console.log("HIT ONCE");
newArr.push(currentWord);
} else {
for (let x = 1; x < array.length; x++) {
const nextWord = array[x];
console.log("nextWord", nextWord);
if (nextWord.slice(0, 1) === newArr[newArr.length - 1].slice(-1)) {
console.log("HIT");
newArr.push(nextWord);
}
}
}
}
});
console.log("REORDERED ARRAY:", newArr); // RESULT SHOULD BE ["team", "moist", "token"]
};
reorderArr();
You could check all combinations with an array of left over words and an array of correct words.
function reorder(array) {
const
check = (pool, result = []) => {
if (!pool.length) return result;
const last = result?.at(-1)?.at(-1);
let longest = [];
for (const word of pool) {
if (last && last !== word[0]) continue;
const temp = check(pool.filter(w => w !== word), [...result, word]);
if (longest.length < temp.length) longest = temp;
}
return longest;
};
return check(array);
}
console.log(...reorder(["team", "token", "moist"]));

What is a Big O of this algorithm?

I wrote the following algorithm and it's pretty horrendous but at the moment it was the only way I was able to solve the problem. I was just wondering what the Big O of this equation is. My best guess is that it is O(n!) since I have the loops inside the filter function. Is this correct?
/*
Scrabble Validator, essentially.
const dictionary = ['apple', 'avocado', 'anteater', 'april', 'basket', 'ball', 'cat', 'cradle'] etc for about 100 or so words
const points = [{a:1}, {b:2}, {c:3}, {d:4}]; etc for all the letters in the alphabet. there are blanks - a blank is worth 0 but can be used in place of any letter
given a string of letters and a dictionary,
1. find all valid anagrams
2. find their point value using a Points object
3. Sort those valid options by highest scoring point
*/
const chars = 'aplpe';
const dictionary = ['apple', 'avocado', 'lap', 'app', 'anteater', 'april', 'basket', 'ball', 'cat', 'cradle'];
const points = [{p:1}, {a:2}, {e:3}, {l:4}];
let pointsMap = {};
points.forEach((point) => pointsMap = { ...pointsMap, ...point });
const charMap = {};
for (let char of chars) {
charMap[char] = charMap[char] + 1 || 1;
}
const matches = dictionary
.filter(word => {
if (word.length > chars.length) return false;
const wordMap = {};
for (let char of word) {
wordMap[char] = wordMap[char] + 1 || 1;
}
for (let char in wordMap) {
if (!charMap[char] || charMap[char] < wordMap[char]) {
return false;
}
}
return true;
})
.map((word) => {
let total = 0;
for (let char of word) {
total += pointsMap[char];
}
return { word, total }
})
.sort((a, b) => a.total > b.total ? -1 : 1)
.map(({ word }) => word);
return matches;

Inconsistency, when returning index of duplicate values

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

Matching 3 or more same elements in a array and adding them to a list

I am trying to find 3 or more matching items in array but it is only matching the first 3 and none matching for the rest of the array. If anyone could help would be great :)
var grid = [2,2,2,5,5,5,3,3,3,3];
checkResults();
function checkResults(){
var list_matches = []; // store all matches found
var listcurrent = []; // store current
var maxitems = 3;
var last = -1; // last cell
for(let j =0; j < grid.length; ++j){
let item = grid[j];
// check if last is null
if(last == -1){
// add first item
listcurrent.push(item);
last = item;
console.log("Added: "+item);
continue;
}
let wasMatch = false;
// check match
if(item == last){
wasMatch = true;
listcurrent.push(item);
last = item;
console.log("Added Match: "+item);
}
if(!wasMatch){
console.log("Not matched: " + item);
if(listcurrent.length >= maxitems){
list_matches.push(listcurrent);
}
// reset to null
last = -1;
listcurrent = [];
}
}
console.log(list_matches);
console.log("Cols: " + grid.length);
}
Expected Results: from [2,2,2,5,5,5,3,3,3,3];
0: 222
1: 555
2: 3333
Current output is:
0: 222 and thats it
You could take a temporary array for collecting the same values and push this array if the length has the wanted minimum length.
function getMore(array, min) {
var result = [],
temp;
array.forEach((v, i, a) => {
if (v !== a[i - 1]) return temp = [v];
temp.push(v);
if (temp.length === min) result.push(temp);
});
return result;
}
console.log(getMore([2, 2, 2, 5, 5, 5, 3, 3, 3, 3], 3));
you can do something like this:
var grid = [ 1, 1, 2, 3, 4, 5 ];
var hashMap = {};
for( var i = 0; i < grid.length; i++ ) {
if( hashMap.hasOwnProperty( grid[i] ) ) {
hashMap[ grid[i] ]++;
} else {
hashMap[ grid[i] ] = 1;
}
}
it will helps you.
//toLowerCase for get unique on words, not on character. if i ignore this, it will return two words=> developers. and developers
//first Split and join for remove '.' character form text and finally last split is for convert string to an Array of words that splited by Space character
let uniqWords = Array.from(new Set(text));
//using Set to get unique words and convert it to Array to do more on Array.
let count = {};
// declare varriable for store counts of words.
uniqWords.map(item => {
count[item] = text.filter(elem => {
//create property with the name of words and filter common words to an Array
return elem == item
}).length
//get Array length for get words repeated count.
})
console.table(count)
//log Result into Console
Another solution using Array.prototype[reduce/map/filter]
const someArray = [2, 2, 2, 5, 5, 5, 3, 3, 3, 3, 9, 9];
console.log(aggregate(someArray));
function aggregate(arr) {
return arr
// retrieve unique values
.reduce((acc, val) => !acc.includes(val) && acc.concat(val) || acc, [])
// use unique values to map arr values to strings
// if number of matches >= 3
.map(val => {
const filtered = arr.filter(v => v == val);
return filtered.length > 2 ? filtered.join("") : false
})
// filter non falsy values
.filter(val => val);
}

Find highest average in JavaScript 2d array of students scores

Yesterday, I faced this interview question. Initially, it seemed pretty easy, at least logically.
But somehow, I could not make it work in JavaScript.
Here is a 2d array of student scores, student names might be repeated multiple times. If this is the case, sum up all the scores and divide by the number of occurrences to find the average, do the Math.floor if necessary.
var arr = [
["Bobby","87"],
["Charles","100"],
["Eric","65"],
["Charles","22"],
["Charles","37"],
["Eric","49"]]
So, Charles average score would be Math.floor((100+22+37)/3) = 53
And, for Eric it would be Math.floor((65+49)/2) = 57.
So highest average would be ["Bobby","87"].
So far what I have tried fruitlessly..
var op_arr = [];
arr.each(function(item) {
var sum = 0;
var itemCount = 1;
var checkFlag = isItemInArray(arr,item);
if(checkFlag) {
itemCount++;
sum += item[1];
}
});
function isItemInArray(array,item) {
for(let i = 0;i < array.length; i++) {
if(array[i][0] === item[0]) {
return array[i];
}
}
return false;
}
But this does not work.
Please help me and explain the logic.
I would use some code to convert the list into a hash map based on the fact that there can be multiple of the same student.
var arr = [
["Bobby","87"],
["Charles","100"],
["Eric","65"],
["Charles","22"],
["Charles","37"],
["Eric","49"]
];
var scores = {};
for (var i = 0; i < arr.length; i++) {
var student = arr[i];
if (!scores.hasOwnProperty(student[0]))
scores[student[0]] = []
scores[student[0]].push(student[1])
}
The result should be:
{
"Bobby": ["87"],
"Charles": ["100", "22", "37"],
"Eric": ["65", "49"]
}
And now you can do a second pass over the object to calculate the average
for (var key in scores) {
if (!scores.hasOwnProperty(key)) continue;
var total = scores[key].reduce(function(next, cur) {
return next + parseInt(cur);
}, 0);
scores[key] = Math.floor(total / scores[key].length);
}
console.log(scores);
I'm sure you can make it a lot more elegant using ES6 features, but this should give you an idea of one solution.
You could first get all average values of each person and then get the highest average of the temporary result.
If more than one person have the same score, all persons are included.
var array = [["Bobby", "87"], ["Charles", "100"], ["Eric", "65"], ["Charles", "22"], ["Charles", "37"], ["Eric", "49"]],
highest = array
.reduce(function (r, a) {
var i = r.findIndex(b => a[0] === b[0]);
if (i !== -1) {
r[i][1] = (r[i][1] * r[i][2] + +a[1]) / ++r[i][2];
} else {
r.push(a.concat(1));
}
return r;
}, [])
.reduce(function (r, a, i) {
if (!i || r[0][1] < a[1]) {
return [a.slice(0, 2)];
}
if (r[0][1] === a[1]) {
r.push(a.slice(0, 2));
}
return r;
}, []);
console.log(highest);
You can following this approach
Collate all the scores for a name in an array of scores using reduce
Iterate the result using map and compute the average of scores array using reduce again.
Demo
var fnSumAvgArray = (arr) => arr.reduce( ( a, c ) => a + c, 0 )/arr.length;
var arr = [
["Bobby","87"],
["Charles","100"],
["Eric","65"],
["Charles","22"],
["Charles","37"],
["Eric","49"]];
var output = Object.values( arr.reduce( (a,c) => (
a[c[0]] = (a[c[0]] || { name : c[0], scores : [] }), //check if accumulator already has been initialized for this name or else intialize
a[ c[ 0 ] ].scores.push ( +c[1] ), //push the score into the name based score array
a ) , {}) ) //return the accumulator
.map( s => (
s.avg = fnSumAvgArray(s.scores), s
));
console.log( output );
var arr = [
["Bobby","87"],
["Charles","100"],
["Eric","65"],
["Charles","22"],
["Charles","37"],
["Eric","49"]];
var x, students=[], counter=[], scoreSums=[], name, i=0;
for(x in arr) {
name = arr[x][0];
j = students.indexOf(name);
if(j < 0) {
students[i] = name;
counter[i] = 1;
scoreSums[i] = parseInt(arr[x][1]);
i++;
} else {
scoreSums[j] += parseInt(arr[x][1]);
counter[j]++;
}
}
var maxMean = 0, mean;
for(i=0; i<students.length; i++) {
mean = scoreSums[i] / counter[i];
if(mean > maxMean) {
name = students[i];
maxMean = mean;
}
}
document.getElementById('maxMean').innerHTML='Highest average is '+ name+' at '+maxMean;
console.log(name, maxMean);
<div id="maxMean"></div>
Here is a 'round the houses' approach. It is a slightly longer way to write it out but I think it's human readable which should help with the concept.
I've commented the code below.
var arr = [
["Bobby", "87"],
["Charles", "100"],
["Eric", "65"],
["Charles", "22"],
["Charles", "37"],
["Eric", "49"]
];
let objects = [];
let names = [];
for (let item of arr) {
// Get unique names
if (names.indexOf(item[0]) <= 0) {
names.push(item[0]);
}
// Create object rather than array
let score = {
name: item[0],
score: parseInt(item[1])
}
objects.push(score);
}
// Work out average based on name
function countScore(name) {
let count = 0;
let total = 0;
for (let object of objects) {
if (object.name === name) {
count += 1;
total += object.score
}
}
let avgScore = total / count;
console.log(name + ': ' + avgScore)
}
// Run function for each unique name
for (let name of names) {
countScore(name);
}
I hope you find this helpful.
let findMaxAvg = (arr) => {
let studmap = new Map();
arr.forEach((item,ind) => {
let [name,score] = [item[0],item[1]];
!studmap.has(name) ? studmap.set(name,[score,1]) : studmap.set(name,
[studmap.get(name)[0]+score,studmap.get(name)[1]+1])
});
return Math.max(...[...studmap.values()].map(sdata =>
Math.round(sdata[0]/sdata[1])));
}
var arr = [
["Bobby","87"],
["Charles","100"],
["Eric","200"],
["Charles","22"],
["Charles","37"],
["Eric","49"]];
console.log(findMaxAvg(arr));
There's a lot of ways you can solve this as per the solutions provided above. I've also encountered this question before and I used
Map()
to collect all students and their scores and return array with [name, average].
const scores = [['brian', 80], ['brian', 90], ['brian', 65], ['robhy', 85], ['robhy', 75], ['ryan', 75], ['murphy', 85]];
const calculateHigestAvg = (scores)=>{
let studentObj = new Map();
let score = [];
for (var i = 0; i < scores.length; i++) {
if (studentObj.has(scores[i][0])) {
const studentScoreArr = studentObj.get(scores[i][0])
studentScoreArr.push(scores[i][1]);
studentObj.set(scores[i][0], studentScoreArr);
} else {
studentObj.set(scores[i][0], [scores[i][1]]);
}
}
return Array.from(studentObj.entries())
.map((item, index)=>{
return [item[0], Math.floor(item[1].reduce((a, b) => a + b) / item[1].length)]
}
, 0)
}
calculateHigestAvg(scores);
// results
[["brian", 78],["robhy", 80],["ryan", 75],["murphy", 85]]

Categories