I am learning regular expression so I did some example. For example I want to validate date dd/mm/yyyy hh:mm:ss or dd-mm-yyy hh:mm:ss
I did like this on https://regex101.com:
([0-9]{2}+[-\/]+[0-9]{2}+[-\/]+[0-9]{4})\s([0-9]{2}+:+[0-9]{2}+:+[0-9]{2})
(\d{2}+[-\/]+\d{2}+[-\/]+\d{4})\s(\d{2}+:+\d{2}+:+\d{2})
Is there any other way to do it so regex can be short? Or it is perfect, I am sure it is not 100% perfect.
i want to add range for example month should not be greater than 12 , in time it should not be grater that 60.
Here is a date validator that I created for this, I think it could be fixed a little but it is working
test()
function dateChecker(re, dOrder, s) {
var validity = {
pass: false,
testString: s
}
s.replace(re, function(full, date, seperator, time) {
var y, m, d, h, min, s, p;
date = date.split(seperator).map(Number);
date[1] --;
time = time ? time.split(':').map(Number) : [0, 0, 0];
p = new Date(
y = date[dOrder.y],
m = date[dOrder.m],
d = date[dOrder.d],
h = time[0],
min = time[1],
s = time[2]);
if (p.getDate() === d &&
p.getMonth() === m &&
p.getFullYear() === y &&
p.getHours() === h &&
p.getMinutes() === min &&
p.getSeconds() === s)
validity.pass = true;
else
validity.failReason = [p, "doesn't match", date, time].join(' ')
});
return validity
}
function dValidator(s) {
return dateChecker(/^((?:\d\d([-\/])){2}\d{4})(?:\s+((?:\d\d:){2}\d\d))?$/, {
d: 0,
m: 1,
y: 2
}, s)
}
function test() {
var failedTests = [],
failData = ["33/09/2064", "31/2/1980"],
sucessData = ["14-03-1904 04:35:17", "29/2/1980"];
failData.map(dValidator).map(function(a, i) {
return a.pass ? failedTests.push([a, i, 'expected to fail but it incorrectly passed']) : console.log('test passed ok', a, i)
})
sucessData.map(dValidator).map(function(a, i) {
return a.pass ? console.log('test passed ok', a, i) : failedTests.push([a, i, 'expected to pass but it has incorrectly failed'])
})
if (failedTests.length) {
console.log(failedTests);
return 'fail'
}
return 'pass'
}
You are using too much +, and you could repeat some groups :
(\d{2}[-\/]){2}\d{4}\s\d{2}(:\d{2}){2}
I found for date m/d/yy or m/d/yyyy or mm/dd/yy or mm/dd/yy
^((0?[13578]|10|12)(-|\/)(([1-9])|(0[1-9])|([12])([0-9]?)|(3[01]?))(-|\/)((19)([2-9])(\d{1})|(20)([01])(\d{1})|([8901])(\d{1}))|(0?[2469]|11)(-|\/)(([1-9])|(0[1-9])|([12])([0-9]?)|(3[0]?))(-|\/)((19)([2-9])(\d{1})|(20)([01])(\d{1})|([8901])(\d{1})))$
Related
Trying to compare two columns in GoogleSheets with this formula in Column C:
=if(A1=B1,"","Mismatch")
Works fine, but I'm getting a lot of false positives:
A.
B
C
MARY JO
Mary Jo
JAY, TIM
TIM JAY
Mismatch
Sam Ron
Sam Ron
Mismatch
Jack *Ma
Jack MA
Mismatch
Any ideas how to work this?
This uses a score based approach to determine a match. You can determine what is/isn't a match based on that score:
Score Formula = getMatchScore(A1,B1)
Match Formula = if(C1<.7,"mismatch",)
function getMatchScore(strA, strB, ignoreCase=true) {
strA = String(strA);
strB = String(strB)
const toLowerCase = ignoreCase ? str => str.toLowerCase() : str => str;
const splitWords = str => str.split(/\b/);
let [maxLenStr, minLenStr] = strA.length > strB.length ? [strA, strB] : [strB, strA];
maxLenStr = toLowerCase(maxLenStr);
minLenStr = toLowerCase(minLenStr);
const maxLength = maxLenStr.length;
const minLength = minLenStr.length;
const lenScore = minLength / maxLength;
const orderScore = Array.from(maxLenStr).reduce(
(oldItem, nItem, index) => nItem === minLenStr[index] ? oldItem + 1 : oldItem, 0
) / maxLength;
const maxKeyWords = splitWords(maxLenStr);
const minKeyWords = splitWords(minLenStr);
const keywordScore = minKeyWords.reduce(({ score, searchWord }, nItem) => {
const newSearchWord = searchWord?.replace(new RegExp(nItem, ignoreCase ? 'i' : ''), '');
score += searchWord.length != newSearchWord.length ? 1: 0;
return { score, searchWord: newSearchWord };
}, { score: 0, searchWord: maxLenStr }).score / minKeyWords.length;
const sortedMaxLenStr = Array.from(maxKeyWords.sort().join(''));
const sortedMinLenStr = Array.from(minKeyWords.sort().join(''));
const charScore = sortedMaxLenStr.reduce((oldItem, nItem, index) => {
const surroundingChars = [sortedMinLenStr[index-1], sortedMinLenStr[index], sortedMinLenStr[index+1]]
.filter(char => char != undefined);
return surroundingChars.includes(nItem)? oldItem + 1 : oldItem
}, 0) / maxLength;
const score = (lenScore * .15) + (orderScore * .25) + (charScore * .25) + (keywordScore * .35);
return score;
}
try:
=ARRAYFORMULA(IFERROR(IF(LEN(
REGEXREPLACE(REGEXREPLACE(LOWER(A1:A), "[^a-z ]", ),
LOWER("["&B1:B&"]"), ))>0, "mismatch", )))
Implementing fuzzy matching via Google Sheets formula would be difficult. I would recommend using a custom formula for this one or a full blown script (both via Google Apps Script) if you want to populate all rows at once.
Custom Formula:
function fuzzyMatch(string1, string2) {
string1 = string1.toLowerCase()
string2 = string2.toLowerCase();
var n = -1;
for(i = 0; char = string2[i]; i++)
if (!~(n = string1.indexOf(char, n + 1)))
return 'Mismatch';
};
What this does is compare if the 2nd string's characters order is found in the same order as the first string. See sample data below for the case where it will return mismatch.
Output:
Note:
Last row is a mismatch as 2nd string have r in it that isn't found at the first string thus correct order is not met.
If this didn't meet your test cases, add a more definitive list that will show the expected output of the formula/function so this can be adjusted, or see player0's answer which solely uses Google Sheets formula and is less stricter with the conditions.
Reference:
https://stackoverflow.com/a/15252131/17842569
The main limitation of traditional fuzzy matching is that it doesn’t take into consideration similarities outside of the strings. Topic clustering requires semantic understanding. Goodlookup is a smart function for spreadsheet users that gets very close to semantic understanding. It’s a pre-trained model that has the intuition of GPT-3 and the join capabilities of fuzzy matching. Use it like vlookup or index match to speed up your topic clustering work in google sheets.
https://www.goodlookup.com/
I used a google script that move row from one sheet to another from column A to column D. On column E i have a formula. How can i put data on last row from Column A to D from example, ignoring formula from column E. Can someone help me with this?
function onEdit(e){
let r = e.range;
if (r.columnStart != 4 || r.rowStart == 1 || e.value == null) return;
const sh = SpreadsheetApp.getActive();
const valArray = ["Waiting","In progress","Complete"];
const destArray = ["Order","Open order","Complete order"];
let dest = sh.getSheetByName(destArray[valArray.indexOf(e.value)]);
let src = sh.getActiveSheet();
if (dest.getName() == src.getName()) return;
src.getRange(r.rowStart,1,1,4).moveTo(dest.getRange(dest.getLastRow()+1,1,1,4));
src.deleteRow(r.rowStart);
}
Here an example of my problem, maybe this will explain better:https://docs.google.com/spreadsheets/d/1qowADCPYiyej25ezXtjVLO5fvg9Gr9rolX3bh2-ZAG4/edit#gid=0
Replace dest.getLastRow() by dest.getLastDataRow('A') and add this function:
Object.prototype.getLastDataRow = function(col){
var lastRow = this.getLastRow();
var range = this.getRange(col + lastRow);
if (range.getValue() !== "") {
return lastRow;
} else {
return range.getNextDataCell(SpreadsheetApp.Direction.UP).getRow();
}
};
I still sugest you to erase all formulas in column E and put in E1
={"price * 2";ARRAYFORMULA(if(C2:C>0;C2:C*2;""))}
so that the formula will populate / will be active the new row
Please consider the following list of ValueTuple C#7
static void Main(string[] args)
{
List<(double prices, int matches)> myList = new List<(double, int)>();
myList.Add((100, 10));
myList.Add((100.50 , 12));
myList.Add((101 , 14));
myList.Add((101.50 , 16));
}
What would be a simple way to search for items meeting conditions for "prices" AND "matches" within the List and return the results in another List of ValueTuple.
For instance if I want to return another List of ValueTuples meeting "prices > 101 and matches > 6"
Can you post an example please?
It's easier if you give names to the items :
var myList = new List<(double d,int i)>
{
(100, 10),
(100.50 , 12),
(101 , 14),
(101.50 , 16)
};
var results = myList.Where(x => x.d>101 && x.i>6);
Without the names you'd have to write
var results = myList.Where(x => x.Item1>101 && x.Item2>6);
C# 7.3 added tuple equality but not comparison. You can now write :
var result = myList.Where(d=>d == (101,14));
and
var result = myList.Where(d=>d != (101,14));
But not
var result = myList.Where(d=>d > (101,14));
I'm investigate too much on _gid and _ga. And as i know, the definition of them:
_ga: used to identify unique users and it expires after 2 years.
_gid: used to identify unique users and it expires after 24 hours.
Example:
_ga: GA1.3.292651669.1502954402
_gid: GA1.3.974792242.1509957189
From what are the values in _ga cookie?, I know the meaning of each filed in _ga. And it's the same for _gid.
But I don't know how to generate third field, random generated user ID(for _ga:292651669 and for _gid: 974792242).
I tried to delete both _ga & _gid and I get the new couple _ga: GA1.3.2097663971.1509959880 and _gid: GA1.3.1180999143.1509959880. The third fields of both are changed. So how can they generate and how google identify user by them. Assumption I open browser today with a couple of _gid and _ga, and tomorrow, I cleared all cookies, ga will create of new couplw (_gid,_ga).; It means I'm in today that different tomorrow's me.
Please help me.
Thanks & Best Regards,
Jame
Here is the code from analytics.js release 2017-09-21 that generates the random id.
It's a random value based on current time, userAgent, cookie, referrer and history.length from the window object.
var O = window;
// var M = document;
// Stack Overflow doesn't allow cookie access in sandbox, so lets fake it:
var M = [];
M.cookie = "cookieName=someValue";
var hd = function() {
return Math.round(2147483647 * Math.random())
}
function La(a) {
var b = 1, c;
if (a)
for (b = 0,
c = a.length - 1; 0 <= c; c--) {
var d = a.charCodeAt(c);
b = (b << 6 & 268435455) + d + (d << 14);
d = b & 266338304;
b = 0 != d ? b ^ d >> 21 : b
}
return b
}
var ra = function() {
for (var a = O.navigator.userAgent + (M.cookie ? M.cookie : "") + (M.referrer ? M.referrer : ""), b = a.length, c = O.history.length; 0 < c; )
a += c-- ^ b++;
return [hd() ^ La(a) & 2147483647, Math.round((new Date).getTime() / 1E3)].join(".")
}
document.getElementById('output').innerText = ra();
<h3>Output:</h3>
<pre id="output"></pre>
I solved my problem in an imperative style, but it looks very ugly. How can I make it better (more elegant, more concise, more functional - finally its Scala). Rows with the same values as the previous row, but with a different letter should be skipped, all other values of the rows should be added.
val row1 = new Row(20, "A", true) // add value
val row2 = new Row(30, "A", true) // add value
val row3 = new Row(40, "A", true) // add value
val row4 = new Row(40, "B", true) // same value as the previous element & different letter -> skip row
val row5 = new Row(60, "B", true) // add value
val row6 = new Row(70, "B", true) // add value
val row7 = new Row(70, "B", true) // same value as the previous element, but the same letter -> add value
val rows = List(row1, row2, row3, row4, row5, row6, row7)
var previousLetter = " "
var previousValue = 0.00
var countSkip = 0
for (row <- rows) {
if (row.value == previousValue && row.letter != previousLetter) {
row.relevant = false
countSkip += 1
}
previousLetter = row.letter
previousValue = row.value
}
// get sum
val sumValue = rows.filter(_.relevant == true).map(_.value) reduceLeftOption(_ + _)
val sum = sumValue match {
case Some(d) => d
case None => 0.00
}
assert(sum == 290)
assert(countSkip == 1)
Thanks in advance
Twistleton
(rows.head :: rows).sliding(2).collect{
case List(Row(v1,c1), Row(v2,c2)) if ! (v1 == v2 && c1 != c2) => v2 }.sum
I think the shortest (bulletproof) solution when Row is a case class (dropping the boolean) is
(for ((Row(v1,c1), Row(v2,c2)) <- (rows zip rows.take(1) ::: rows) if (v1 != v2 || c1 == c2)) yield v1).sum
Some of the other solutions don't handle the list-is-empty case, but this is largely because sliding has a bug where it will return a partial list if the list is too short. Clearer to me (and also bulletproof) is:
(rows zip rows.take(1) ::: rows).collect{
case (Row(v1,c1), Row(v2,c2)) if (v1 != v2 || c1 == c2) => v1
}.sum
(which is only two characters longer if you keep it on one line). If you need the number skipped also,
val indicated = (rows zip rows.take(1) ::: rows).collect {
case (Row(v1,c1), Row(v2,c2)) => (v1, v1 != v2 || c1 == c2)
}
val countSkip = indicated.filterNot(_._2).length
val sum = indicated.filter(_._2).map(_._1).sum
Fold it:
scala> rows.foldLeft((row1, 0))((p:(Row,Int), r:Row) => (r, p._2 + (if (p._1.value == r.value && p._1.letter != r.letter) 0 else r.value)))._2
res2: Int = 290
(new Row(0, " ", true) +: rows).sliding(2).map { case List(r1, r2) =>
if (r1.value != r2.value || r1.letter == r2.letter) { r2.value }
else { 0 }
}.sum
Of course you can drop the boolean member of Row if you do not need it for something else
Reduce it:
rows.reduceLeft { (prev, curr) =>
if (prev.value == curr.value && prev.letter != curr.letter) {
curr.relevant = false
countSkip += 1
}
curr
}