I have an ActionScript 3 array that lists pairs of items like this:
pairs[0] = Array('ItemA', 'ItemB');
pairs[1] = Array('ItemA', 'ItemC');
pairs[2] = Array('ItemC', 'ItemD');
pairs[3] = Array('ItemC', 'ItemE');
pairs[4] = Array('ItemF', 'ItemG');
pairs[5] = Array('ItemF', 'ItemH');
And I need to loop over the array in some way to find all overlapping pairs (any pairs that share common pairs).
For example, ItemA is paired with ItemB and ItemC, so they belong in a group together. ItemC is also paired with ItemD and ItemE so they also need to be a part of the first group.
ItemF, ItemG and ItemH do not overlap with any of the items fromt he first group, so they need to be put into their own group.
The resulting array would need to be something like:
groups[0] = Array('ItemA', 'ItemB', 'ItemC', 'ItemD', 'ItemE');
groups[1] = Array('ItemF', 'ItemG', 'ItemH');
Thanks for any help and suggestions!
Edit:
A little bit of a back story; I'm trying to group together movie clips that overlap each other in 2D to create groups or clusters (might be a better word).
So if I have 3 movieclips on the stage and ClipA overlaps with ClipB and ClipB overlaps ClipC (but ClipA doesn't directly overlap ClipC) they should all be grouped together as they are all a part of the same cluster. This way should a new clip overlap any single item in a cluster, it will be added to that group's array.
I've already got the code worked out to find overlapping elements which is producing this pairs list, now I need to condense it into tidy groups.
An algorithm like the example below should work.
NOTE: This is not the most efficient or concise way to write this code (it's certainly more repetitive than it needs to be), but I wanted to keep it clear and simple for this example. [Also, I haven't tested this code--it's presented as pseudo-code only--so if you find an error, please just let me know, and I'll fix it]
var idx:Object = new Object;
var groups:Array = new Array();
for( var i:int = 0; i<pairs.length; ++i ) {
var onePair:Array = pairs[i];
// which groups do the two items belong to?
var g1:Array = idx[onePair[0]];
var g2:Array = idx[onePair[1]];
if( !g1 ) {
// if item #1 is not yet in a group, then add it to item #2's
// existing group, or if neither group exists yet, just create a new one
g1 = g2;
if( !g1 ) {
g1 = [];
groups.push(g1);
}
g1.push( onePair[0] );
// ensure that the idx properly reflects the location of the new item
idx[onePair[0]] = g1;
}
// now do the same for the second item... but g1 will never be null, so
// this case is a little simpler.
if( !g2 ) {
g2 = g1;
g2.push( onePair[1] );
idx[onePair[1]] = g2;
}
if( g1 != g2 ) {
// now, if they're not already the same group, then merge the two
// groups, and update the idx to reflect the merge.
for( var z:int=0; z<g2.length; ++z ) {
idx[g2[z]] = g1;
g1.push( g2[z] );
g2.splice(0);
}
}
}
groups will end up being an array of arrays, just like you asked for -- but there will be a few empty arrays that can be discarded. Just prune (or ignore) the empty ones, and you'll have your groups.
the basic idea here, is that idx provides a lookup table that indicates, throughout the indexing process, for any given item, which group it's in (if any). This allows us to determine whether an item has been encountered previously or not, and if so, to utilize it's existing group.
You can use an Object to keep the track of the association of a pair iten and a group, the key will be each item of your pair.
Here a litle snippet that make the works :
var pairs:Array=[];
pairs[0] = ['ItemA', 'ItemB'];
pairs[1] = ['ItemA', 'ItemC'];
pairs[2] = ['ItemC', 'ItemD'];
pairs[3] = ['ItemC', 'ItemE'];
pairs[4] = ['ItemF', 'ItemG'];
pairs[5] = ['ItemF', 'ItemH'];
// will contain group created
var groups:Array=[];
// will contain association between a pair item and a group
var pair2group:Object={};
// function that turn pairs into groups
function makeGroups(pairs:Array):void{
var pairLen:int = pairs.length;
for (var i:int=0;i<pairLen;i++){
var pair:Array = pairs[i];
var item1:String = pair[0];
var item2:String = pair[1];
var group:Array = pair2group[item1];
// is first pair item already in a group
if (group == null) {
// no so create a new group
group=[];
// create the association
pair2group[item1] = group;
// add the item to the group we have created
group.push(item1);
// add it to all the groups
groups.push(group);
}
// is the second pair item into a grouo
if (pair2group[item2] == null) {
// no so add it to the group where the first item belong
group.push(item2);
// create the association for the second item
pair2group[item2] = group;
}
}
}
// ---- test
makeGroups(pairs);
trace(groups.length);
trace(groups[0]);
trace(groups[1]);
After lots of playing around here's the solution I came up with.
This will take an 2D overlapArray that has pairs and produce a group list with unique values.
I used a in_array() function to duplicate PHP's handy function for finding if an item is already in an array.
for each(var pair:Array in overlapArray) {
var pairInGroup = false;
for each(var group:Array in overlapArrayGroups) {
if(in_array(pair[0],group) || in_array(pair[1],group)) {
if(!in_array(pair[0],group)) {
group.push(pair[0]);
}
if(!in_array(pair[1],group)) {
group.push(pair[1]);
}
pairInGroup = true;
}
}
if(!pairInGroup) {
overlapArrayGroups.push(pair);
}
}
The in_array() function:
public static function in_array( needle:String, haystack:Array ):Boolean {
for( var a = 0; a < haystack.length; a++ ) {
if( haystack[a] == needle ) {
return true;
} else if( haystack[a] is Array ) {
return in_array(needle, haystack[a]);
}
}
return false;
}
Related
I am a stuck on this piece of code. I have an Example sheet with two tabs. The first tab is new items. A new item is comprised of two pieces, a attribute code (string), and an item ID (number). In the other tab "Locations" there are a bunch of empty locations. Each location has primary attribute (string), and a set of secondary attribute codes in a longer string.
I have assigned these two ranges to two unique arrays.
function matchcodes() {
var locss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Locations');
var lastlocRow = locss.getLastRow();
var newitems = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('New Items');
var lastNIRow = newitems.getLastRow();
var itemcodes = newitems.getRange("A1:B" + lastNIRow).getValues();
var locations = locationssheet.getRange("A2:D" + lastlocationRow).getValues();
Logger.log(itemcodes)
Logger.log(locations)}
What I am attempting to do is compare itemcodes[i][0] to locations[j][2] (match item attribute with location primary attribute). If the strings match I want to copy itemcodes[i][1] (ItemID) and set it as the value of locations[j][1]. If the strings do not match check the next iteration of locations[j][2].
If no matching attributes are found in locations[j][2], I would like to see if it is contained as a substring in locations[j][3] (starting back at the top and iterating through the whole list of secondary attributes. If the substring code is contained in loactions[j][3] I would like take the same action in the first IF condition.
Once a new item is matched, the loop can break, and the next item can be located itemcodes[i+1][0]. If no match is found in the primary or secondary search, also iterate to the next new item.
Where I'm struggling is writing the condition statements to compare both strings and substrings within strings.
//for (var i = 0; i < itemcodes.length; i++) {
//for (var j = 0; j < locations.length; j++) {
//if (itemcodes[i][0] == locations[j][2]) {
// I want set the value of locations[j][1] with itemcodes[i][1]
}
// if no match is found in entire [j][2] column, search for substring in locations[j][3] column
//if item match is found, or no match is found in all of [j][2] or [j][3] break loop and iterate to [i+1][0] and start the next loop
Input (3 iterations)
Results
Any help would be much appreciated. Or if you can point me to a similar thread. (I've not had any success finding a similar example) Thanks in advance!
Try:
function matchCodes() {
const newItems = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(`New Items`)
const locations = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(`Locations`)
const newItemsValues = newItems.getDataRange().getValues()
const locationsValues = locations.getDataRange().offset(1, 0).getValues()
newItemsValues.forEach(([attribute, id]) => {
const primaryTarget = locationsValues.findIndex(row => row[2] === attribute && row[1] === ``)
if (primaryTarget !== -1) return locationsValues[primaryTarget][1] = id
const secondaryTarget = locationsValues.findIndex(row => row[3].includes(attribute) && row[1] === ``)
if (secondaryTarget !== -1) return locationsValues[secondaryTarget][1] = id
})
locations.getDataRange().offset(1, 0).setValues(locationsValues)
}
Learn More:
Array.findIndex()
Destructuring Assignment
I have a set of data in a Google spreadsheet in two columns. One column is a list of article titles and the other is the ID of a hotel that is in that article. Call it list1.
Example data
I would like returned a new list with article titles in one column, and an array of the hotel IDs in that article in the other column. Call it list2.
Example data
There are thousands of lines that this needs to be done for, and so my hope was to use Google Apps Script to help perform this task. My original thinking was to
Create column 1 of list2 which has the unique article titles (no script here, just the G-sheets =unique() formula.
Iterate through the titles in list2, looking for a match in first column of the list1
If there is a match:
retrieve its corresponding value in column 2
push it to an empty array in column two of list2
move onto next row in list1
if no longer a match, loop back to step 2.
I've written the following code. I am currently getting a type error (TypeError: Cannot read property '0' of undefined (line 13, file "Code")), however, I wanted to ask whether this is even a valid approach to the problem?
function getHotelIds() {
var outputSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('list2');
var lastRow = outputSheet.getLastRow();
var data = outputSheet.getRange(2,1,lastRow,2).getValues();
var workingSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('list1');
var lastActiveRow = workingSheet.getLastRow();
var itemIDS = [];
for (var i=1; i<=data.length; i++) {
var currentArticle = data[i][0];
var lookupArticle = workingSheet[i][0];
if (currentArticle === lookupArticle) {
var tempValue = [workingSheet[i][1]];
itemIDS.push(tempValue);
}
}
}
Use a simple google sheets formula:
You can use a very simple formula to achieve your goal instead of using long and complicated scripts.
Use =unique(list1!A2:A) in cell A2 of list2 sheet to get the unique hotels.
and then use this formula to all the unique hotels by dragging it down in column B.
=JOIN(",",filter(list1!B:B,list1!A:A=A2))
You got the idea right, but the logic needed some tweaking. The "undefined" error is caused by the workingSheet[i][0]. WorkingSheet is a Sheet object, not an array of data. Also, is not necessary to get the data from list2 (output), it is rather the opposite. You have to get the data from the list1 (source) sheet instead, and iterate over it.
I added a new variable, oldHotel, which will be used to compare each line with the current hotel. If it's different, it means we have reached a different Hotel and the data should be written in list2.
function getHotelIds() {
var outputSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('list2');
var outLastRow = outputSheet.getLastRow();
var workingSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('list1');
var lastActiveRow = workingSheet.getLastRow();
var sourceValues = workingSheet.getRange("A2:B" + lastActiveRow).getValues();
var itemIDS = [];
var oldHotel = sourceValues[0][0]; //first hotel of the list
for (var i = 0; i < sourceValues.length; i++) {
if (sourceValues[i][0] == oldHotel) {
itemIDS.push(sourceValues[i][1]);
/*When we reach the end of the list, the oldHotel variable will never be different. So the next if condition is needed. Otherwise it wouldn't write down the last Hotel.
*/
if (i == sourceValues.length - 1) {
outputSheet.getRange(outLastRow + 1, 1, 1, 2).setValues([
[sourceValues[i][0], itemIDS.toString()]
]);
}
} else {
outputSheet.getRange(outLastRow + 1, 1, 1, 2).setValues([
[sourceValues[i - 1][0], itemIDS.toString()]
]);
oldHotel = sourceValues[i][0]; //new Hotel will be compared
outLastRow = outputSheet.getLastRow(); //lastrow has updated
itemIDS = []; //clears the array to include the next codes
}
}
}
I also converted the itemIDS array to a String each time, so it's written down in a single cell without issues.
Make sure each column of the Sheet is set to "Plain text" from Format > Number > Plain Text
References
getRange
setValues
toString()
I have a tableView(purchases cart), which include magazines in multiple sections. In section, we can see cells with stuff from magazine. In the cell, I did display UILabel(price), UILabel(count) and UILabel with summary price (item * count) in one cell. Also, we can see two buttons (plus and minus), for changing count of item. Example -
var count: Float = Float(cell.countLabel.text!)!
guard Int(count) > 1 else { return }
var shortPrice = cell.newPrice.text
shortPrice?.removeLast(2)
let floatPrice = Float(shortPrice!)
count -= 1
let newSumShortPrice = floatPrice! * count
cell.countLabel.text = String(Int(count))
cell.summPrice.text = "\(newSumShortPrice) ₽"
But changes didn't work with an array.
The strcuct of my model -
struct ViewModel {
var name: String?
var offers: [Offers]?
}
struct Offers : Mappable {
var count : Int?
var fullPrice : String?
var shortPrice : String?
}
var purchasesViewModel = [PurchaseList.Fetch.ViewModel]()
I know, that I must pass changed data (count) to my array and use method tableView.reloadData(). But I can't, because I don't know how to do that.
How I can transfer new count value (check struct Offers) to array purchasesViewModel?
You can go to the index of the array and delete the particular data(old data) from purchasesViewModel and recreate new data and insert it on that index. I hope this will work.
I am looking to use angular.equals on two arrays which contain objects, but in order to be more efficient, I want to just compare based on a single id token within each object. What would be a good way to do this in Angular?
To give an example,
Array A1 = [obj1, obj2, obj3] and
Array A2 = [obj4, obj5, obj6].
I want say that A1 = A2, if obj1.id = obj4.id, and obj2.id = obj5.id and obj3.id = obj6.id
you can use the map pattern to extract out the ids to compare, and then determine if the id lists are the same.
function compareById(a, b) {
var aIds = a.map(function(x) { return x.id; } ).unique().sort();
var bIds = b.map(function(x) { return x.id; } ).unique().sort();
return aIds.length == bIds.length &&
aIds.every(function(e,i) { return bIds.indexOf(e) == i; });
}
I need help in keeping track of objects in an array. I have tried giving each object an arrayIndex var, so I can splice by getting that var which represents the index in the Array.
object0.arrayIndex = 0;
object1.arrayIndex = 1;
object2.arrayIndex = 2;
object3.arrayIndex = 3;
But this is problematic if you move objects to different arrays. Objects would move from different places and therefore the arrayIndex var needs to be constantly updated.
I have done this by adding an static ID to each object. With a for loop I check each object for the corresponding object ID I want to splice
var objectID:Number = objectArrayTarget.id;
for (var t:int; t<_objectArrayLayer1.length; t++)
{
if (objectID == _objectArrayLayer1[i].id)
{
var indexOfObject:Number = (_objectArrayLayer1.indexOf(_objectArrayLayer1[i]));
}
}
_objectArrayLayer1.splice(indexOfObject, 1);
While this works is there a more efficient way of keeping track of objects in an Array? With 100+ objects this might create some slowdown
P.S. These objects are getting spliced and then pushed to a new array.
If your "Objects would move from different places" means same object from one place to another place, there is no arrayIndex needed
var indexOfObject:Number = _objectArrayLayer1.indexOf(targetObj);
if (indexOfObject >= 0) {
_objectArrayLayer1.splice(indexOfObject, 1);
}
If it means different object, like a copy, you could compare some properties to get the targetObj
for (var t:int = 0; t<_objectArrayLayer1.length; t++)
{
if (targetObj.id == _objectArrayLayer1[i].id)//assume id is unique key of the object
{
break;//i is the index here
}
}
if (i != _objectArrayLayer1.length) {//find target object
}
If the object type has a unique key, like a id, or you can make a unique key with some properties of the object, like name + "_" + order, you could use dictionary, Like Patel mentioned.
var dic:Dictionary = new Dictionary(true);
dic[obj1.id] = obj1;
dic[obj2.id] = obj2;//assume id the unique key,or you can use other key
So you can find obj like this
var obj:Object = dic[target.id]
Instead of using an Array.
I think you should use a Set implementation like Hashset
You'll get constant-time lookup, no sorting required,you can add,remove and lookup for object.