I have a two dimensional JS array in which some rows are useless and needs to be deleted;
In particular I need to delete the rows that are embedded in other rows (by saying that row B is embedded in row A I mean not just that A is a superset of B, but that A contains all the elements in B, in sequence and in the same order)
EX. I have:
var matrix = [
["User","Shop","Offer","Product","File"],
["User","Shop","File"],
["User","Shop","Map"],
["User","Shop","Promotion"],
["User","Shop","Offer","Product","Reservation"],
["User","Group","Accesslevel"],
["User","Group"],
["User","Reservation"],
["User","Shop"],
["User","Shop","Offer","Product","Markers"]
];
In this example the second row (["User","Shop","File"]) should NOT be deleted (all its elements are in the first row, but not consecutive);
Row 7 (["User","Group"]) should be deleted because is embedded in the 6th (["User","Group","Accesslevel"]) and also row 9 (["User","Shop"]) because is embedded in many others..
I'm looking for a possible efficient algorithm to check if an array is embedded in another one;
I will use this in nodejs.
This should do the trick.
// Is row2 "embedded" in row1?
function embedded(row1, row2) {
return row2.length < row1.length &&
row2.every(function(elt, i) { return elt === row1[i]; });
}
//filter out rows in matrix which are "embedded" in other rows
matrix.filter(function(row) {
return !matrix.some(function(row2) { return embedded(row2, row); });
});
here is a solution which I used few days ago for the same purpose but on the client side, This would also work on node server.
http://jsfiddle.net/8wLst3qr/
I have changed the program according to your needs,
What I have done here is,
some initialisation,
var matrix = [
["User","Shop","Offer","Product","File"],
["User","Shop","File"],
["User","Shop","Map"],
["User","Shop","Promotion"],
["User","Shop","Offer","Product","Reservation"],
["User","Group","Accesslevel"],
["User","Group"],
["User","Reservation"],
["User","Shop"],
["User","Shop","Offer","Product","Markers"]
];
var tempArr=matrix;
here are the steps
convert the array of arrays to an array of strings-(this is because you need to clear the redundant data only if it is in the same order), code as follows.
var json=[];
for(k=0;k<tempArr.length;k++)
{
json[k]=tempArr[k].toString();
}
and then match the index of each string in other strings in the array, if matches, check the string whose length is less and delete
it.
for(k=0;k<json.length;k++)
{
for(l=0;l<json.length;l++)
{
console.log("val l="+json[l]+"val k="+json[k]+"value="+json[l].indexOf(json[k]));
console.log("k="+k+";l="+l);
if(k!=l && (json[k].indexOf(json[l]) !=-1))
{
console.log("removing");
console.log("removing");
if(json[k].length>json[l].length)
{
json.splice(l, 1);
}
else
{
json.splice(k, 1);
}
}
}
}
hope it helps,
edit-sorry you would require to check the console.log for the output on fiddle
Related
The goal is to filter an array based on the slots the user has selected.
For example an array has slots for 7pm-9pm,10pm-12pm and so on.
Now the user selects 7pm-9pm, so now I want to filter the array which have 7ppm-9pm or is the users wants
7pm-9pm and 10pm-11pm so the data should be based on 7pm-9pm and 10pm-11pm
Here is how I store the values
This is the original array
data :[
{
name:"something",
phone:"another",
extraDetails : {
// some more data
slots : [
{item:"6PM-7PM"},
{item:"7PM-8pm}
]
}
},{
// Similarly more array with similar data but somewhere slots might be null
}
]
Now for example we have this array
slots:[{6PM-7PM,9PM-10PM,11PM-12AM}]
Now this should filter all those which includes timeslots of 6PM-7PM,9PM-10PM,11PM-12AM
or if the user selects
slots:[{6PM-7PM}]
We should still get the results that includes 6pm-7pm more or else don't matter.
First, I'd suggest using this for your slots representation for simplicity, but you can alter this approach depending on your actual code:
slots: ['6PM-7PM', '9PM-10PM', '11PM-12PM']
Then you can iterate through your data and use filter:
const querySlots = ['6PM-7PM', '9PM-10PM', '11PM-12PM'];
const matchedPersonsWithSlots = data.filter( (person) => {
let i = 0;
while ( i < person.extraDetails.slots.length ) {
if (querySlots.includes(person.extraDetails.slots[i]) return true;
i += 1;
}
return false;
});
matchedPersonsWithSlots will then have all the people that have a slot that matches one of the slots in your query, because if any of the query slots are in a person's list of slots, then it's included in the result set.
EDIT to include a different use case
If, however, every slot in the query array must be matched, then the filtering has to be done differently, but with even less code.
const matchedPersonsWithAllSlots = data.filter(person =>
querySlots.every((qSlot)=>person.extraDetails.slots.includes(qSlot)));
The above will go through each person in your data, and for each of them, determine whether the person has all of your query slots, and include them in the result list, only if this is true.
I built a custom component that filters an array of objects. The filter uses buttons, sets from active to non-active and allows more than one option on/off at the same time.
StackBlitz of my attempt - https://stackblitz.com/edit/timeline-angular-7-ut6fxu
In my demo you will see 3 buttons/options of north, south and east. By clicking on one you make it active and the result should include or exclude a matching "location" either north, south and east.
I have created my methods and structure to do the filtering, I'm struggling with the final piece of logic.
So far I have created a method to create an array of filtered locations depending on what the user clicks from the 3 buttons.
Next this passes to my "filter array" that gets the logic that should compare this filtered array against the original to bring back the array of results that are still remaining.
Its not quite working and not sure why - I originally got this piece of functionality working by using a pipe, but fore reasons do not want to go in that direction.
//the action
toggle(location) {
let indexLocation = this.filteredLocations.indexOf(location);
if (indexLocation >= 0) {
this.filteredLocations = this.filteredLocations.filter(
i => i !== location
);
} else {
this.filteredLocations.push({ location });
}
this.filterTimeLine();
}
// the filter
filterTimeLine() {
this.filteredTimeline = this.timeLine.filter(x =>
this.contactMethodFilter(x)
);
}
//the logic
private contactMethodFilter(entry) {
const myArrayFiltered = this.timeLine.filter(el => {
return this.filteredLocations.some(f => {
return f.location === el.location;
});
});
}
https://stackblitz.com/edit/timeline-angular-7-ut6fxu
Sorry for my expression but u have a disaster in your code. jajaja!. maybe u lost that what u need but the logic in your functions in so wrong. comparing string with objects. filter a array that filter the same array inside... soo u need make a few changes.
One:
this.filteredLocations.push({location});
Your are pushing object. u need push only the string.
this.filteredLocations.push(location);
Two:
filterTimeLine() {
this.filteredTimeline = this.timeLine.filter(x =>
this.contactMethodFilter(x)
);
}
in this function you filter the timeLine array. and inside of contactMethodFilter you call filter method to timeLine again....
See a functional solution:
https://stackblitz.com/edit/timeline-angular-7-rg7k3j
private contactMethodFilter(entry) {
const myArrayFiltered = this.timeLine.filter(el => {
return this.filteredLocations.some(f => {
return f.location === el.location;
});
});
}
This function is not returning any value and is passed to the .filter
Consider returning a boolean based on your logic. Currently the filter gets undefined(falsy) and everything would be filtered out
Note this is not a duplicate of How to extend an existing JavaScript array with another array, without creating a new array? because I'm looking to have a nested array, not to simply extend an array with another array to result into 1 array. Please read the question properly before you mark this as duplicate.
I'm looping through rows in a (Google) sheet to collect values, and would like to add each row as array to an array, which should result in an output like this (simplified example to illustrate):
array_main = [[row1_cell1,row1_cell2,row1_cell3], [row2_cell1,row2_cell2,row2_cell3], ...]
I first tried this with .push, which adds the values, but not as array:
accounts_last_row = 10
accounts_array = []
for (var i = 0; i < accounts_last_row; ++i) {
if ((accounts_range[i][1] == 'test') {
accounts_array.push([ [accounts_range[i][1]],[accounts_range[i][2]] ])
}
}
I'm aware similar questions have been asked, but most of them simply recommend using .concat to merge 2 arrays. I tried this as well but it doesn't add anything to the array:
...
if ((accounts_range[i][1] == 'test') {
accounts_array.concat( [accounts_range[i][1]],[accounts_range[i][2]] )
}
...
What am I missing? Thanks in advance.
You almost had it, inner arrays are simple ones, you had too many brackets.
Try like this:
accounts_array.push( [accounts_range[i][1],accounts_range[i][2]] );
the code above will work to add rows.
If you want to add data as a single column the you will have to change the brackets like this:
accounts_array.push( [accounts_range[i][1]],[accounts_range[i][2]] );
This type of operation can be done neatly with Array#filter and Array#push and apply:
const results = [];
const colIndexToTest = /** 0, 1, etc. */;
const requiredValue = /** something */;
SpreadsheetApp.getActive().getSheets().forEach(
function (sheet, sheetIndex) {
var matchedRows = sheet.getDataRange().getValues().filter(
function (row, rowIndex) {
// Return true if this is a row we want.
return row[colIndexToTest] === requiredValue;
});
if (matchedRows.length)
Array.prototype.push.apply(results, matchedRows);
});
// Use Stackdriver to view complex objects properly.
console.log({message: "matching rows from all sheets", results: results});
The above searches the given column of all rows on all sheets for the given value, and collects it into a 2d array. If all rows are the same number of columns, this array would be directly serializable with Range#setValues.
This code could have used map instead of forEach and the push.apply, but that would place empty or undefined elements for sheet indexes that had no matches.
I'm assuming if account-range[i][1] is 'test' copy the entire row to accounts_array. Drop the second index.
accounts_last_row = 10
accounts_array = []
for (var i = 0; i < accounts_last_row; ++i) {
if ((accounts_range[i][1] == 'test') {
accounts_array.push(accounts_range[i])
}
}
I am trying to remove duplicate items from a collection that I request via an API in Laravel.
This is my code:
computed: {
// slice the array of data to display
filteredList() {
/* NEW PART */
var tips = this.dublicate;
/* END NEW PART */
tips = this.items.filter(item => {
return item.tip.toLowerCase().includes(this.search.toLowerCase())
})
return tips.slice(0, this.display);
},
dublicate() {
var filtered_array = [];
for(var i =0; i < this.items.length; i++) {
if(this.items[i].tip.toLowerCase() != this.items[i+1].tip.toLowerCase()) {
filtered_array.push(this.items[i])
}
}
return filtered_array;
}
}
}
If I remove the code within the NEW PART comments, everythin works fine.
In the NEW PART I am trying to remove duplicate content, based on the items tip attribute.
If the tip attribute is the same as the next items tip attribute, it should be excluded from the tips array, which is returned as a v-for="tips in filteredList".
However, I just get an empty array with this new part. What am I doing wrong?
I get the following from Vue Devtools:
dublicate:"(error during evaluation)"
filteredList:"(error during evaluation)"
An example data from items, that are from an API request:
(This is the data that I get, when I dont try to remove duplicates, which works)
As this is in VueJS, I cant use the answer provided here.
You are looking past the end of the array with i + 1. You need to push the last item without looking for the one after it (because there isn't one). I think using filter is more straightforward than building an array with a for loop.
dublicate() {
return this.items.filter((a, i) =>
i === this.items.length - 1 ||
a.tip.toLowerCase() !== this.items[i + 1].tip.toLowerCase()
);
}
Given an example input:
[
{"id":1,"currentBlack":1,"currentWhite":0,"max":1},
{"id":2,"currentBlack":0,"currentWhite":1,"max":1},
]
Output all possible states of the input where currentBlack and currentWhite can have a value anywhere in the range from their initial value up to the maximum value.
Correct output for this example:
[
[
{"id":1,"currentBlack":1,"currentWhite":0,"max":1},
{"id":2,"currentBlack":0,"currentWhite":1,"max":1},
],
[
{"id":1,"currentBlack":1,"currentWhite":1,"max":1},
{"id":2,"currentBlack":0,"currentWhite":1,"max":1},
],
[
{"id":1,"currentBlack":1,"currentWhite":1,"max":1},
{"id":2,"currentBlack":1,"currentWhite":1,"max":1},
],
[
{"id":1,"currentBlack":1,"currentWhite":0,"max":1},
{"id":2,"currentBlack":1,"currentWhite":1,"max":1},
]
]
The real input will have max anywhere between 1 and 8 and there will be far more objects within the input array. My attempt is below (heavily commented):
function allPossibleCounts(pieceCounts) {//pieceCounts is the input
var collection = []; //used to collect all possible values
recursiveCalls(pieceCounts); //runs recursive function
return collection; //returns result
function recursiveCalls(pieceCounts) {
//if pieceCounts is already in collection then return, not yet implemented so duplicates are currently possible
collection.push(pieceCounts);//inputs a potential value
console.log(JSON.stringify(pieceCounts));//this is successfully logs the correct values
console.log(JSON.stringify(collection));//collection isn't correct, all values at the top of the array are copies of each other
for (let n in pieceCounts) {//pieceCounts should be the same at the start of each loop within each scope, aka pieceCounts should be the same at the end of this loop as it is at the start
subBlackCall(pieceCounts);
function subBlackCall(pieceCounts) {
if (pieceCounts[n].currentBlack < pieceCounts[n].max) {
pieceCounts[n].currentBlack++;//increment
recursiveCalls(pieceCounts);
subBlackCall(pieceCounts);//essentially you're either adding +1 or +2 or +3 ect all the way up to max and calling recursiveCalls() off of each of those incremented values
pieceCounts[n].currentBlack--;//decrement to return pieceCounts to how it was at the start of this function
}
}
subWhiteCall(pieceCounts);
function subWhiteCall(pieceCounts) {
if (pieceCounts[n].currentWhite < pieceCounts[n].max) {
pieceCounts[n].currentWhite++;
recursiveCalls(pieceCounts);
subWhiteCall(pieceCounts);
pieceCounts[n].currentWhite--;
}
}
}
}
}
But currently my attempt outputs as this ungodly mess of copied arrays
[[{"id":1,"currentBlack":1,"currentWhite":1,"max":1},{"id":2,"currentBlack":1,"currentWhite":1,"max":1}],[{"id":1,"currentBlack":1,"currentWhite":1,"max":1},{"id":2,"currentBlack":1,"currentWhite":1,"max":1}],[{"id":1,"currentBlack":1,"currentWhite":1,"max":1},{"id":2,"currentBlack":1,"currentWhite":1,"max":1}],[{"id":1,"currentBlack":1,"currentWhite":1,"max":1},{"id":2,"currentBlack":1,"currentWhite":1,"max":1}],[{"id":1,"currentBlack":1,"currentWhite":1,"max":1},{"id":2,"currentBlack":1,"currentWhite":1,"max":1}]]
Edit: working code: https://pastebin.com/qqFTppsY
The pieceCounts[n] always reference to the one object. You should recreate the pieceCount for saving in to the collection as different object. For example, you can add
pieceCounts = JSON.parse(JSON.stringify(pieceCounts)); // just clone
at the start of recursiveCalls function.
To avoid conversion to JSON and back, I would suggest using Object.assign to perform a deeper copy in combination with map on the array:
function allPossibleCounts(pieceCounts) {
var result = [],
current = deeperCopy(pieceCounts);
function deeperCopy(arr) {
return arr.map( row => Object.assign({}, row) );
}
function recurse(depth) {
// depth: indication of which value will be incremented. Each "row" has
// 2 items (black/white), so when depth is even, it refers to black, when
// odd to white. Divide by two for getting the "row" in which the increment
// should happen.
var idx = depth >> 1, // divide by 2 for getting row index
prop = depth % 2 ? 'currentWhite' : 'currentBlack', // odd/even
row = pieceCounts[idx];
if (!row) { // at the end of the array
// Take a copy of this variation and add it to the results
result.push(deeperCopy(current));
return; // backtrack for other variations
}
for (var value = row[prop]; value <= row.max; value++) {
// Set the value of this property
current[idx][prop] = value;
// Collect all variations that can be made by varying any of
// the property values that follow after this one
recurse(depth+1);
// Repeat for all higher values this property can get.
}
}
recurse(0); // Start the process
return result;
}
// Sample input
var pieceCounts = [
{"id":1,"currentBlack":1,"currentWhite":0,"max":1},
{"id":2,"currentBlack":0,"currentWhite":1,"max":1},
];
// Get results
var result = allPossibleCounts(pieceCounts);
// Output
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
The idea is to use recursion: imagine the problem can be solved for all variations that can be made for all properties, except the first one. Produce those, and then change the first property value to the next possible value. Repeat again the production of all variations, etc. The combination of all those results together will be the solution for when the first property value should also be varied.
This is an ideal situation for recursion. The recursion stops when there are no more property values remaining: in that case there is only one solution; the one with all the values set as they are. It can be added to the list of results.
The properties can be enumerated like this:
row currentBlack currentWhite
---------------------------------
0 0 1
1 2 3
2 4 5
3 6 7
...
n 2n-2 2n-1
We could call that number depth, and increase it at every step of deeper recursion. Given a depth, the property to vary is defined by:
depth is even => currentBlack
depth is odd => currentWhite
row number = depth / 2 (ignoring the remainder)