I have a function which checks a number(quantity) against an array of quantity range: price
var data = {
"1 - 4": " $4.25 ",
"10 - 24": " $3.25 ",
"25 - 49": " $3.04 ",
"5 - 9": " $3.51 ",
"50+": " $2.84 "
}
function get_price(arr, val) {
var price = Object.keys(arr).reduce((a, c) => {
var s = c.trim();
if (/\d+\-\d+/.test(s)) {
var range = s.split("-");
if (val >= parseInt(range[0]) && val <= parseInt(range[1])) {
a = arr[c];
}
} else {
s = s.replace(/\D/g, "");
if (val >= s) {
a = arr[c];
}
}
return a;
}, 0);
return price;
}
The problem is that if a number is greater then 50 but less then 100 it is calculating correctly, over 100 it is not able to see the 50+ and use that price.
Any help is appreciated!
Use strings as your objects' keys, that will work fine:
const range = {
"1-4": " $4.25 ",
"10-24": " $3.25 ",
"25-49": " $3.04 ",
"5-9": " $3.51 ",
"50+": " $2.84 "
}
More about property accessors:
In this code, property must be a valid JavaScript identifier, i.e. a sequence of alphanumerical characters, also including the underscore ("_") and dollar sign ("$"), that cannot start with a number. For example, object.$1 is valid, while object.1 is not.
- MDN Web docs
Demo:
function get_price(arr, val) {
var price = Object.keys(arr).reduce((a, c) => {
var s = c.trim();
if (/\d+\-\d+/.test(s)) {
var range = s.split("-");
if (val >= parseInt(range[0]) && val <= parseInt(range[1])) {
a = arr[c];
}
} else {
s = s.replace(/\D/g, "");
if (val >= s) {
a = arr[c];
}
}
return a;
}, 0);
return price;
}
const range = {
"1-4": " $4.25 ",
"10-24": " $3.25 ",
"25-49": " $3.04 ",
"5-9": " $3.51 ",
"50+": " $2.84 "
}
console.log(get_price(range, 60))
console.log(get_price(range, 500))
Your code will not work only if you pass strings as the val parameter to the get_price as it will do string comparison between lets say "100" > "50" and it will fail.
Convert to numbers to be sure
if (+val >= +s) {
a = arr[c];
}
and
if (+val >= parseInt(range[0]) && +val <= parseInt(range[1])) {
a = arr[c];
}
I have a somewhat different approach. I'm assuming that the ranges list is likely to get reused. This solution uses a function that turns, say '25 - 49', into {start: 25, end: 49}, and uses it to create an array of objects such as {start: 25, end: 49, value: '$3.04'}. Then I return a function closing over this array that accepts a price and return the value of the first range which includes this price.
This has the (probably minor) advantage of not continually parsing the range strings. More importantly, it breaks the problem into several reusable pieces. makeRange might be reused elsewhere in a project, and so might findByRange. The only part of this specific to the current needs is getPrice, and that becomes fairly declarative.
const makeRange = (desc, match = '') =>
((match = desc.match(/(\d+)\s*\-\s*(\d+)/)) && match && {start: Number(match[1]), end: Number(match[2])})
|| ((match = desc.match(/(\d+)\s*\+/)) && match && {start: Number(match[1]), end: Infinity})
|| ((match = desc.match(/<\s*(\d+)/)) && match && {start: -Infinity, end: Number(match[1]) - 1})
|| {start: NaN, end: NaN}
const findByRange = rangeValues => {
const ranges = Object.keys(rangeValues).map(
key => Object.assign({value: rangeValues[key]}, makeRange(key))
).sort((a, b) => a.start - b.start) // sorting mostly for cleanliness
return val => {
const range = ranges.find(range => range.start <= val && val <= range.end)
return range ? range.value : false // or throw an exception, or use default
}
}
const getPrice = findByRange({
'< 5' : ' $4.25 ', // could also be '1 - 4'
'5-9' : ' $3.51 ',
'10-24' : ' $3.25 ',
'25-49' : ' $3.04 ',
'50+' : ' $2.84 '
});
[3, 7, 16, 42, 57, 143].forEach(
n => console.log(`${n} --> ${getPrice(n)}`)
)
Related
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);
});
I have two functions, one that checks if the passed in string contains consecutive ascending numbers, the other checks for descending consecutive numbers.
I'm wondering if these functions can be rewritten to be cleaner/ shorter than they are in their current state. At present, they're not DRY as logic is reused between the two.
The value passed in will always be a string of 6 characters, e.g "123456".
function ascendingNumbers(value) {
const valueArray = value.split("").map((item) => {
return parseInt(item, 10);
});
let match = 0;
if (valueArray[1] - 1 === valueArray[0]) {
match++;
}
for (let i = 0; i < valueArray.length - 1; i++) {
valueArray[i + 1] !== valueArray[i] + 1 ? null : match++;
}
if (match === 6) {
return false;
} else {
return true;
}
}
function descendingNumbers(value) {
const valueArray = value.split("").map((item) => {
return parseInt(item, 10);
});
let match = 0;
if (valueArray[1] + 1 === valueArray[0]) {
match++;
}
for (let i = 0; i < valueArray.length - 1; i++) {
valueArray[i + 1] !== valueArray[i] - 1 ? null : match++;
}
if (match === 6) {
return false;
} else {
return true;
}
}
You could use Array.map and Array.reduce to achieve this in a compact way, we'd create an ensureOrder function for this purpose.
You could also do this in an even more compact way with regex, using a positive lookahead to ensure we have 6 digits, then check for a range of optional digits.
function ensureOrder(value, length, ascending) {
const valueArray = Array.prototype.map.call(value, Number);
if (valueArray.length !== length) return false;
return !!valueArray.reduce((last, current, index) => ((last !== null) && (ascending ? current > last: current < last)) ? current: null);
}
console.log("Test ascending:")
let testAsc = ["345678", "123456", "456789", "123458", "223456", "1", "654321", "1234567", "string1"];
testAsc.forEach(input => console.log("Test for "+ input + ": ", ensureOrder(input, 6, true)))
console.log("Test descending:")
let testDesc = ["654321", "765432", "987654", "987321", "123456", "6", "9876543", "string1"];
testDesc.forEach(input => console.log("Test for "+ input + ": ", ensureOrder(input, 6, false)))
// Regex flavour of ascending function
function isAscending(value) {
return /^(?=\d{6}$)0?1?2?3?4?5?6?7?8?9?$/.test(value);
}
// Regex flavour of descending function
function isDescending(value) {
return /^(?=\d{6}$)9?8?7?6?5?4?3?2?1?0?$/.test(value);
}
console.log("Test ascending (regex):")
testAsc.forEach(input => console.log("Test for "+ input + ": ", isAscending(input)))
console.log("Test descending (regex):")
testDesc.forEach(input => console.log("Test for "+ input + ": ", isDescending(input)))
We can use this kind of approach which will tell us whether we are the number in ascending or descending order
AscendingOrDescending = (inputString) => {
const arrayOfNumbers= Array.from(inputString);
const asscendingResult = arrayOfNumbers.every((number,index,array) => {
return index < array.length ? Number(number) + 1 === Number(array[index+1]) || Number(number) - 1 === Number(array[index-1]) : true;
});
return asscendingResult ? 'string is in ascending or descending order' : 'string is not in ascending or descending order'
}
console.log(AscendingOrDescending("123456"))
I'm trying to write a function that returns an array of equally chunked up dates and number of days pertaining to those dates. Should there be a remainder of those days they get appended to the array as follow.
Expected outcome:
[{
'startDate' : 20160719 //dates generated from momentjs
'numOfDays': 5
},{
'startDate' : 20160724
'numOfDays': 5
},{
'startDate' : 20160729
'numOfDays': 3
}]
Below is the function I've written in which you can pass in a start date (momentjs), the total number of days (daysToDisplay) and number of days to be divided by (numOfDays).
function buildQueue(startDate, numOfDays, daysToDisplay) {
if (!startDate || !numOfDays || !daysToDisplay) {
throw new Error('All params required!');
}
var num = numOfDays > daysToDisplay ? daysToDisplay : numOfDays;
var div = Math.floor(daysToDisplay / num);
var count = daysToDisplay;
var rem = daysToDisplay % num;
var lastItem;
var i;
var arr = [];
for (i = 0; i <= daysToDisplay; i += num) {
arr.push({
startDate: moment(startDate, 'YYYYMMDD').add(i, 'days').format('YYYYMMDD'),
numOfDays: numOfDays,
count: i
})
if (rem === count) {
break;
}
count -= num;
}
if (count > 0) {
lastItem = arr[arr.length - 1];
var leftover = daysToDisplay - lastItem.count;
arr.push({
startDate: moment(lastItem.startDate, 'YYYYMMDD').add(num, 'days').format('YYYYMMDD'),
numOfDays: rem,
count: leftover + lastItem.count
});
}
return arr;
}
A working example is here (https://jsfiddle.net/zv5ghqpa/1/). The code appears to work in scenarios where daysToDisplay is dividable by more than 2.
When daysToDisplay is only dividable by one, we get an additional item in the returned array basically due to the zero index in the for loop. The expected outcome if I call buildQueue('20160719', 5, 8) should be:
[{
'startDate': 20160719
'numOfDays': 5
}, {
'startDate': 20160724
'numOfDays': 3
}]
Instead its returning:
[{
'startDate': 20160719
'numOfDays': 5
},{
'startDate': 20160724
'numOfDays': 5
}, {
'startDate': 20160729
'numOfDays': 3
}]
I hope i've given enough info... this is really doing my head in.
Thanks in advance!
I think this is code you're looking for:
function buildQueue(startDate, numOfDays, daysToDisplay) {
if (!startDate || !numOfDays || !daysToDisplay) {
throw new Error('All params required!');
}
var num = numOfDays > daysToDisplay ? daysToDisplay : numOfDays;
var div = Math.floor(daysToDisplay / num);
var count = daysToDisplay;
var rem = daysToDisplay % num;
var n = 0;
var i;
var arr = [];
for (i = 0; i <= daysToDisplay; i += num) {
arr.push({
startDate: moment(startDate, 'YYYYMMDD').add(i, 'days').format('YYYYMMDD'),
numOfDays: daysToDisplay % num,
count: i
})
console.log(rem + ' ' + count);
if (rem === count) {
count = 0;
break;
}
count -= num;
}
if (count > 0) {
var leftover = daysToDisplay - arr[arr.length - 1].count;
arr.push({
startDate: moment(arr[arr.length - 1].startDate, 'YYYYMMDD').add(num, 'days').format('YYYYMMDD'),
numOfDays: daysToDisplay % num,
count: leftover + arr[arr.length - 1].count
});
}
return arr;
}
//console.log(buildQueue(moment(), 80, 100));
console.log(buildQueue(moment(), 5, 8));
//console.log(buildQueue(moment(), 15, 100));
//console.log(buildQueue(moment(), 30, 100));
//console.log(buildQueue(moment(), 45, 100));
I think the 'Expected outcome' was correct before you edited the question. I also note the sample code produced a count property that you don't want in the output.
Perhaps this code does what you want?
function buildQueue(startDate, numOfDays, daysToDisplay) {
var arr = []
while (daysToDisplay > 0) {
arr.push({
startDate: moment(startDate, 'YYYYMMDD')
.add(numOfDays * arr.length, 'days')
.format('YYYYMMDD'),
numDays: Math.min(numOfDays, daysToDisplay)
})
daysToDisplay -= numOfDays
}
return arr
}
I'm looking for a way to construct regular expressions to match numeric inputs specified by a given integer range, ie. if I pass in a range of 1,3-4 then a regex would be returned matching just 1, 3 and 4.
I wrote the following method to try and do this:
function generateRegex(values) {
if (values == "*") {
return new RegExp("^[0-9]+$");
} else {
return new RegExp("^[" + values + "]+$");
}
}
I'm having issues however as sometimes I need to match double digits, such as "8-16", and I also need to ensure that if I am passed a single digit value, such as "1", that the generated regex matches only 1, and not say 11.
I really would like this to remain a pretty small snippet of code, but am not sure enough about regexs to know how to do this. Would be massively grateful for any help!
EDIT: I realise I wasn't clear, with my original paragraph, so have edited it. I realise the regex's that I originally generated do not work at all
Regexes don't know anything about numbers, only digits. So [8-16] is invalid because you say match between 8 and 1 (instead of 1 and 8 e.g.) plus the digit 6.
If you want to match numbers, you have to consider them lexically. For example, to match numbers between 1 and 30, you have to write something like (other regexes exist):
/^(30|[1-2]\d|[1-9])$/
I was sure it was 4-8 hours :-) In the end (and in its uselessness) it was a good exercise in composing Regexes. You are free to try it. If we exclude one use of continue and the use of the Array constructor, it's fully jsLint ok.
var BuildRegex = function(matches) {
"use strict";
var splits = matches.split(','),
res = '^(',
i, subSplit, min, max, temp, tempMin;
if (splits.length === 0) {
return new RegExp('^()$');
}
for (i = 0; i < splits.length; i += 1) {
if (splits[i] === '*') {
return new RegExp('^([0-9]+)$');
}
subSplit = splits[i].split('-');
min = BuildRegex.Trim(subSplit[0], '0');
if (min === '') {
return null;
}
if (subSplit.length === 1) {
res += min;
res += '|';
continue;
} else if (subSplit.length > 2) {
return null;
}
max = BuildRegex.Trim(subSplit[1], '0');
if (max === '') {
return null;
}
if (min.length > max.length) {
return null;
}
// For 2-998 we first produce 2-9, then 10-99
temp = BuildRegex.DifferentLength(res, min, max);
tempMin = temp.min;
if (tempMin === null) {
return null;
}
res = temp.res;
// Then here 100-998
res = BuildRegex.SameLength(res, tempMin, max);
}
res = res.substr(0, res.length - 1);
res += ')$';
return new RegExp(res);
};
BuildRegex.Repeat = function(ch, n) {
"use strict";
return new Array(n + 1).join(ch);
};
BuildRegex.Trim = function(str, ch) {
"use strict";
var i = 0;
while (i < str.length && str[i] === ch) {
i += 1;
}
return str.substr(i);
};
BuildRegex.IsOnlyDigit = function(str, start, digit) {
"use strict";
var i;
for (i = start; i < str.length; i += 1) {
if (str[i] !== digit) {
return false;
}
}
return true;
};
BuildRegex.RangeDigit = function(min, max) {
"use strict";
if (min === max) {
return min;
}
return '[' + min + '-' + max + ']';
};
BuildRegex.DifferentLength = function(res, min, max) {
"use strict";
var tempMin = min,
i, tempMax;
for (i = min.length; i < max.length; i += 1) {
tempMax = BuildRegex.Repeat('9', i);
res = BuildRegex.SameLength(res, tempMin, tempMax);
tempMin = '1' + BuildRegex.Repeat('0', i);
}
if (tempMin > tempMax) {
return null;
}
return {
min: tempMin,
res: res
};
};
BuildRegex.SameLength = function(res, min, max) {
"use strict";
var commonPart;
// 100-100
if (min === max) {
res += min;
res += '|';
return res;
}
for (commonPart = 0; commonPart < min.length; commonPart += 1) {
if (min[commonPart] !== max[commonPart]) {
break;
}
}
res = BuildRegex.RecursivelyAddRange(res, min.substr(0, commonPart), min.substr(commonPart), max.substr(commonPart));
return res;
};
BuildRegex.RecursivelyAddRange = function(res, prefix, min, max) {
"use strict";
var only0Min, only9Max, i, middleMin, middleMax;
if (min.length === 1) {
res += prefix;
res += BuildRegex.RangeDigit(min[0], max[0]);
res += '|';
return res;
}
// Check if
only0Min = BuildRegex.IsOnlyDigit(min, 1, '0');
only9Max = BuildRegex.IsOnlyDigit(max, 1, '9');
if (only0Min && only9Max) {
res += prefix;
res += BuildRegex.RangeDigit(min[0], max[0]);
for (i = 1; i < min.length; i += 1) {
res += '[0-9]';
}
res += '|';
return res;
}
middleMin = min;
if (!only0Min) {
res = BuildRegex.RecursivelyAddRange(res, prefix + min[0], min.substr(1), BuildRegex.Repeat('9', min.length - 1));
if (min[0] !== '9') {
middleMin = String.fromCharCode(min.charCodeAt(0) + 1) + BuildRegex.Repeat('0', min.length - 1);
} else {
middleMin = null;
}
}
middleMax = max;
if (!only9Max) {
if (max[0] !== '0') {
middleMax = String.fromCharCode(max.charCodeAt(0) - 1) + BuildRegex.Repeat('9', max.length - 1);
} else {
middleMax = null;
}
}
if (middleMin !== null && middleMax !== null && middleMin[0] <= middleMax[0]) {
res = BuildRegex.RecursivelyAddRange(res, prefix + BuildRegex.RangeDigit(middleMin[0], middleMax[0]), middleMin.substr(1), middleMax.substr(1));
}
if (!only9Max) {
res = BuildRegex.RecursivelyAddRange(res, prefix + max[0], BuildRegex.Repeat('0', max.length - 1), max.substr(1));
}
return res;
};
// ----------------------------------------------------------
var printRegex = function(p) {
"use strict";
document.write(p + ': ' + BuildRegex(p) + '<br>');
};
printRegex('*');
printRegex('1');
printRegex('1,*');
printRegex('1,2,3,4');
printRegex('1,11-88');
printRegex('1,11-88,90-101');
printRegex('1-11111');
printRegex('75-11119');
Test here http://jsfiddle.net/dnqYV/
The C# version is here http://ideone.com/3aEt3E
I'm not sure there is a (sane) way to test integer ranges with RegExp. I believe you're fixated on RegExp, where there are much simpler (more flexible) approaches. Take a look at IntRangeTest().
var range = new IntRangeTest('0,10-20');
console.log(
"0,10-20",
range.test("") == false,
range.test("-5") == false,
range.test("0") == true,
range.test("5") == false,
range.test("11") == true,
range.test("123.23") == false
);
If you feel like it, you can easily add this to Number.prototype. You could also quite easily make this an extension to RegExp, if that's what you're worried about.
Ok so it seems that there are 4 main cases that I need to address:
Single digits, ie 1, would simply generate the regex /^1$/
Multiple digits, ie 12, would require the regex /^12&/
Single digit ranges, ie 3-6, would generate the regex /^[3-6]$/
And finally, multiple digit ranges work in a similar method to multiple digits but with a range, ie 11-14 would become /^1[1-4]$/. These would need to be split into multiple regexes if they span over multiple start digits, Ie 23-31 would become /^2[3-9]|3[0-1]$/
Therefore, all I need to do is identify each of these cases and create a compound regex using | like xanatos suggested. Ie, to match all of the above criteria would generate a regex like:
/^( 1 | 12 | [3-6] | 1[1-4] | 2[3-9]|3[0-1] )$/
Do other agree this seems like a decent way to progress?
This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
How can I format numbers as money in JavaScript?
I am doing a divide and multiply to figure out the value of a field and the value looks like this
$32560.000000000004.00
I am setting the value like this
sq_footage = $("#sq_footage").val();
sq_footage_total = (((sq_footage / 36) * factor) * average )* 30;
if(factor != ""){
$("#new_calc").val("$" + sq_footage_total + ".00");
}
Is there a way to format it like
$32,560.00
I know this has been solved before, but here's my solution just for fun:
function formatDollar(num) {
var p = num.toFixed(2).split(".");
return ["$", p[0].split("").reverse().reduce(function(acc, num, i) {
return num + (i && !(i % 3) ? "," : "") + acc;
}, "."), p[1]].join("");
}
And some tests:
formatDollar(45664544.23423) // "$45,664,544.23"
formatDollar(45) // "$45.00"
formatDollar(123) // "$123.00"
formatDollar(7824) // "$7,824.00"
formatDollar(1) // "$1.00"
formatDollar(939,009) // ?
function formatDollar(num) {
var p = num.toFixed(2).split(".");
return ["$", p[0].split("").reverse().reduce(function(acc, num, i) {
return num + (i && !(i % 3) ? "," : "") + acc;
}, "."), p[1]].join("");
}
const tests = [
{
input: 45664544.23423,
expected: "$45,664,544.23"
},
{
input: 45,
expected: "$45.00"
},
{
input: 123,
expected: "$123.00"
},
{
input: 7824,
expected: "$7,824.00"
},
{
input: 1,
expected: "$1.00"
},
{
input: -939009,
expected: "-$939,009.00"
}
]
tests.forEach(test => {
const { input, expected } = test
const actual = formatDollar(input)
if (expected !== actual) {
console.log(`Failed: ${input}. Expected ${expected}; actual: ${actual}`)
} else {
console.log(`Passed: ${input}`)
}
})
Your tag indicates that you are using jquery - so you might use the jQuery Format Currency Plugin