How to edit list elements in kotlin or add to second list? - list

I tried to edit a list in-place but wasn't able to get it working. Now trying to edit individual elements and add them to a second list. However the second list remains null and does not get updated. Any help would be appreciated.
var localFiles: MutableList<String> = File(localPath).list().toMutableList()
var localFileDates: MutableList<String>? = null
val iterateLocal = localFileDates?.listIterator()
for (item in localFiles) {
var date = item.takeLast(10).take(6)
if (date.matches("[0-9]+".toRegex()) and (date.length == 6) and (date != null) and (date != "null")) {
iterateLocal?.add(item.takeLast(10).take(6))
}
}
println(networkFiles) // prints correct outpu
println(localFileDates) // prints null

You need init localFileDates variable:
var localFileDates = MutableList()
var localFiles: MutableList<String> = File(localPath).list().toMutableList()
var localFileDates = MutableList<String>()
val iterateLocal = localFileDates?.listIterator()
for (item in localFiles) {
var date = item.takeLast(10).take(6)
if (date.matches("[0-9]+".toRegex()) and (date.length == 6) and (date != null) and (date != "null")) {
iterateLocal?.add(item.takeLast(10).take(6))
}
}
println(networkFiles) // prints correct outpu
println(localFileDates) // prints correct

It is better to use map{..} function to create a copy of the list with updated values.
val localFiles = File(localPath).list()?.toMutableList() ?: listOf()
val localFileDates = localFiles.mapNotNull { item ->
val date = item.takeLast(10).take(6)
if (date.matches("[0-9]{6}".toRegex()) {
date
} else {
null
}
}
println(localFiles)
println(localFileDates)
I use the mapNotNull{..} function calls the block for every element of the list and builds the new list only from non-null values.
You do not need var in your code, explicit type names can be omitted too.
The condition can be simplified - no need for the null check, the regular expression filters our the data == "null" case, the length check can be included into the regex too. The date variable can be re-used too.

A more idiomatic (and readable) way:
val localFileDates = File(localPath).list().map { it.takeLast(10).take(6) }.filter {
it.matches("[0-9]+".toRegex()) && (it.length == 6) && (it != "null")
}
Also I suggest you create a named function for takeLast(10).take(6) and the condition to make it clear what is the intent of these lines of code.

Related

If any row in range (G11:G25) contains boolean (true) then run function, else msgBox

The function I'm running (clearRowContents) in sheet 'Section 2' will clear contents and validation for any checked item (col H) in a list as well as the checkbox itself (col G). The remaining unchecked boxes and list items will then be sorted to clear any blank rows just created by the clearRowContents function. This functions works as tested.
However, if no item is checked (col G == false) and the "clear" button is pressed, how can I have a message pop up letting the user know that they must first check the box next to the item and then press the button to clear its contents from the list? I'm trying to figure out how to write the script for the clearItemMessage function.
Also, for script writing purposes, this sheet will be duplicated many times to create various validation menus for different topics... each sheet will be a different "chapter" in a manual with its own unique set of drop downs (in a MASTER DROPDOWN tab).
link to sheet: https://docs.google.com/spreadsheets/d/1ZdlJdhA0ZJOIwLA9dw5-y5v1FyLfRSywjmQ543EwMFQ/edit?usp=sharing
code:
function clearItemMessage(){
var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var checkboxRange = ss.getRangeList("$G$11:$G$25").getValues();
if (checkboxRange == true){
clearRowContents (col);
} else (Browser.msgBox("To delete items, select the box next to the items and then press the delete button."));
}
function clearRowContents (col){ // col is the index of the column to check for checkbox being true
var col = 7; //col G
var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var data = ss.getDataRange().getValues();
//Format font & size
var sheetFont = ss.getRange("A:Z");
var boxFont = ss.getRange("$G$11:$G$25");
var listFont = ss.getRange("$H$11:$H$25");
sheetFont.setFontFamily("Montserrat");
boxFont.setFontSize(8)
.setFontColor("#434343")
.setBackground("#ffffff");
listFont.setFontSize(12)
.setFontColor("#434343")
.setBackground("#ffffff");
//clear 'true' data validations
var deleteRanges = data.reduce(function(ar, e, i) {
if (e[col - 1] === true) {
return ar.concat(["H" + (i + 1), "G" + (i + 1)]);
}
return ar;
}, []);
if (deleteRanges.length > 0) {
ss.getRangeList(deleteRanges).clearContent().clearDataValidations();
}
//sort based on checkbox value
var range = ss.getRange("$G$11:$H$25");
range.sort({column: 7, ascending: false});
}
In your situation, how about modifying clearItemMessage() as follows?
Modified script:
function clearItemMessage(){
var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var checkboxes = ss.getRange("$G$11:$G$25").getValues();
if (checkboxes.filter(([g]) => g === true).length > 0){ // or if (checkboxes.some(([g]) => g === true)) {
clearRowContents();
} else {
Browser.msgBox("To delete items, select the box next to the items and then press the delete button.");
}
}
From your question, I understood your clearRowContents works. So I proposed to modify clearItemMessage.
In your clearRowContents, var col = 7 is used. So I think that function clearRowContents (col){ can be modified to function clearRowContents (){.
Reference:
filter()

Prevent item in list get deleted

I assign the onValue to two variables in initState.
_boc.selectList(locationId).then((onValue) {
list = onValue;
filterList = onValue;
setState(() {});
});
When it comes to this function, I clear the filterList, but the length of the list become 0.
Future<List<Obk>> getSuggestion(
String search) async {
if (search == "empty") return [];
if (search == "error") throw Error();
filterList.clear();
print(list.length); // this print 0
});
...
}
but if I remove this line filterList = onValue; , it will show the length of the list.
How to prevent the items in list from deleted?
Instead of assigned the same List Object to both list and filteredList. You can make a new list object using List.from(). So that your original List remains unchanged.
_boc.selectList(locationId).then((onValue) {
list = onValue;
filterList = List.from(onValue);
setState(() {});
});
Now if you try to run your code and print the list length then you'll not get 0 after calling filterList.clear().
Future<List<Obk>> getSuggestion(
String search) async {
if (search == "empty") return [];
if (search == "error") throw Error();
filterList.clear();
print(list.length); // this will print list length
});
...
}

Google Script - Removing Else statement properly so it skips/ignores

I have a checklist, column C indicates which test is automated by "enabled" or "disabled" written in the cells.
Further down the row is a column for the the Pass / Empty column for each test.
I have code that looks for if Enabled in column C, in X column on that row, mark as Pass automatically (or 'P' in my case).
The problem: If C column contains "Disabled" but also a Pass, when I run the script it replaces that Pass with an empty cell. How can I change the else statement to just ignore that cell and leave whatever is in it for anything but Enabled condition is met
function autoPassPC() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Aug 2020');
// What to put in the test result
var values1 = "P";
// Where to look for Auto:
var values2 = sheet.getRange("C10:C15" + sheet.getLastRow()).getValues();
// Keyword to look for in Auto: column
var putValues = [];
for (var i = 0; i < values2.length; i++) {
if (values2[i][0] === "Enabled") {
putValues.push([values1]);
} else {
putValues.push([""]);
}
}
// Put value1 inside row, column# for test result
sheet.getRange(10, 25, putValues.length, 1).setValues(putValues);
}
Basically how do I get rid of
} else {
putValues.push([""]);
}
properly? Just deleting this causes the script to put 'P' on every single row. Just want it to ignore the cells instead.
Thanks!
Removing the else statement will actually skip the row, but as soon as you skip a row, all following values in putValues will be on the wrong row. Instead of "skipping", try pushing the already existing value into putValues.
function autoPassPC() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Aug 2020');
// What to put in the test result
var values1 = "P";
// Where to look for Auto:
var enabledDisabled = sheet.getRange("C10:C15" + sheet.getLastRow()).getValues();
var testResultsRange = sheet.getRange("X10:X15" + sheet.getLastRow());
var testResults = testResultsRange.getValues();
// Keyword to look for in Auto: column
var putValues = [];
for (var i = 0; i < enabledDisabled.length; i++) {
if (enabledDisabled[i][0] === "Enabled") {
putValues.push([values1]);
} else {
putValues.push([testResults[i][0]]); // Push the existing cell value
}
}
// Put value1 inside row, column# for test result
testResultsRange.setValues(putValues);
}
You could also try getting the entire table at once thus having only one getValues() call.

parse and replace a list of object in kotlin

I am currently having a list of obeject defined as:
fun updateList(tools: List<Tool>, updateTools: List<Updated>){
... code below
}
the Tool data class is defined as:
data class Tool(
var id: String = ""
var description: String = ""
var assignedTo: String = ""
)
the Updated data class is defined as:
data class Updated(
var id: String = ""
var assignedTo: String = ""
)
Basically, I parse the list updateTools and if I found a id match in tools, I update the assignedTo field from the Tool type object from tools by the one from updateTools
fun updateList(tools: List<Tool>, updateTools: List<Updated>){
updateTools.forEach{
val idToSearch = it.id
val nameToReplace = it.name
tools.find(){
if(it.id == idToSearch){it.name=nameToReplace}
}
}
return tools
}
it's not working but I do not see how to make it easier to work. I just started kotlin and I feel that it's not the good way to do it
any idea ?
Thanks
First of all:
you're not assigning assignedTo, you're assigning name...
in the predicate passed to find, which
should only return a Boolean value to filter elements, and
should probably not have any side effects,
those should be done later with a call to i.e. forEach.
Additionally, your constructor parameters to the data class are normal parameters, and as such, need commas between them!
Your last code block, corrected, would be:
updateTools.forEach {
val idToSearch = it.id
val nameToReplace = it.name
tools.find { it.id == idToSearch }.forEach { it.assignedTo = nameToReplace }
}
return tools
I'd do it like this (shorter):
updateTools.forEach { u -> tools.filter { it.id == u.id }.forEach { it.assignedTo = u.name } }
This loops through each update, filters tools for tools with the right ID, and sets the name of each of these tools.
I use forEach as filter returns a List<Tool>.
If you can guarantee that id is unique, you can do it like this instead:
updateTools.forEach { u -> tools.find { it.id == u.id }?.assignedTo = u.name }
firstOrNull returns the first element matching the condition, or null if there is none. Edit: it seems find is firstOrNull - its implementation just calls firstOrNull.
The ?. safe call operator returns null if the left operand is null, otherwise, it calls the method.
For = and other operators which return Unit (i.e. void, nothing), using the safe call operator simply does nothing if the left operand is null.
If we combine these, it effectively sets the name of the first element which matches this condition.
First, you're missing comma after properties in your data classes, so it should be:
data class Tool(
var id: String = "",
var description: String = "",
var assignedTo: String = ""
)
data class Updated(
var id: String = "",
var assignedTo: String = ""
)
As for second problem, there're probably number of ways to do that, but I've only corrected your idea:
fun updateList(tools: List<Tool>, updateTools: List<Updated>): List<Tool> {
updateTools.forEach{ ut ->
tools.find { it.id == ut.id }?.assignedTo = ut.assignedTo
}
return tools
}
Instead of assigning values to variables, you can name parameter for forEach and use it in rest of the loop.

Numbered lists continues when Apps Script appends template-content into document

I have an Apps Script that copies the content of an template-file to the end of a document. It works with one minor annoyance: the numbered list continues from one copy to the next.
I have many different templates that users can append to the end of the document. Each template is stored in its own Document.
function addSub(template_id){
var mainBody = DocumentApp.getActiveDocument().getBody();
var tempBody = DocumentApp.openById(template_id).getBody();
for(var i = 0;i<tempBody .getNumChildren();i++){
var element = tempBody .getChild(i);
if(element.getType() == DocumentApp.ElementType.TABLE)
mainBody.appendTable(element.copy());
else if(element.getType() == DocumentApp.ElementType.PARAGRAPH)
mainBody.appendParagraph(element.copy());
else if(element.getType() == DocumentApp.ElementType.LIST_ITEM)
mainBody.appendListItem(element.copy());
else if(element.getType() == DocumentApp.ElementType.PAGE_BREAK)
mainBody.appendPageBreak(element.copy());
}
}
It could look like this: ( I want the list to reset for each new copy of the template)
table with name of this template
some raw text
List item1
List item2
table with name of this template
some raw text
List item1
List item2
Do you now that ListItems have an ID, which is a STRING and you can access it via myListItem.getListId().
It seems that all your ListItems have the same ID.
If this is the case, the numbering has to be as you described.
Why do they have the same ListID?
I don't know.
Seems that the body.appendListem method always chooses the same listId.
I didn't test it yet, but you could try to set the listID of the newly append ListItem to that of the original document, if they are different.
Yes, i know, the .copy() method should enclose this information, but the body.appendListItem method may not care.
So, you could try to first save the detached copy of the listItem.
Then append it to the new body.
And then set the id of the newly appended listItem to that of the detached copy.
It's stupid, i know, but it may help.
Didn't try it yet.
And i have little experience with listItems, bit what i saw up to now is that there seems to be only one ListId in the body of a document, if you append or insert listItems.
This could be the cause of the problem.
Hope this helps.
After Richard Gantz solved it, It was corrected by this code:
var listItemDictionary = {};//top
...
else if(element.getType() == DocumentApp.ElementType.LIST_ITEM){
var listCopy = element.copy().asListItem()
var lcID = listCopy.getListId();
if (listItemDictionary[lcID] == null){
var tempLI = mainBody.appendListItem("temp")
listItemDictionary[lcID] = tempLI;
}
Logger.log(lcID)
mainBody.insertListItem(childIndex+j, listCopy.setListId(listItemDictionary[lcID]));
}
...
if(listItemDictionary){//bottom
mainBody.appendParagraph("");
for(var key in listItemDictionary){
listItemDictionary[key].clear().removeFromParent()
}
}
Based on the answer from Niklas Ternvall/Richard Gantz, I found a simpler solution for the case of each template having no more than one list.
function addSub(template_id) {
var mainBody = DocumentApp.getActiveDocument().getBody();
var tempBody = DocumentApp.openById(template_id).getBody();
var listID = null;
for(var i = 0;i<tempBody.getNumChildren();i++){
var element = tempBody.getChild(i).copy();
var type = element.getType();
if(type == DocumentApp.ElementType.TABLE)
mainBody.appendTable(element);
else if(type == DocumentApp.ElementType.PARAGRAPH)
mainBody.appendParagraph(element);
else if(type == DocumentApp.ElementType.LIST_ITEM){
if(listID==null) // First list item
listID = mainBody.appendListItem('temp'); // Define new listID
mainBody.appendListItem(element).setListId(listID); // Apply to copy
}
else if(type == DocumentApp.ElementType.PAGE_BREAK)
mainBody.appendPageBreak(element);
}
mainBody.removeChild(listID); // Delete temporary list item
}
Each time you call the function, listID=null is the indicator for whether or not there have been any list items in the template. When you get to the first list item, appending the text 'temp' forces a new list and hence a new listID that you can apply to the list items from the template. After you finish going through the template, mainBody.removeChild(listID) removes 'temp' from the top of your list.
This solution worked for me when using one template 100 times in one document, essentially as a mail merge. I'm fairly new to Apps Script, so I would appreciate any feedback if there is a reason this wouldn't work for a single-list template.