I want to convert the ranges of below fashion to rows and column notations
The example input and the expected output is given below
Input is A1 => Then output is [0,1,0,1]
B1 = [0,1,1,2]
A2 = [1,2,0,1]
C2 = [1,2,2,3]
C12 = [11,12,2,3]
A1:B4 = [0,4,0,2]
C1:D11 = [0,11,2,4]
F32:H43 = [31,43,5,8]
C:F = [null,null,2,6]
30:38 = [29,38,null,null]
76:79 = [75,79,null,null]
AA1:AD15 = [0,15,26,30]
Z2:B1 = [null,null,null,null] or undefined //any invalid range
2:B1 = [null,null,null,null] or undefined //any invalid range
0:0 = [null,null,null,null] or undefined //any invalid range
4-2 = [null,null,null,null] or undefined //any invalid range
6-7d7 = [null,null,null,null] or undefined //any invalid range
The general formula
[startrow-1,endrow,startcol-1,endcol]
30:38 = [startrow-1,endrow,null,null]
76:79 = [startrow-1,endrow,null,null]
C:F = [null,null,2,6]
A1
can also be written as
A1:A1 = [0,1,0,1]
C12
can also be written as
C12:C12 = [11,12,2,3]
My code works for these
A10:B10 //already works
A:B10 //not working so far
A10:B //not working so far
A:B //not working so far
10:10 //not working so far
<!DOCTYPE html>
<html>
<body>
<script>
function fromA1Notation(cell) {
var i, l, chr,
sum = 0,
A = "A".charCodeAt(0),
radix = "Z".charCodeAt(0) - A + 1;
if (typeof cell !== 'string' || !/^[A-Z]+$/.test(cell)) {
throw new Error("Expected column label");
}
for (i = 0, l = cell.length; i < l; i++) {
chr = cell.charCodeAt(i);
sum = sum * radix + chr - A + 1
}
return sum;
}
var input = "A1:B20";
if (input.length > 0 && input.match(/[A-Z]+[0-9]+:[A-Z]+[0-9]+/i) != null) {
var matched = input.match("([A-Za-z]+)([0-9]+):([A-Za-z]+)([0-9]+)");
console.log(JSON.stringify(matched))
if (matched != null) {
a1range = {
a1not: input,
c1: (fromA1Notation(matched[1].toUpperCase()) - 1),
r1: (matched[2] - 1),
c2: fromA1Notation(matched[3].toUpperCase()),
r2: matched[4]
};
if (a1range.c1 >= a1range.c2 || a1range.c1 >= a1range.c2) {
a1range = undefined;
}
console.log(a1range)
}
}
console.log(a1range)
</script>
</body>
</html>
Here is a solution that:
turns 'A1' pattern into 'A1:A1'
matches col, num, ':', col, num
converts col letters into 1-based index
does invalid range check
fixed the start row and start col index to be zero based
function A2N (str) {
return str.split('').reduce((acc, char, idx) => {
return acc += char.charCodeAt(0) - 65 + (idx * 26);
}, 1);
}
[ 'A1', 'B1', 'A2', 'C:F', 'A10:B10', 'A:B10', 'A10:B', 'A:B', '10:10', 'AA1', 'AAA1', 'A', '1', 'B', '20', 'Z9:A1', '#'
].forEach(str => {
let parts = str
.replace(/^(\w+)$/, '$1:$1') // turn 'A1' into 'A1:A1'
.match(/^([A-Z]*)([0-9]*)(?::([A-Z]*)([0-9]*))?$/);
let result = [ null, null, null, null ];
if(parts) {
result = [
parts[2] ? Number(parts[2]) : null,
parts[4] ? Number(parts[4]) : null,
parts[1] ? A2N(parts[1]) : null,
parts[3] ? A2N(parts[3]) : null
];
if(result[0] && result[1] && result[0] > result[1]) {
// invalid range
result[0] = null;
result[1] = null;
}
if(result[2] && result[3] && result[2] > result[3]) {
// invalid range
result[2] = null;
result[3] = null;
}
if(result[0]) {
// zero-based start row
result[0]--;
}
if(result[2]) {
// zero-based start col
result[2]--;
}
}
console.log(str, '==>', result);
});
Related
I'm trying to write a script to output JSON according to these constraints.
So far I think my logic is correct.
Any help is appreciated.
CURRENT ISSUES:
[working now]I can't figure out why duration continues to return 0
[working now]how to tackle setting the max/min
tackling how to handle when two excursions of different types occur back to back (“hot” ⇒ “cold” or “cold” ⇒ “hot”)
This is how each new object should appear
let current_excursion = {
'device_sensor' : '',
'start_at' : [],
'stop_at' : 0,
'duration' : 0,
'type': '',
'max/min':0
}
device_sensor
The sId this excursion was detected on.
start_at
The date and time the temperature first is out of range in ISO_8601 format.
stop_at
The date and time the temperature is back in range in ISO_8601 format.
duration
The total time in seconds the temperature was out of range.
type
Either the string “hot” or “cold” depending on the type of excursion.
max/min
The temperature extreme for the excursion. For a “hot” excursion this will be the max and for a “cold” excursion the min.
A temperature excursion event starts
when the temperature goes out of range and ends when the temperature returns
to the range.
For a “hot” excursion this is when the temperature is greater than 8 °C,
and for a “cold” excursion this is when the temperature is less than 2 °C.
If two excursions of different types occur back to back
(“hot” ⇒ “cold” or “cold” ⇒ “hot”) please take the midpoint of the two
timestamps as the end of the first excursion and the start of the second.
If an excursion is occurring at the end of the temperature readings
please end the excursion at the last reading (duration = 0)
Here is the link to the test data
Test Case Data
Here is what I've written so far:
const tempTypeTernary = (num) =>{
if(num < 2){
return 'cold'
} else if(num > 8){
return 'hot'
}
}
const excursion_duration = (x,y) =>{
let start = new Date(x) / 1000
let end = new Date(y) / 1000
return end - start
}
const reset_excursion = (obj) => {
Object.keys(obj).map(key => {
if (obj[key] instanceof Array) obj[key] = []
else obj[key] = ''
})
}
const list_excursion = (array) =>{
let result = [];
let max_min_excursion = 0;
let current_excursion = {
'device_sensor' : '',
'start_at' : [],
'stop_at' : 0,
'duration' : 0,
'type': '',
'max/min':0
}
for(let k = 0; k < array.length;k++){
if( array[k]['tmp'] < 2 || array[k]['tmp'] > 8){
current_excursion['device_sensor'] = array[k]['sId'];
current_excursion['start_at'] = [new Date(array[k]['time']).toISOString(),array[k]['time']];
current_excursion['type'] = tempTypeTernary(array[k]['tmp']);
if( array[k]['tmp'] > 2 || array[k]['tmp'] < 8){
current_excursion['stop_at'] = new Date(array[k]['time']).toISOString();
current_excursion['duration'] = excursion_duration(current_excursion['start_at'][1],array[k]['time'])
}
result.push(current_excursion)
reset_excursion(current_excursion)
}
}
return result
}
list_excursion(json)
Let me be bold and try to answer on just eyeballing the code; please tryout this:
const tempTypeTernary = (num) =>{
if(num < 2){
return 'cold'
} else if(num > 8){
return 'hot'
}
}
const excursion_duration = (x,y) =>{
let start = new Date(x) / 1000
let end = new Date(y) / 1000
return end - start
}
const reset_excursion = (obj) => {
Object.keys(obj).map(key => {
if (obj[key] instanceof Array) obj[key] = []
else obj[key] = ''
})
}
const list_excursion = (array) =>{
let result = [];
let max_min_excursion = 0;
let current_excursion = {
'device_sensor' : '',
'start_at' : [],
'stop_at' : 0,
'duration' : 0,
'type': '',
'max/min':0
}
for(let k = 0; k < array.length;k++){
if( array[k]['tmp'] < 2 || array[k]['tmp'] > 8)
{
if (current_excursion['type']==null)
{
current_excursion['device_sensor'] = array[k]['sId'];
current_excursion['start_at'] = [new Date(array[k]['time']).toISOString(),array[k]['time']];
current_excursion['type'] = tempTypeTernary(array[k]['tmp'])
}
}
else // this is where the second 'if' was
{
if (current_excursion['type']!=null)
{
current_excursion['stop_at'] = new Date(array[k]['time']).toISOString();
current_excursion['duration'] = excursion_duration(current_excursion['start_at'][1],array[k]['time'])
result.push(current_excursion)
reset_excursion(current_excursion)
}
}
}
I'm trying to transform an array of numbers such that each number has only one nonzero digit.
so basically
"7970521.5544"
will give me
["7000000", "900000", "70000", "500", "20", "1", ".5", ".05", ".004", ".0004"]
I tried:
var j = "7970521.5544"
var k =j.replace('.','')
var result = k.split('')
for (var i = 0; i < result.length; i++) {
console.log(parseFloat(Math.round(result[i] * 10000) /10).toFixed(10))
}
Any ideas, I'm not sure where to go from here?
Algorithm:
Split the number in two parts using the decimal notation.
Run a for loop to multiply each digit with the corresponding power of 10, like:
value = value * Math.pow(10, index); // for digits before decimal
value = value * Math.pow(10, -1 * index); // for digits after decimal
Then, filter the non-zero elements and concatenate both the arrays. (remember to re-reverse the left-side array)
var n = "7970521.5544"
var arr = n.split('.'); // '7970521' and '5544'
var left = arr[0].split('').reverse(); // '1250797'
var right = arr[1].split(''); // '5544'
for(let i = 0; i < left.length; i++)
left[i] = (+left[i] * Math.pow(10, i) || '').toString();
for(let i = 0; i < right.length; i++)
right[i] = '.' + +right[i] * Math.pow(10, -i);
let res = left.reverse() // reverses the array
.filter(n => !!n)
// ^^^^^^ filters those value which are non zero
.concat(right.filter(n => n !== '.0'));
// ^^^^^^ concatenation
console.log(res);
You can use padStart and padEnd combined with reduce() to build the array. The amount you want to pad will be the index of the decimal minus the index in the loop for items left of the decimal and the opposite on the right.
Using reduce() you can make a new array with the padded strings taking care to avoid the zeroes and the decimal itself.
let s = "7970521.5544"
let arr = s.split('')
let d_index = s.indexOf('.')
if (d_index == -1) d_index = s.length // edge case for nums with no decimal
let nums = arr.reduce((arr, n, i) => {
if (n == 0 || i == d_index) return arr
arr.push((i < d_index)
? n.padEnd(d_index - i, '0')
: '.' + n.padStart(i - d_index, '0'))
return arr
}, [])
console.log(nums)
You could split your string and then utilize Array.prototype.reduce method. Take note of the decimal position and then just pad your value with "0" accordingly. Something like below:
var s = "7970521.5544";
var original = s.split('');
var decimalPosition = original.indexOf('.');
var placeValues = original.reduce((accum, el, idx) => {
var f = el;
if (idx < decimalPosition) {
for (let i = idx; i < (decimalPosition - 1); i++) {
f += "0";
}
accum.push(f);
} else if (idx > decimalPosition) {
let offset = Math.abs(decimalPosition - idx) - 2;
for (let i = 0; i <= offset; i++) {
f = "0" + f;
}
f = "." + f;
accum.push(f);
}
return accum;
}, []);
console.log(placeValues);
Shorter alternative (doesn't work in IE) :
var s = "7970521.5544"
var i = s.split('.')[0].length
var a = [...s].reduce((a, c) => (i && +c && a.push(i > 0 ?
c.padEnd(i, 0) : '.'.padEnd(-i, 0) + c), --i, a), [])
console.log( a )
IE version :
var s = "7970521.5544"
var i = s.split('.')[0].length
var a = [].reduce.call(s, function(a, c) { return (i && +c && a.push(i > 0 ?
c + Array(i).join(0) : '.' + Array(-i).join(0) + c), --i, a); }, [])
console.log( a )
function standardToExpanded(n) {
return String(String(Number(n))
.split(".")
.map(function(n, i) {
// digits, decimals..
var v = n.split("");
// reverse decimals..
v = i ? v.reverse() : v;
v = v
.map(function(x, j) {
// expanded term..
return Number([x, n.slice(j + 1).replace(/\d/g, 0)].join(""));
})
.filter(Boolean); // omit zero terms
// unreverse decimals..
v = i ? v.map(function(x) {
return '.' + String(x).split('').reverse().join('')
}).reverse() : v;
return v;
})).split(',');
}
console.log(standardToExpanded("7970521.5544"));
// -> ["7000000", "900000", "70000", "500", "20", "1", ".5", ".05", ".004", ".0004"]
This looks like something out of my son's old 3rd Grade (core curriculum) Math book!
Example: '1234567' String has : 7 consecutive digits and these are 1234567
Example : '123456' String has : 6 consecutive digits and these are 123456
I need both the
count
the digits sequence as output also
Please help, regex or custom function
I had used inefficient brute force approach:
var m10 = /^\d{10}$/.exec(str);
var m9 = /^\d{9}$/.exec(str);
var m8 = /^\d{8}$/.exec(str);
Using this I need to have m1,m2 to m10 and then use if else to check if the string has 1 or 10 consecutive digits and then output it
Time is important here so trying to find optimize way of doing this.
You can get the matches using match and regex \d+
var matches = "1234567String".match(/\d+/g);
Now get the max value using reduce
if( matches )
{
var maxLengthStr = matches.reduce( ( a, c ) => ( c.length > a.length ? c : a ) , "" );
}
You can get the length of string as
var fnGetMax = str => {
var matches = "1234567String".match(/\d+/g);
var maxLengthStr = matches ? matches.reduce( ( a, c ) => (c.length > a.length ? c : a ) , "" ) : "";
return { value : maxLengthStr , length : maxLengthStr.length }; //return the expression
}
Demo
var fnGetMax = str => {
var matches = "1234567String".match(/\d+/g);
var maxLengthStr = matches ? matches.reduce((a, c) => (c.length > a.length ? c : a), "") : "";
return {
value: maxLengthStr, length: maxLengthStr.length
}; //return the expression
}
console.log( fnGetMax( "34234ksdf34234ssdcs432sadfe34343" ) );
if performance is what you are looking for, i think you really should consider using a loop, see this example:
NOTE: in case of two substring with identical lengths, with this method the first one will be returned (for the last one, use if(s.length >= saved.length)). If no substring is found, empty string is currently returned.
var result;
var fnGetMax = str => {
var matches = str.match(/\d+/g);
var maxLengthStr = matches ? matches.reduce((a, c) => (c.length > a.length ? c : a), "") : "";
return {
value: maxLengthStr, length: maxLengthStr.length
}; //return the expression
}
var s = performance.now();
console.log( result = fnGetMax( "34234ksdf34234ssdcs432sadfe34343" ) );
var e = performance.now();
console.log('time: ', e - s);
document.getElementById('result1').innerHTML = 'REDUCE - string: ' + result.value + ' / length: ' + result.length + ' / timing: ' + (e - s);
var fnGetMax2 = str => {
var saved = '', s = '', i, l = str.length;
for(i = 0; i < l; i++){
if('0123456789'.indexOf(str[i]) !== -1){
s += str[i];
if(s.length > saved.length){
saved = s;
}
}else{
s = '';
}
}
return {
value: saved, length: saved.length
}
}
var s = performance.now();
console.log( result = fnGetMax2( "34234ksdf34234ssdcs432sadfe34343" ) );
var e = performance.now();
console.log('time: ', e - s);
document.getElementById('result2').innerHTML = 'LOOP - string: ' + result.value + ' / length: ' + result.length + ' / timing: ' + (e - s);
<div id="result1"></div>
<div id="result2"></div>
I have a matrix for which I have a function that picks an element of the array at random using the following code:
npcSelectShip() {
let selectCol = () => {
let colIndex = Math.floor(Math.random() * this.npcSelectionField.length);
let selectCell = () => {
let cellIndex = Math.floor(Math.random() * this.npcSelectionField[colIndex].length);
if (this.npcSelectionField[colIndex][cellIndex].isEmpty === false) {
selectCell();
} else {
this.npcSelectionField[colIndex][cellIndex].isEmpty = false;
this.pickDirection(this.npcSelectionField, colIndex, cellIndex);
}
}
selectCell();
}
selectCol();
}
After this I have another function that searches for the neighbors ( top, right, bottom and left ) of the randomly picked element, randomly picks a neighbor and changes a property:
pickDirection(field, col, cell) {
let neighbors = [];
neighbors.push(
field[col - 1][cell],
field[col + 1][cell],
field[col][cell - 1],
field[col][cell + 1]
);
let randDir = () => {
let randIndex = neighbors[Math.floor(Math.random() * neighbors.length)];
if (randIndex.isEmpty === false) {
randDir();
} else {
randIndex.isEmpty = false;
}
}
randDir();
}
The problem I'm facing is when the the randomly picked element has an index of either 0 or equal to the array lenght, because if it picks a neighbor at index-1 or index+1 its basically "out of bounds" and I get these errors:
TypeError: Cannot read property 'isEmpty' of undefined
TypeError: Cannot read property '9' of undefined
Is there a way I can solve this without having to write a shitload of ifs and elses ?
Appreciate the help.
You could use Array.concat and a default pattern, which returns an empty array.
In combination with concat, empty arrays acts as neutral values.
var neighbors = [].concat(
(field[col - 1] || [])[cell] || [],
(field[col + 1] || [])[cell] || [],
(field[col] || [])[cell - 1] || [],
(field[col] || [])[cell + 1] || []
);
Or use a wrapper for accessing
function getCell(array, col, cell) {
return (array[col] || [])[cell] || [];
}
usage
var neighbors = [].concat(
getCell(field, col - 1, cell),
getCell(field, col + 1, cell),
getCell(field, col, cell - 1),
getCell(field, col, cell + 1)
);
How it works
(field[col - 1] || [])[cell] || []
It tries to get the value of
field[col - 1]
and if the value is undefined,
field[col - 1] || []
it returns an empty array, with the locical logical OR || operator. What we get is either the array of field[col - 1] or an empty array [].
For the next index, we use the same pattern and check if
(field[col - 1] || [])[cell]
exists and if not then we take another empty array as result
(field[col - 1] || [])[cell] || []
Now we have either a truthy value, like an object or an empty array.
This is necessary, because empty arrays are not added to an array with Array.concat.
You have basically 2 options:
First option: Do not pickup in the first/last row/column:
npcSelectShip() {
let selectCol = () => {
let colIndex = Math.floor(Math.random() * (this.npcSelectionField.length-2)) +1;
let selectCell = () => {
let cellIndex = Math.floor(Math.random() * (this.npcSelectionField[colIndex].length-2)) +1;
if (this.npcSelectionField[colIndex][cellIndex].isEmpty === false) {
selectCell();
} else {
this.npcSelectionField[colIndex][cellIndex].isEmpty = false;
this.pickDirection(this.npcSelectionField, colIndex, cellIndex);
}
}
selectCell();
}
selectCol();
}
(see the "-2" and "+1" after Math.random functions call)
Math.floor(Math.random() * (length-2)) +1;
Will take a number between floor(0*(length-2)) +1 = 1 and floor(1*(length-2)) +1 = length-1
Second option: Make condition to not add as a neighbor something out of range
pickDirection(field, col, cell) {
let neighbors = [];
if(col-1 >= 0) {
neighbors.push(field[col - 1][cell]);
}
if(col < field.length) {
neighbors.push(field[col + 1][cell]);
}
if(cell-1 >= 0) {
neighbors.push(field[col][cell -1]);
}
if(cell < field[col].length) {
neighbors.push(field[col][cell +1]);
}
let randDir = () => {
let randIndex = neighbors[Math.floor(Math.random() * neighbors.length)];
if (randIndex.isEmpty === false) {
randDir();
} else {
randIndex.isEmpty = false;
}
}
randDir();
}
I would like to know if it's possible to change the data type for a column. For instance, the json data passed to the grid are strings, but I would like slickgrid to consider it as integers or floats to be able to sort it correctly.
var data = [{"NOM": "Saguenay - Lac-Saint-Jean", "CODE": "02", "id": "0", "integer": "1"},]
I would like the 'integer' column to be an int not a string, without changing the data itself.
Thank you for your help.
As I mentioned in my comment, you are looking at the wrong place (no offense); there is no need to change datatype as actually this will not fix your problem with sort, since the SlickGrid default sort is string sort. But you could use custom sort to fix your problem.
So here is the solution: Define sort function and use them as needed. Here is a list of custom sort functions you could create:
function sorterStringCompare(a, b) {
var x = a[sortcol], y = b[sortcol];
return sortdir * (x === y ? 0 : (x > y ? 1 : -1));
}
function sorterNumeric(a, b) {
var x = (isNaN(a[sortcol]) || a[sortcol] === "" || a[sortcol] === null) ? -99e+10 : parseFloat(a[sortcol]);
var y = (isNaN(b[sortcol]) || b[sortcol] === "" || b[sortcol] === null) ? -99e+10 : parseFloat(b[sortcol]);
return sortdir * (x === y ? 0 : (x > y ? 1 : -1));
}
function sorterRating(a, b) {
var xrow = a[sortcol], yrow = b[sortcol];
var x = xrow[3], y = yrow[3];
return sortdir * (x === y ? 0 : (x > y ? 1 : -1));
}
function sorterDateIso(a, b) {
var regex_a = new RegExp("^((19[1-9][1-9])|([2][01][0-9]))\\d-([0]\\d|[1][0-2])-([0-2]\\d|[3][0-1])(\\s([0]\\d|[1][0-2])(\\:[0-5]\\d){1,2}(\\:[0-5]\\d){1,2})?$", "gi");
var regex_b = new RegExp("^((19[1-9][1-9])|([2][01][0-9]))\\d-([0]\\d|[1][0-2])-([0-2]\\d|[3][0-1])(\\s([0]\\d|[1][0-2])(\\:[0-5]\\d){1,2}(\\:[0-5]\\d){1,2})?$", "gi");
if (regex_a.test(a[sortcol]) && regex_b.test(b[sortcol])) {
var date_a = new Date(a[sortcol]);
var date_b = new Date(b[sortcol]);
var diff = date_a.getTime() - date_b.getTime();
return sortdir * (diff === 0 ? 0 : (date_a > date_b ? 1 : -1));
}
else {
var x = a[sortcol], y = b[sortcol];
return sortdir * (x === y ? 0 : (x > y ? 1 : -1));
}
}
and then in your columns definition you would use whichever custom filter you need, in your case the sorterNumeric() is what you're looking for...so your columns definition would look like the following (custom filter are at the end):
var columns = [
{id:"column1", name:"column1", field: "Column String", width:40, sortable:true, sorter:sorterStringCompare},
{id:"column2", name:"column2", field: "Column integer", width:40, sortable:true, sorter:sorterNumeric},
{id:"column3", name:"column3", field: "Column rating", width:40, sortable:true, sorter:sorterRating}
];
Saguenay...? Quebecois? :)
EDIT
I forgot to add the piece of code that attach the new sorter property to the onSort event (of course without it then it won't work), make sure you have same object name for grid and dataView, correct to whatever your variables naming are (if need be), here is the code:
grid.onSort.subscribe(function (e, args) {
var cols = args.sortCols;
dataView.sort(function (dataRow1, dataRow2) {
for (var i = 0, l = cols.length; i < l; i++) {
sortdir = cols[i].sortAsc ? 1 : -1;
sortcol = cols[i].sortCol.field;
var result = cols[i].sortCol.sorter(dataRow1, dataRow2); // sorter property from column definition comes in play here
if (result != 0) {
return result;
}
}
return 0;
});
args.grid.invalidateAllRows();
args.grid.render();
});
You could also put your code directly into the last onSort.subscribe but I suggest having the sorter into a separate function since it is cleaner (which is the code I sent).
I used this to sort the numbers correctly.
grid.onSort.subscribe(function(e, args) {
var cols = args.sortCols;
data.sort(function(dataRow1, dataRow2) {
for (var i = 0, l = cols.length; i < l; i++) {
var result = sortOnString(cols, i, dataRow1, dataRow2);
if (result != 0) {
return result;
}
}
return 0;
});
grid.invalidate();
grid.render();
});
function sortOnString(cols, i, dataRow1, dataRow2) {
var field = cols[i].sortCol.field;
var sign = cols[i].sortAsc ? 1 : -1;
console.log("name filed " + field);
if (field === 'Folio' || field === 'Orden') {
var value1 = parseInt(dataRow1[field]),
value2 = parseInt(dataRow2[field]);
console.log("name value 1 " + value1 + "name value 2 " + value2);
} else {
var value1 = dataRow1[field],
value2 = dataRow2[field];
}
var result = (value1 == value2 ? 0 : (value1 > value2 ? 1 : -1)) * sign;
return result;
}