Convert number input to characters in Google Sheets - google-sheets

How to convert a number entered in google sheets to some characters?
Example:
input in cell: “1234”
output format in cell: “ABCD”
So I’d like to have some logic that will loop through every digit and convert it to a corresponding character of my choice, and have that be what is displayed in the cell.
EDIT: The character <> number representation don't have to be sequential. So we could have 1:"A", 2:"Q", 3:"E" etc. And the conversion should happen in place then replace the input altogether, as if the string was entered initially and not the numbers. Not sure if that can be achieved without a script.

You'll need a script
You can take advantage of an onEdit trigger. This is something that allows a script to run every single time there is an edit on your sheet. Within the trigger, you can tell it to replace the contents of a cell if it meets certain criteria. For instance, if its in a certain range, or it contains a certain value.
Example:
const code = {
'A': 1,
'B': 2,
'C': 5,
'D': 6,
'E': 9,
'F': 8,
'G': 7,
'H': 3,
'I': 2,
'J': 45,
'K': 4,
'L': 32,
'M': 45,
'N': 5,
'O': 4,
'P': 3,
'Q': 6,
}
function onEdit(e) {
if (e.range.columnEnd === 1 &&
e.range.columnStart === 1) {
let letters = e.value.split("")
let numbers = letters.map(letter => code[letter])
let newValue = numbers.join("")
e.range.setValue(newValue)
}
}
Within the code dictionary, you need to define what you want the conversion to be.
Then within the onEdit function:
It checks if the change is in the first column.
If so, it takes the value (e.value) and splits it into an array. So if the value input into the cell was "ABC" then the resulting array would be ["A","B","C"].
Then it map the array using the code. This just goes through the array and returns whatever is in the dictionary. So now you have [1,2,5].
Then it join the resulting array into a continuous string "125"
Finally, it setValue using the e.range to the newValue.
Room for improvement
You need to make sure you have all the possible characters that someone will enter within the code object. Or you will need to handle the case of what to do with a character that is not in the object. For example, you could just return a space:
let numbers = letters.map(letter => {
if (code.hasOwnProperty(letter)) {
return code[letter]
} else {
return " "
}
You may have an issue with converting numbers to letters. Seeing as there are only 10 single digit numbers (0 - 9). For example, if you have A = 1 and B = 11. If the value in a cell was converted to 11 then you wouldn't know if the original value had been AA or B.
References and further reading
onEdit()
Range
setValue(value)
Event objects

try:
=INDEX(JOIN(, CHAR(64+REGEXEXTRACT(A1&"", JOIN("|", REPT("(.)", LEN(A1)))))))
suggested approach:
=INDEX(JOIN(, CHAR(64+SPLIT(A3, " "))))

Related

Is it possible to remove duplicates in Dart with another variable

I have searched a lot for removing duplicates from a list in Dart using ANOTHER variable.
Here is what I mean:
List<int> numbers = [1, 2, 3, 4];
// This list has 4 new elements than the first one
List<int> moreNumbers = [1, 2, 3, 4, 5, 6, 7, 8];
// Now I want to push the moreNumbers unique elements to the numbers one
I want to push it so the end result for the numbers variable should be:
[1, 2, 3, 4, 5, 6, 7, 8];
Is it possible?
void main() {
var lst = [1,2,3,4];
var lst2 = [1,2,3,4,5,6,7,8];
var s = {...(lst+lst2)};
print(s.toList());
}
The trivial approach would be:
for (var number in moreNumbers) {
if (!numbers.contains(number)) {
numbers.add(number);
}
}
Not particularly efficient if numbers is long, because contains on a list can take time proportional to the length of the list.
The time/space trade-off would be creating a set from numbers, because sets have cheap contains:
var alsoNumbers = numbers.toSet(); // Also linear, but only happens once.
for (var number in moreNumbers) {
if (alsoNumbers.add(number)) { // true if elements was added
numbers.add(number);
}
}
(Using add instead of contains ensures that you update the set with new values, so you won't add the same new value twice.)
If you could just make numbers a Set to begin with, it would be much easier to avoid duplicates, just do numbers.addAll(moreNumbers).

Google sheets - Extract numbers with their units of measurment

I want a function that can extract numbers with their units of measurment from a text.
For example in A2 i have:
This box weights 5kg and the other box weights 10 kg.
So i want a function that will return:
5kg 10kg
NOTE: I want the function to work with any unit of measurment, not just "kg".
I am a begginer in google sheets so it would be really helpful if you could provide me with a working function.
You can use this sample custom function that extracts words that starts with a number followed by a character/s.
/**
* #customfunction
*/
function EXTRACTMEASUREMENT(input) {
// match all words which starts with a number
var result = input.match(/\d+[a-zA-Z]+\S*/g)
// combine array into a string separated with spaces
result = result.join(' ');
// Remove special characters(except whitespace) in the string
result = result.replace(/[^\/a-zA-Z0-9\s]/g, '')
return result;
}
Output:
Limitations:
Measurements with spaces between the value and the unit cannot be detected. (See result in cell A5 when space exist in 10 kg.)
Regardless whether the character/s after the number is a valid unit or not, it will be extracted. (See result in cell A5 where 20yy is not a valid measurement unit)
If you want to exempt particular characters not to be removed, you can add them in the braces [^\/a-zA-Z0-9\s] (example / will not be removed).
Note:
This can be improved if you can list valid measurement units that should be supported.
Try
=arrayformula(substitute(transpose(query(flatten(split(
REGEXREPLACE(A1,"([0-9.,/]+[ ]{0,1}[a-z1-3/.\-""]+)","♣♦$1♣")
,"♣")),"select * where Col1 like '♦%' ")),"♦",""))
One more option:
=ArrayFormula(IF(LEN(A:A),
SPLIT(
REGEXREPLACE(
REGEXREPLACE(A:A,"("&
REGEXREPLACE(
REGEXREPLACE(
REGEXREPLACE(A:A,"(\d+[.,/]*\d*(?:\w+|\s\w+)[\""./\-\w+]*)",""),
"\s+\.","|\\."),
"\s+","|")
&")",""),
"(\d)\s","$1")
," ",,1)
,))
try:
=INDEX(SPLIT(FLATTEN(QUERY(TRANSPOSE(IFERROR(SUBSTITUTE(
REGEXEXTRACT(SPLIT(REGEXREPLACE(A1:A, "(\d+.\d+|\d+)", "×$1"), "×"),
TEXTJOIN("|", 1, {"\d+.\d+ ","\d+.\d+","\d+ ","\d+"}&
SORT({"nm";"mm";"cm";"dm";"km";"m";"t";"kg";"dg";"g";"l";"ml";"dl"},
LEN({"nm";"mm";"cm";"dm";"km";"m";"t";"kg";"dg";"g";"l";"ml";"dl"}), 0))),
" ", ))),,9^9)), " "))
update:
all in one cell:
=INDEX(TRIM(FLATTEN(QUERY(TRANSPOSE(IFERROR(SUBSTITUTE(
REGEXEXTRACT(SPLIT(REGEXREPLACE(A1:A, "(\d+.\d+|\d+)", "×$1"), "×"),
TEXTJOIN("|", 1, {"\d+.\d+ ","\d+.\d+","\d+ ","\d+"}&
SORT({"nm";"mm";"cm";"dm";"km";"m";"t";"kg";"dg";"g";"l";"ml";"dl"},
LEN({"nm";"mm";"cm";"dm";"km";"m";"t";"kg";"dg";"g";"l";"ml";"dl"}), 0))),
" ", ))),,9^9))))

Finding lowest value with no overlapping dates

I have a spreadsheet with criteria, a start and end date, and a value. The goal is to find the lowest value for each unique criteria and start date without overlapping dates (exclusive of end date). I made a pivot table to make it easier for myself but I know there is probably a way to highlight all valid rows that meet the above requirements with some formula or conditional formatting.
I have attached a google drive link where the spreadsheet can be found here and I have some images of the sheet as well. I know that it might be possible with conditional formatting but I just don't know how to combine everything I want it to do in a single formula.
Example below:
Row 2 is a valid entry because it has the lowest value for Item 1 starting on 03-15-2021, same with row 9.
Row 5 is valid because the start date does not fall within the date range of row 2 (exclusive of end date)
Row 7 is not valid because the start date is between the start and end date of row 6
You may add a bounded script to your project. Then you can call it either with a picture/drawing that has the function assigned (button-like), or adding a menu to Google Sheets.
From what you said in the question and the comments, this seems to do what you are trying. Notice that this requires the V8 runtime (which should be the default).
function validate() {
// Get the correct sheet
const spreadsheet = SpreadsheetApp.getActiveSpreadsheet()
const sheet = spreadsheet.getSheetByName('Sheet1')
// Get the data
const length = sheet.getLastRow() - 1
const range = sheet.getRange(2, 1, length, 4)
const rows = range.getValues()
const data = Array.from(rows.entries(), ([index, [item, start, end, value]]) => {
/*
* Row Index
* 1 Criteria 1
* 2 Item 1 0
* 3 Item 1 1
* 4 Item 1 2
*
* row = index + 2
*/
return {
row: index + 2,
criteria: item,
start: start.getTime(),
end: end.getTime(),
value: value
}
})
// Sort the data by criteria (asc), start date (asc), value (asc) and end date (asc)
data.sort((a, b) => {
let order = a.criteria.localeCompare(b.criteria)
if (order !== 0) return order
order = a.start - b.start
if (order !== 0) return order
order = a.value - b.value
if (order !== 0) return order
order = a.end - b.end
return order
})
// Iterate elements and extract the valid ones
// Notice that because we sorted them, the first one of each criteria will always be valid
const valid = []
let currentCriteria
let currentValid = []
for (let row of data) {
if (row.criteria !== currentCriteria) {
// First of the criteria
valid.push(...currentValid) // Move the valids from the old criteria to the valid list
currentValid = [row] // The new list of valid rows is only the current one (for now)
currentCriteria = row.criteria // Set the criteria
} else {
const startDateCollision = currentValid.some(valid => {
row.start >= valid.start && row.start < valid.end
})
if (!startDateCollision) {
currentValid.push(row)
}
}
}
valid.push(...currentValid)
// Remove any old marks
sheet.getRange(2, 5, length).setValue('')
// Mark the valid rows
for (let row of valid) {
sheet.getRange(row.row, 5).setValue('Valid')
}
}
Algorithm rundown
We get the sheet that we have the data in. In this case we do it by name (remember to change it if it's not the default Sheet1)
We read the data and transform it in a more an array of objects, which for this case makes it easier to manage
We sort the data. This is similar to the transpose you made but in the code. It also forces a priority order and groups it by criteria
Iterate the rows, keeping only the valid:
We keep a list of all the valid ones (valid) and one for the current criteria only (currentValid) because we only have to check data collisions with the ones in the same criteria.
The first iteration will always enter the if block (because currentCriteria is undefined).
When changing criteria, we dump all the rows in currentValid into valid. We do the same after the loop with the last criteria
When changing criteria, the CurrentValid is an array with the current row as an element because the first row will always be valid (because of sorting)
For the other rows, we check if the starting date is between the starting and ending date of any of the valid rows for that criteria. If it's not, add it to this criteria's valid rows
We remove all the current "Valid" in the validity row and fill it out with the valids
The cornerstone of the algorithm is actually sorting the data. It allows us to not have to search for the best row, as it's always the next one. It also ensures things like that the first row of a criteria is always valid.
Learning resources
Javascript tutorial (W3Schools)
Google App Scripts
Overview of Google Apps Script
Extending Google Sheets
Custom Menus in Google Workspace
Code references
Class SpreadsheetApp
Class Sheet
Sheet.getRange (notice the 3 overloads)
let ... of (MDN)
Spread syntax (...) (MDN)
Arrow function expressions (MDN)
Array.from() (MDN)
Array.prototype.push() (MDN)
Array.prototype.sort() (MDN)
Date.prototype.getTime() (MDN)
String.prototype.localeCompare() (MDN)

Dart Regex Matching

I want to check via regex in dart whether a line contains string such as ABS_D0 or ABS_D1, or ABS_D2 etc upto ABS_D40 and also ABS_DX.
var dcc1= "ABS_D0 4, 5, 158, b";
var dcc2 = "ABS_D1 3, 5, 157, b";
var dccEnd = "ABS_DX";
If line contains matching string then line is split via comma and stored in list.
example
ABS_D0 4, 5, 158, b should become list[0]=0,list[1]=4,list[2]=5,list[3]=158,list[4]=b
ABS_D1 3, 5, 157, b should become list[0]=1,list[1]=3,list[2]=5,list[3]=157,list[4]=b
You are not saying which tpe the list elements must have. The one containing "b" is clearly a string, but should 158 be a string or an integer?
I'll make it a string for now, you can always use int.parse if you want it as an integer.
final absRE = RegExp(r"ABS_D([1-4]?\d|X)\s*");
List<String> matchABS(String line) {
var match = absRE.firstMatch(line);
if (match == null) return null;
var result = [match[1]]
result.addAll(line.substring(match.end).split(",").map((s) => s.trim());
return result;
}
The regular expression matches "ABS_D" followed by either a number in the range 0..40 (well, it accepts up to 49 actually, but I assume that's not a problem) or "X". Then the code splits the rest of the line on commas.

converting string to table data type

I have some code, I want it to pick a random string from the list and convert it to a data type to be used in joypad.set() function.
Here is my code:
Buttons = { A = true,
B = true,
Down = true}
while (true) do
Random = math.random(3)
NewButton = (Buttons[Random])
joypad.set(1, (NewButton))
emu.frameadvance();
end;
You don't state your problem, but from your code, it looks like you're not getting the values from the array you expect. You're getting a random number between 1 and 3, but A, B, and Down are not 1, 2, and 3. Buttons is an associative array (key-value pairs) the way you declare it, so if you want to use it this way, you will need to set up a second array with just the key names, and get a random index from that, like so:
ButtonKeys = { "A", "B", "Down" }
Random = math.random(3)
NewButton = (Buttons[ButtonKeys[Random]])
This creates a table with the values of A, B, and Down as index 1, 2, and 3, so you use the random number to get the value from the ButtonKeys array, then use that value as the index for the Buttons array.
Edit: I reread the question and went over my original answer and realized I was thinking about you declaring the table differently. The way you declare the table, A, B, and Down become properties of Buttons, which you can access by calling them directly like Buttons.A, Buttons.B, and Buttons.C, or by using brackets with a string name of the property you want to access. In your case, Buttons["A"], Buttons["B"], and Buttons["Down"].

Resources