Set showing duplicate data as well in Apex - salesforce

I created a set which is showing duplicate data. How is it possible?
Set<Account> s = new Set<Account>();
Account a1 = new Account(Name='Hello1',NumberofEmployees=20);
Account a2 = new Account(Name='Hello2',NumberofEmployees=20);
Account a3 = new Account(Name='Hello3',NumberofEmployees=20);
Account a4 = new Account(Name='Hello4');
Account a5 = new Account(Name='Hello1');
s.add(a1);
s.add(a2);
s.add(a3);
s.add(a4);
s.add(a5);
System.debug(s);
a4.NumberOfEmployees = 30;
a5.NumberOfEmployees = 20;
System.debug(s);
System.debug('----------Loop---------');
for (Account acc: s) {
System.debug(acc);
}
Debug Report
DEBUG|{Account:{Name=Hello1}, Account:{Name=Hello1, NumberOfEmployees=20}, Account:{Name=Hello2, NumberOfEmployees=20}, Account:{Name=Hello3, NumberOfEmployees=20}, Account:{Name=Hello4}}
DEBUG|{Account:{Name=Hello1, NumberOfEmployees=20}, Account:{Name=Hello2, NumberOfEmployees=20}, Account:{Name=Hello3, NumberOfEmployees=20}, Account:{Name=Hello4, NumberOfEmployees=30}}
DEBUG|-------Loop--------
DEBUG|Account:{Name=Hello1, NumberOfEmployees=20}
DEBUG|Account:{Name=Hello2, NumberOfEmployees=20}
DEBUG|Account:{Name=Hello3, NumberOfEmployees=20}
DEBUG|Account:{Name=Hello4, NumberOfEmployees=30}
DEBUG|Account:{Name=Hello1, NumberOfEmployees=20}
How is it showing duplicate values in loop like hello1 as set is suppose to store only unique data.

It isn't an error. In order to know if an element is duplicated or not (when calling add, contains, etc...), the set checks its hashCode.
An HashSet stores its element into an array, using the element's hashCode as index.
a1 and a5 have different hashCode when you've added them to the set, so there is no collision.
By changing any property of the object its hasCode change, but the position in the set don't. Indeed set.contains will return false if you do so.
Set<Account> s = new Set<Account>();
Account a1 = new Account(Name='Hello1',NumberofEmployees=20);
s.add(a1);
a1.Site = 'test';
// the hashCode's changed so the set doesn't find a1...
System.assertEquals(false, s.contains(a1));
// ...and it can be added again to the set in a different position.
s.add(a1);
System.assertEquals(2, s.size());
Account a2 = new Account(Name='Hello1', NumberofEmployees=20, Site='Test');
// a2 has the same hashCode of a1, so you "find" it...
System.assertEquals(true, s.contains(a2));
// ...and if you add a2 to the set, the size will not change
s.add(a2);
System.assertEquals(2, s.size()); // no duplicate element has been added to the set
That's why you should prefer primitive types or immutable objects over mutable ones as keys for a Map.

Related

Google Apps Script - Randomizing Dropdown - Selecting Index

What I have seems simple, but looking through SO and other sites hasn't let me figure it out.
H3 and S3 are cells with a dropdown data validation list.
I have a button to randomize my these. This is easy when it's a number, but these are strings.
S3 is the cell for size so I have this randomized.
var randSize = Math.floor(Math.random() * (20 - 1 + 1)) + 1
ss.getRange("S3").setValue(randSize)
I don't understand how to either set a random int to select the value based on array index. Or get a random string value based on the entries in cell H3, then setting H3 to equal the new string.
Name | #
random item | random #
I need a random item to be set randomly from a value from H3.
Issue and solution:
If I understand you correctly, you have a set of string values in a column, and you want to copy a random value from that column to the cell H3.
If that's correct, you can just do the following:
Retrieve the column values to a simple array via getValues() and flat.
Retrieve a random value from that array, using length and Math.
Set that value to your target cell, using setValue.
Code sample:
function getRandomValue() {
const ss = SpreadsheetApp.getActiveSpreadsheet(); // If standalone, use openById/openByUrl
const sourceSheet = ss.getSheetByName("COLUMN_VALUES_SHEET_NAME");
const targetSheet = ss.getSheetByName("H3_SHEET_NAME");
const COLUMN_INDEX = 3; // Change according to your preferences
const FIRST_ROW = 2; // Change according to your preferences
const NUM_ROWS = sourceSheet.getLastRow() - FIRST_ROW + 1;
const array = sourceSheet.getRange(FIRST_ROW, COLUMN_INDEX, NUM_ROWS).getValues().flat();
const randomIndex = Math.floor(Math.random() * array.length);
const randomValue = array[randomIndex];
targetSheet.getRange("H3").setValue(randomValue);
}

Loop function to fill Crosselling template

I need to create a file containing information on crosselling for my webshop.
In my example file you'll see the tab "Basic Data", this is the data available to me already. Column A contains certain products, column B shows the assigned (product)categories.
The Tab "Starting Point" shows how the final file will be structured and how the function should come into play. It would need to do the following:
Copy the first product from the Unique product list (D2) to A2 (already done here)
Paste a filterfunction into B2 (already done here)
This filterfunction lists all products that belong to the same category like Product 1 except for Product 1 itself
Apply a numerical position tag in tens in Column C to the whole range of products related to Product 1 (in this case B2:B4), starting from 10 (..20, 30, ff) and optimally randomize it. (already done here)
Drag down A2, respectively paste "Product 1" into all cells below A2 until the end of the result of the filterfunction in Columns B is reached (already done here).
Continue the loop by pasting "Product 2" into A5, pasting the filterfunction into B5 and so on.
In "Desired Result" you can see how the end result should look like in this example. There are only 8 products, but I'd need to be able to do this for hundreds of products, that's why a function is needed.
I hope somebody is able to help me here.
Answer
You can get your desired result using Google Apps Script as I suggested.
How to use it:
Open Apps Script clicking on Tools > Script editor and you will see the script editor. It is based on JavaScript and it allows you to create, access, and modify Google Sheets files with a service called Spreadsheet Service.
Paste the following code and click on Run
Code
function main() {
const ss = SpreadsheetApp.getActiveSpreadsheet()
const sheetResult = ss.getSheetByName('Desired Result')
const sheetBasic = ss.getSheetByName('Basic Data')
// write unique products
const val = "=ARRAYFORMULA(UNIQUE('Basic Data'!A2:A))"
sheetResult.getRange('D2').setValue(val)
// get basic data
const lastColumn = sheetBasic.getRange('A:A').getValues().filter(String).length; // number of products
const cat = sheetBasic.getRange('A2:B'+lastColumn).getValues() // product | category
const productGroup = [] // array to store data
// loop each product
for (var j=0; j<cat.length; j++){
const p1 = cat[j]
var k = 1
for (var i=0; i<cat.length; i++){
if (cat[i][1] == p1[1] && cat[i][0] != p1[0]){
const val = [p1[0],cat[i][0],k*10]
k = k + 1
productGroup.push(val)
}
}
}
var n = productGroup.length+1
sheetResult.getRange('A2:C'+n).setValues(productGroup)
}
Some comments
This solution does not randomize the position value. If you need it I can help you with that, but first I want to check that this solution fits you.
Let me know if you can obtain your desired result. Keep in mind that this solution uses the name of the sheets.
Reference
Google Apps Script
Spreadsheet Service
JavaScript

Google Apps Script: how to create an array of values for a given value by reading from a two column list?

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

Pasting values in a different column depending on a Data Validation drop-down selection

Suppose I have the following setting on a Google Sheet:
Column A containing a list of different items (Apple/Melon/Grapes), column B containing Data Validation drop-down menus with either option 1 or 2.
What I want is that if I select option 1 for any of the items, the value of the corresponding cell in column A is going to pasted in D2. If I select option 1 for another item, the value will be pasted in D3, and so forth, thus building a secondary list without leaving any blank cells in between. If I select option 2, the item should be ignored.
Ideally, the order of the items in column D would follow my actions chronologically, i.e. if the item in A3 is the first item I select option 1 for, then it shall be on the top of the column D list at all times, even if later on I select option 1 for A1 as well (which shall then sit on the second position of the D list).
Is it possible to be achieved?
You can do this via Apps Script with a simple onEdit trigger, using its event object.
Select Tools > Script editor to open the Apps Script editor.
Copy this function and save the project (check inline comments):
function onEdit(e) {
// Get the range that was edited (column, row, sheet, value):
var range = e.range;
var sheet = range.getSheet();
var col = range.getColumn();
var row = range.getRow();
var value = range.getValue();
var sheetName = "Sheet1"; // Name of your sheet (the tab) (please change if necessary)
// Check that edited column is B, edited value is 1, and edited sheet is "Sheet1":
if (sheet.getName() === sheetName && col === 2 && value === 1) {
// Get length of non-empty cells in column D:
var columnD = sheet.getRange("D:D").getValues().map(function(item) {
return item[0];
}).filter(function(item) {
return item !== "";
});
var firstEmptyRow = columnD.length + 1; // First empty row in column D
var itemToCopy = sheet.getRange(row, 1).getValue(); // Get value from column A to copy
sheet.getRange(firstEmptyRow, 4).setValue(itemToCopy); // Write value to column D
}
}
Now, every time you edit column B and the edited value is 1, the corresponding option from column A will get copied to column D.
Reference:
onEdit
Event objects: Edit
delete range D2:D and paste in D2 cell:
=FILTER(A:A; B:B=1)

How to build multi-dimensional arrays in the appropriate orientation. (Rows/Cols in correct places)

I'm working on building a Google Sheets-based tool to calculate the cost of making various machined and fabricated parts. As it currently sits, there are about 60 different variables that I modify each time I build an estimate. Things like "number of parts," "length of bar to cut each part from," "cost/bar," "machining time," "machining rate," etc. All of these values I have populated on one sheet, and laid out in a way like. I want to make a button that takes a "snapshot" of all of these values, and stores them on another sheet for later reference. I'd then, ideally create another button, that allows me to re-populate all of the cells based off of a unique ID (such as Part #). This would let me tweak an estimate, or even refer back to material sizes etc in a meaningful way.
So far, I've created a "Named Range" for each of the values, so that as I change the layout, or add values, my script code should update accordingly, instead of using direct cell references.
I've built a few functions to get and set the value's of these named ranges. They're working as expected(i think) for what I'm trying to do. But when I try to place the array of Named Ranges inside of a multi-dimensional array of the named ranges WITH their respective values, I'm running into an issue where each named range is a ROW and their respective value is a second Column. And I need it swapped
I'm not super comfortable with multi-dimensional arrays and am thinking myself in circles trying to figure out how to transpose this logically. My gut says the way I'm attempting to build the arrays is my problem, not just how I'm iterating through them.
function saveCurrentValues(){
//set master spreadhseet
var ss = SpreadsheetApp.getActiveSpreadsheet();
//set calc and save sheets to vars
var calcSheet = ss.getSheetByName('Part Cost Calculator')
var saveSheet = ss.getSheetByName('Saved Parts');
//set named ranges from calcSheet to array
var namedRanges = calcSheet.getNamedRanges();
var savedValues = new Array();
//find next available row for save data (currently troubleshooting)
var nextAvailSaveRange = saveSheet.getRange(1, 1, 60, 2);
//iterate through array and call getNamedRange() function to return name and current value
for(i = 0; i < namedRanges.length; i++){
savedValues[i] = getNamedRange(namedRanges[i].getName());
}
nextAvailSaveRange.setValues(savedValues);
}
function getNamedRange(name){
var ss = SpreadsheetApp.getActiveSheet();
var value = ss.getRange(name).getValue();
Logger.log([name,value]);
return [name, value];
}
As you can see by how I had to temporarily format the nextAvailSaveRange, it needs 60 ROWS, and only two columns, because of how the array is constructed. I'd like to better understand how I'm creating this multi-dimensional array vertically instead of horizontally, and how to fix it!
Once this is done, I'd like to create headers that match the Named Ranges on my save sheet, to allow me to iterate through functions and look for a match to the appropriate column by name. That way if I add more values or change their order, or the order of the array, it wont matter. I think I'll be able to figure that out pretty easily if I can control these damn arrays better!
I agree with the OP. Array building AND iteration are the immediate problems and they are the stumbling block to the development of the spreadsheet.
The OP has a raised number of issues, however the most immediate, and the one to be resolved under this answer, is the copying of a list of parts from one sheet to another. In the OP's code, named ranges were retrieved and used as a basis for creating the copy of the list of parts. However, this also creates a duplicate set of named ranges on the target sheet. In my view this was unnecessarily complicating the duplication of the parts list since it is easy to programmatically create/update a list of named ranges.
The following code consists of three functions:
so_5466573501() - Copies the list of parts from one sheet to another.
Named Ranges are ignored; the OP's stumbling block is the iteration of the raw data and management of arrays. This code deals only with that aspect as a means of simplifying this issue.
createnamedranges() - Programmatically creates/updates Named ranges.
This code is included to assure the OP that it is not important to make named ranges the focus of the duplication by showing how easy it is to programmatically turn a list of parts into a series of Named Ranges (for development, I created 60 Parts and the entire code executes in under a 1 second). The code assumes a list in two columns (Column A = Parameter Name, Column B = Parameter value). The code loops through the list creating/updating a set of named ranges - the range name is the Parameter Name in Column A, and the range itself is the the corresponding row in Column B. The name of the sheet is set in a variable, so this function can be easily adapted.
deletenamedranges() - Programmatically deletes Named ranges.
This code deletes all the Named Ranges from a given sheet. This function is included because the OP's existing code creates duplicate named ranges, and it might be necessary to quickly delete them from a sheet. The sheet name is stored as a variable, so the function can be easily adapted.
function so_5466573501() {
//set master spreadsheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
//create variables for calc and save sheets
var calcSheet = ss.getSheetByName('Part Cost Calculator')
var saveSheet = ss.getSheetByName('Saved Parts');
//get the Parts Parameters from Part Cost Calculator
//var namedRanges = calcSheet.getNamedRanges();
//Logger.log("DEBUG: Number of named ranges on Parts Cost Calculator = "+namedRanges.length);
// get the number of parts in the list on Parts Cost Calculator
var Avals = calcSheet.getRange("A1:A").getValues();
var Alast = Avals.filter(String).length;
//Logger.log("DEBUG: Number of parts in the list: "+Alast); //DEBUG
// get the parts list
var partsRange = calcSheet.getRange(1, 1, Alast, 2);
var partsRangeValues = partsRange.getValues();
//Logger.log("DEBUG: The parts range is: "+partsRange.getA1Notation());//DEBUG
//Logger.log("DEBUG: Parts List Row #1: Name: "+partsRangeValues[0][0]+", Value: "+partsRangeValues[0][1]);//DEBUG
// create an array to use for saving results and updating new Saved Parts sheet
var savedValues = new Array();
// Loop through the Parts List, row by row
for (i = 0; i < Alast; i++) {
// push the part name and part value onto the array
savedValues.push([partsRangeValues[i][0], partsRangeValues[i][1]]);
//Logger.log("DEBUG: Parts List: i = "+i+", Name: "+partsRangeValues[i][0]+", Value: "+partsRangeValues[i][1]);//DEBUG
}
// identify the range on the Saved Parts sheet to copy the parts list array.
var saveRange = saveSheet.getRange(1, 1, Alast, 2);
saveRange.setValues(savedValues);
}
function createnamedranges() {
//set master spreadhseet
var ss = SpreadsheetApp.getActiveSpreadsheet();
//create variables for calc and save sheets
var calcSheetName = "Part Cost Calculator";
var calcSheet = ss.getSheetByName(calcSheetName);
// get the number of parts in the list on Parts Cost Calculator
var AVals = calcSheet.getRange("A1:A").getValues();
var ALast = AVals.filter(String).length;
// get the parts range and values
var partsRange = calcSheet.getRange(1, 1, ALast, 2);
//Logger.log("DEBUG: The Parts range is "+partsRange.getA1Notation());//DEBUG
var partsRangeValues = partsRange.getValues();
// Loop through the parts list row by row
for (var i = 0; i < ALast; i++) {
// get the Part name and assign as the range name
var nrpartname = partsRangeValues[i][0];
//Logger.log("DEBUG: PartName = "+nrpartname+", value: "+partsRangeValues[i][1]);//DEBUG
// get the range to be named -note (i+1) because the loop starts at 0 (zero) but `getrange` starts at 1 (one)
var rng_to_name = ss.getSheetByName(calcSheetName).getRange((i + 1), 2);
//Logger.log("DEBUG: rng_to_name: "+rng_to_name+", range details: "+rng_to_name.getA1Notation());
// set (and/or update) the named range
ss.setNamedRange(nrpartname, rng_to_name);
// DEBUG: check that the range was created //DEBUG
// var rangeCheck = ss.getRangeByName(nrpartname);//DEBUG
// var rangeCheckName = rangeCheck.getA1Notation(); //DEBUG
// Logger.log("DEBUG: Rangename: "+nrpartname+", Range: "+rangeCheckName);//DEBUG
// credit megabyte1024 https://stackoverflow.com/a/12325103/1330560 "setNamedRange() outside of the spreadsheet container?"
}
}
function deletenamedranges() {
//set master spreadhseet
var ss = SpreadsheetApp.getActiveSpreadsheet();
//create variables for calc and save sheets
var calcSheet = ss.getSheetByName('Part Cost Calculator');
// get the named ranges
var namedRanges = calcSheet.getNamedRanges();
// loop through the list of named ranges and delete them
for (var i = 0; i < namedRanges.length; i++) {
namedRanges[i].remove();
}
}
ADDENDUM: - Copy based on Named Ranges
The original so_5466573501 assumes that the parts are in a simple 2 column-list; in which case, Named Ranges are irrelevant.
The following code assumes that the parts are not in a list but scattered, in no particular order, throughout the sheet "Part Cost Calculator". This code is based on obtaining the NamedRanges, identifying the respective Named Range row and column, correlating said row and column to the ENTIRE data range, and then copying the results to the "Saved Parts" sheet. No Named Ranges are created by default on the "Saved Parts" sheet but this can be easily done by using the createnamedranges function (appropriately edited for the correct sheet name).
function so_5466573502() {
//set master spreadhseet
var ss = SpreadsheetApp.getActiveSpreadsheet();
//create variables for calc and save sheets
var calcSheet = ss.getSheetByName('Part Cost Calculator')
var saveSheet = ss.getSheetByName('Saved Parts');
//get the Parts Parameters from Part Cost Calculator
var namedRanges = calcSheet.getNamedRanges();
var numNR = namedRanges.length
//Logger.log("DEBUG: Number of named ranges on Parts Cost Calculator = "+numNR);
// get all the data
var dataRangeValues = calcSheet.getDataRange().getValues();
// create an array to temporarily store results
var resultsarray = [];
// Loop through the array of Named Ranges
for (var x = 0; x < numNR; x++) {
var nrName = namedRanges[x].getName();
var nrRange = namedRanges[x].getRange();
var nrRangerow = nrRange.getRow();
var nrRangecol = nrRange.getColumn();
var nrRangeValue = dataRangeValues[nrRangerow - 1][nrRangecol - 1];
//Logger.log("DEBUG: Named Range-Name: "+nrName+", Range: "+nrRange.getA1Notation()+", Row: "+nrRangerow+", Column: "+nrRangecol+", Value-"+nrRangeValue);//DEBUG
// populate the array with the part name and the part value
resultsarray.push([nrName, nrRangeValue]);
}
// identify the range on the Saved Parts sheet to copy the parts list array.
var saveRange = saveSheet.getRange(1, 1, numNR, 2);
saveRange.setValues(resultsarray);
// sort the results on "Saved Parts"
saveRange.activate().sort({
column: 1,
ascending: true
});
}

Resources