Javascript arrays ab=ba - javascript

If i have a multidimensional array like: [[a,b],[a,c],[b,a],[b,c],[c,a],[c,b]] how can i go through and remove repeats where [a,b] is the same as [b,a].
also, the array is actually massive, in the tens of thousands. A for loop would have to be done backwards because the array length will shrink on every iteration. Im not even sure that an each loop would work for this. I really am at a loss for just a concept on how to begin.
Also, i tried searching for this for about an hour, and i don't even know how to phrase it.

I think I'm going to try a different approach to this problem. I also think it'll be quicker than some of the solutions proposed (though we'd need of course to test it and benchmark it).
First off, why don't we take advantage of the hash oriented nature of javascript arrays and objects? We could create an object containing the relations (in order to create a kind of a map) and store in a new array those relationships that hasn't been stored yet. With this approach there's no problem about objects either, we just request for an identifier or hash or whatever for every object. This identifier must make the relationship between them possible.
UPDATE
The script now controls the possibility of repeated elements f.e [[a,b],[a,b]]
The script now controls the possibility of elements with the same object repeated f.e [[a,a],[a,a][a,a]] would return [a,a]
The code:
var temp = {},
massive_arr = [['a','b'],['a','c'],['a','d'], ['b','a'],['b','c'],['b','d'],['c','a'],['c','b'],['c','d']],
final_arr = [],
i = 0,
id1,
id2;
for( ; i < massive_arr.length; i++ ) {
id0 = objectIdentifier(massive_arr[i][0]);// Identifier of first object
id1 = objectIdentifier(massive_arr[i][1]);// Identifier of second object
if(!temp[id0]) {// If the attribute doesn't exist in the temporary object, we create it.
temp[id0] = {};
temp[id0][id1] = 1;
} else {// if it exists, we add the new key.
temp[id0][id1] = 1;
}
if( id0 === id1 && !temp[id0][id1+"_bis"] ) {// Especial case [a,a]
temp[id0][id1+"_bis"] = 1;
final_arr.push(massive_arr[i]);
continue;// Jump to next iteration
}
if (!temp[id1]) {// Store element and mark it as stored.
temp[id1] = {};
temp[id1][id0] = 1;
final_arr.push(massive_arr[i]);
continue;// Jump to next iteration
}
if (!temp[id1][id0]) {// Store element and mark it as stored.
temp[id1][id0] = 1;
final_arr.push(massive_arr[i]);
}
}
console.log(final_arr);
function objectIdentifier(obj) {
return obj;// You must return a valid identifier for the object. For instance, obj.id or obj.hashMap... whatever that identifies it unequivocally.
}
You can test it here
SECOND UPDATE
Though this is not what was requested in the first place, I've changed the method a bit to adapt it to elements of n length (n can vary if desired).
This method is slower due to the fact that relies on sort to generate a valid key for the map. Even so, I think it's fast enough.
var temp = {},
massive_arr = [
['a', 'a', 'a'], //0
['a', 'a', 'b'], //1
['a', 'b', 'a'],
['a', 'a', 'b'],
['a', 'c', 'b'], //2
['a', 'c', 'd'], //3
['b', 'b', 'c'], //4
['b', 'b', 'b'], //5
['b', 'b', 'b'],
['b', 'c', 'b'],
['b', 'c', 'd'], //6
['b', 'd', 'a'], //7
['c', 'd', 'b'],
['c', 'a', 'c'], //8
['c', 'c', 'a'],
['c', 'd', 'a', 'j'], // 9
['c', 'd', 'a', 'j', 'k'], // 10
['c', 'd', 'a', 'o'], //11
['c', 'd', 'a']
],
final_arr = [],
i = 0,
j,
ord,
key;
for (; i < massive_arr.length; i++) {
ord = [];
for (j = 0; j < massive_arr[i].length; j++) {
ord.push(objectIdentifier(massive_arr[i][j]));
}
ord.sort();
key = ord.toString();
if (!temp[key]) {
temp[key] = 1;
final_arr.push(massive_arr[i]);
}
}
console.log(final_arr);
function objectIdentifier(obj) {
return obj;
}
It can be tested here

Based on my understanding that you want to remove from the parent array any children arrays which hold the same set of objects without regard for order, this should do it is some code:
function getId(obj) { // apparently these objects have identifiers
return obj._id; // I'm testing with MongoDB documents
}
function arraysEqual(a, b) {
if (a === b) { return true; }
if (a == null || b == null) { return false; }
if (a.length != b.length) { return false; }
aIds = []; bIds = [];
for (var i = 0; i < a.length; i++) {
aIds.push(getId(a[i])); bIds.push(getId(b[i]));
}
aIds.sort(); bIds.sort();
for ( var i = 0; i < aIds.length; i++ ) {
if(aIds[i] !== bIds[i]) { return false; }
}
return true;
}
function removeRepeats(list) {
var i, j;
for (i=0; i < list.length; i++) {
for (j=i+1; j < list.length; j++) {
if (arraysEqual(list[i], list[j])) {
list.splice(j,1);
}
}
}
}
The removeRepeats function goes through each element and compares it with every element that comes after it. The arraysEqual function simply returns true if the arrays are equal. The isEquivalent function should test object equivalence. As noted on that webpage, there are libraries that test object equivalence. If you are okay with adding those libraries, you can replace the isEquivalent function with _.isEqual.

***
* Turns out the OP has objects in his list, so this approach won't
* work in that case. I'll leave this for future reference.
***
var foo = [['a','b'],['a','c'],['b','a'],['b','c'],['c','a'],['c','b']];
function removeRepeats(list) {
var i;
var b = [];
var _c = [];
for (i = 0; i < list.length; i++) {
var a = list[i].sort();
var stra = a.join("-");
if(_c.indexOf(stra) === -1) {
b.push(a);
_c.push(stra);
}
}
return b;
}
console.log(removeRepeats(foo));
It's not the most pretty code I've ever produced, but it should be enough to get you started I guess. What I'm doing is creating two new arrays, b and _c. b will be the array without the repeats. _c is a helper array which contains all the unique pairs already processed as a string, so I can do easy string comparisons while looping through list.

Related

Comparing two arrays and returning valid and misplaced elements

I wanted to make a simple game for my first project but I've encountered some problems with the logic behind it.
The game should compare two arrays, one of which stores user input and the other which is randomly generated. Both arrays have the length of n (let's say n=3) and accept n unique characters as their values. Let's say that the user input is ['A','A', 'B'] and that the winning combination is ['B', 'A', 'C']. The win condition is simple, all three elements from the user input array must be valid. An element is valid if both it's value and index correspond to the element in the second array.
Checking this is simple enough:
for (let i = 0; i<arr1.length; i++) {
for (let j = 0; j<arr1.length; j++){
if (arr[i] === arr1[j] && getIndices(arr[i], arr1[j]) === true){
valid ++;
}
However, I also want to keep track of misplaced elements, where arr[i] matches the value of arr[j] but the equality check on their indices returns false. Here's the problem, if I were to put this inside an else statement, and compare ['A', 'B', 'A'] to ['A', 'C', 'C'] it would return 1 valid as it should, but also 1 misplaced which is incorrect because 'A' only appears once in the second array. How would you set up the statement to avoid this?
I'm quite new to this so I haven't tried much.
If the input value and the win condition has the same length, you don't need two for loop. And name your variables correctly: inputs and condition.
var points = 0
var misplacedElements = []
for (let i = 0; i<inputs.length; i++) {
//findIndex returns index of the element on the condition array
//If element don't exist returns -1
const indexOfInput = condition.findIndex(e=> e === inputs[i])
if(indexOfInput != -1){
//Element contains but might not be on the same index
if(indexOfInput == i){
//On the same index so give a point
points++
}else{
//Hold the index or the element depends to your need
misplacedElements.push( i )
}
}
You can ask if you don't understand.
This is the JS way.
const userList = ['A', 'B', 'C'];
const winList = ['A', 'B', 'A'];
const scoreCalculator = ({ user, win }) => {
let points = 0;
user.forEach((value, index) => {
if (value === win[index]) {
points++;
}
});
return points;
}
console.log(scoreCalculator({user: userList, win: winList}));
The cost will be O(n).
With normal for execution.
const userList = ['A', 'B', 'C'];
const winList = ['A', 'B', 'A'];
const scoreCalculator = ({ user, win }) => {
let points = 0;
for(let i = 0; user.list; i++) {
if (user[i] === win[i]) {
points++;
}
});
return points;
}
console.log(scoreCalculator({user: userList, win: winList}));
As you can see, Array.prototype.forEach() its work like normal for.

How sort arrays in js

There are arrays A and B. We need to add to array C all the values of arrays A and B that are equal in both value and indexes.
A = [a,b,c], B = [c,b,a], C = [b]
Also: to add to array D all unique values from array A that are contained in array B.
A = [d,a,b,c], B = [c,b,a,a], D = [a,b,c]
Is it possible to do this without a nested loop? I can handle the first task, but how do I fill D with values?
for (let i = 0; i < a.length; i++) {
if (b[i] === a[i]) {
c.push(a[i]);
} else if() {
// Some code
}
}
filter method? (for second question)
let D = A.filter(e => B.includes(e));
adding because you asked for D to contain unique values from A, thanks to #jarmod for pointing this out. You could use filter again to remove duplicates. I found this: Get all unique values in a JavaScript array (remove duplicates)
You can use a Set to keep track of unique values and .has() for efficient lookup in that Set. This code uses only one loop in your code, but several set operations use loops in their internal implementation (to build a set from an Array or convert a set to an Array). That cannot be avoided.
const A = ['e', 'd', 'a', 'b', 'c'];
const B = ['e', 'c', 'b', 'a', 'a'];
function processArrays(a, b) {
const c = [];
const aSet = new Set(a);
const commonSet = new Set();
for (let i = 0; i < a.length; i++) {
if (b[i] === a[i]) {
c.push(a[i]);
}
// if aSet has this value, then add it to commonSet
if (aSet.has(b[i])) {
commonSet.add(b[i])
}
}
return { samePositionElements: c, commonElements: Array.from(commonSet) };
}
console.log(processArrays(A, B));
Note, I don't do an if/else here because these are two independent output tests. The first is if the two elements in the same position have the same value. The second is whether it's an element in common with the other array (regardless of position).
This code assumes the two input arrays have the same length. If you wish to support inputs with different length, that can be accommodated with slightly more code.
Simple Way and Fast Solution:
let A = ['a','b','c', 'e'];
let B = ['c','b','a', 'e'];
let common =[];
for(let i=0; i<A.length && i<B.length; i++){
if(A[i]==B[i]){
common.push(A[i])
}
}
console.log(common)

why is this removeDuplicate not working?

I making a function that simply removes duplicate strings, so there are only unique strings left:
var d = ['a','b','B','C','d','e','f','e'];
d.length; //8
function removeDuplicates(data) {
var i, j, a, b;
for(i = 0; i < data.length - 1; i++) {
a = data[i];
for(j = i; j < data.length; j++) {
b = data[j];
if (a.toLowerCase() == b.toLowerCase()) {
data.splice(j, 1);
}
}
}
}
removeDuplicates(d)
d.length; //4 ????
There were only two duplicates, Only B, e should have gotten removed.
but I get this:
["b", "C", "e", "e"]
Your issue is coming from the fact that any time i === j you'll have a match, and it'll be removed. In your inner loop, just put a check so that you only do your "remove them if they're equal" in situations where i !== j
Also, as nnn noted in the comments, splicing will mess up the index. The cleanest way to do this is with a filter and an every.
Updated solution below:
var d = ['a', 'b', 'B', 'C', 'd', 'e', 'f', 'e'];
var e = ['e','e','e','e'];
// d.length; //8
function removeDuplicates(data) {
const noDuplicates = data.filter((letter, ogIndex) => {
return data.every((innerLetter, innerIndex) => {
if (ogIndex < innerIndex) {
const check = letter.toLowerCase() !== innerLetter.toLowerCase();
return check;
} else {
return true;
}
})
})
return noDuplicates
}
console.log(removeDuplicates(d));
// ["a", "B", "C", "d", "f", "e"]
console.log(removeDuplicates(e));
// ["e"]
With ES6/ES2015, a simple way of getting a list of unique items is to use the new Set data type.
To "unique"-ify an array, just convert it to a set and then back to an array. i.e.
[...new Set(originalArray)]
(Update: My answer originally used Array.from(S_E_T) to convert the set back to an array. I have now changed it to [...S_E_T] to do that conversion.)
To do the same in a case-insensitive manner, follow the same logic, but just convert each original array element to its lowercase equivalent before converting that whole array to the set.
To do keep only the first instance of a string of any case (e.g. to keep 'cAt' from ['cAt', 'CaT'] instead of just the all-lowercased 'cat'), first perform the case-insensitive search as above, then get the index of each unique lowercase element from the lowercased un-uniqified original array, then use that index to retrieve the whichever-cased element from the original-cased un-uniqified original array.
const show = msg => {console.log(JSON.stringify(msg))};
const f = (arr) => {
const arrLC = arr.map(x => x.toLowerCase(x));
return [...new Set(arrLC)].map(x => arr[arrLC.indexOf(x)])
};
const d1 = ['a','b','B','C','d','e','f','e'];
const d2 = ['e', 'e', 'e', 'e', 'e'];
show(d1);
show([...new Set(d1)]);
show([...new Set(d1.map(x => x.toLowerCase(x)))]);
show(f(d1));
show('------');
show(d2);
show([...new Set(d2)]);

Compare array of quiz answers (A/B/C/D) to criteria combinations

I'm building a relatively simple quiz, have all the markup and form logic built out, now on submit I am getting an array of answers (A/B/C/D) that I need to compare to various combinations from a list of criteria
Here is what the criteria looks like. Too add to the complexity I'm trying to set it up to allow various answers, denoted by a / separator in the string. Below is a simplified representation of the code, assume that I am working within the form submit handler, and results is the form data I get back from groups of radio inputs.
var criteria = [
{
combination: ['B', 'B', 'A/B', 'C', 'A/B/C/D'],
type: 'Type A'
},
{
combination: ['B', 'A/B/C/D', 'A/B', 'A/B/D', 'A/B/C/D'],
type: 'Type B'
},
{
combination: ['D', 'A', 'C', 'A/B', 'A/B/C/D'],
type: 'Type C'
}
]
// this data comes back from form
var results = ['B', 'A', 'A', 'D', 'C'];
function compareResults(results, criteria) {
$.each( results, function (i, answer) {
// ?
})
}
compareResults(results, criteria)
I'm having a brainfart on how to step through and compare to the multidimensional object/array. I know I need a for .. in loop, to .split('/'), etc. Any help is appreciated.
Edit
If it makes it easier, I can structure the criteria combinations like so:
combination: {
'1' : ['B'],
'2' : ['B'],
'3' : ['A','B'],
'4' : ['C'],
'5' : ['A','B','C','D'],
},
Not sure if this is what you wanted. I'm assuming the result can only be one (i.e criteria are mutually exclusive, or their intersection is empty).
function compareResults(results, criteria) {
for(var z = 0; z < criteria.length; z++){
var cr = criteria[z];
var matches = true;
for(var k = 0; k < results.length; k++){
var combinationItems = cr.combination[k].split("/"),
item = results[k],
itemMatches = false;
for(var i = 0; i < combinationItems.length; i++){
if(combinationItems[i] == item){
itemMatches = true;
break;
}
}
if(!itemMatches){
matches = false;
break;
}
}
if(matches) return cr.type;
}
return null;
}

Function to convert an Array to an Associative array [duplicate]

This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
Convert flat array [k1,v1,k2,v2] to object {k1:v1,k2:v2} in JavaScript?
I want to convert an array to an associative array in JavaScript.
For example, given the following input,
var a = ['a', 'b', 'c', 'd'];
I want to get the next associative array as output:
{'a' : 'b', 'c' : 'd'}
How can I do that?
Using .forEach:
var a = ['a', 'b', 'c', 'd'];
var obj_a = {};
a.forEach(function(val, i) {
if (i % 2 === 1) return; // Skip all even elements (= odd indexes)
obj_a[val] = a[i + 1]; // Assign the next element as a value of the object,
// using the current value as key
});
// Test output:
JSON.stringify(obj_a); // {"a":"b","c":"d"}
Try the following:
var obj = {};
for (var i = 0, length = a.length; i < length; i += 2) {
obj[a[i]] = a[i+1];
}
There is no such thing as an associative array, they're called Objects but do pretty much the same :-)
Here's how you would do the conversion
var obj = {}; // "associative array" or Object
var a = ['a', 'b', 'c', 'd'];
for(index in a) {
if (index % 2 == 0) {
var key = a[index];
var val = a[index+1];
obj[key] = val;
}
}

Categories