This question already has answers here:
regular expression to match days hours minutes
(5 answers)
Closed 2 years ago.
I'm getting data like "134h22m54s" from an API. With a function I created, it returns as an object includes hours, minutes and seconds.
Here my split function:
parseHMS (value) { // value = "134h22m54s" or "3m0s" or only "0s"
let hour = "0";
let minute = "0";
let second = "0";
if (value.includes('h')) {
let splitByHour = value.split('h');
hour = splitByHour[0]
value = splitByHour[1];
}
if (value.includes('m')) {
let splitByHour = value.split('m');
minute = splitByHour[0]
value = splitByHour[1];
}
if (value.includes('s')) {
let splitByHour = value.split('s');
minute = splitByHour[0]
}
return {h: hour, m: minute, s: second}
}
And the output:
{
h: "134",
m: "22",
s: "54"
}
I would like to know if there is an easier way to parse this type of data.
You can use Regex:
function parseDuration(s) {
var m = /(?:(\d+)d)?(?:(\d+)h)?(?:(\d+)m?)?/.exec(s);
return {
days: parseInt(m[1], 10) || 0,
hours: parseInt(m[2], 10) || 0,
minutes: parseInt(m[3], 10) || 0
};
}
console.log(parseDuration('10d12h59m'));
console.log(parseDuration('10d12h'));
console.log(parseDuration('10d'));
console.log(parseDuration('12h'));
console.log(parseDuration('70'));
As a simple but not bulletproof solution you can split your string by numeric characters and filter only numbers.
const [h,m,s] = "134h22m54s".split(/(\d+)/).filter(Number);
console.log(h,m,s);
I would recommend using a regular expression here. This can selectively match parts of a string (i.e. it may or may not be there) using non-capturing groups with a quantifier.
let parseHMS = (value) => {
let reg = /(?:(?:(\d+)h)?(?:(\d+)m)?)?(\d+)s/
let result = reg.exec(value)
return {h: result[1], m: result[2], s: result[3]}
}
Explanation on regex101
You can improve it, but maybe something like this with regex:
const parseHMS = (value) => {
let reg = /((\d+)h)?((\d+)m)?((\d+)s)/
let result = reg.exec(value)
return {h: result[2], m: result[4], s: result[6]}
}
Related
I have a situation where I'm trying to join the array of strings only if it has a value. I did using let variable, but I wonder can I do this using map. I'm very mindful on the position, so I used in this way.
const [hours, minutes, seconds] =["", "10", ""]
let time = "";
if (hours) {
time += `${hours}h `;
}
if (minutes) {
time += `${minutes}m `;
}
if (seconds) {
time += `${seconds}s`;
}
console.log(time)
Will I be able to do this using map, filter and join?
If you put the hours, minutes, and seconds back into an array, you can make another array for the suffixes, add the suffixes by mapping the values, then filter out the resulting substrings that are 1 character or less (which would indicate that there wasn't a corresponding value).
const suffixes = ['h', 'm', 's'];
const [hours, minutes, seconds] = ["", "10", ""]; // combine this with the next line if you can
const hms = [hours, minutes, seconds];
const time = hms
.map((value, i) => value + suffixes[i])
.filter(substr => substr.length > 1)
.join(' ');
console.log(time)
Here's a nice little snippet
const suffixs = "hms"; // String arrays of single characters can be shortened to this!
const values = ["","10",""];
var string = values
.map((x,i) => x ? x + suffixs[i] : x)
.filter(x => x)
.join(" ");
console.log(string);
// Or if you want to have 0 in place of empty values
var string2 = values
.map((x,i) => (x || "0") + suffixs[i])
.join(" ");
console.log(string2);
Personally, I would create a list of objects, that represents the time.
I prefer this over using two arrays, as it can save some bugs (like changing the order of the data in one list but not in the other one):
const [hours, minutes, seconds] = ["20", "10", ""];
const time = [
{ amount: hours, sign: "h" },
{ amount: minutes, sign: "m" },
{ amount: seconds, sign: "s" },
];
const str = time
.filter((val) => val.amount)
.map((val) => val.amount + val.sign)
.join(" ");
console.log(str);
I am trying to replace 4 numbers with a time format. For example if user enters 1234 to be replaced with 12:34
I have found that this regex does this job
let myString = "1234";
myString.replace(/\b(\d{2})(\d{2})/g, '$1:$2')
But now I am trying to figure out how to use this with cases like
94 - this should be replaced with 23 then it does same for time after the colon
01:74 - this should be replaced with 01:59
I have found a regex which does that ^([0-1]?[0-9]|2[0-3]):[0-5][0-9], but I am not able to figure out how to combine this with .replace
You will need to match on the first and second pair of digits and then bound them by their max values. Once you have the bounded values, you can pad the numbers and join them with a colon.
const toTimeString = (value) => {
const
[, hours, minutes] = value.match(/^(\d{2})(\d{2})$/),
hour = `${Math.min(+hours, 24)}`.padStart(2, '0'),
minute = `${Math.min(+minutes, 59)}`.padStart(2, '0');
return `${hour}:${minute}`;
};
console.log(toTimeString('0174')); // 01:59
console.log(toTimeString('3412')); // 24:12
Now, here is a replacer example:
const minPad = (value, min) => `${Math.min(value, min)}`.padStart(2, '0');
const toTimeString = (value) =>
value.replace(/\b(\d{2})(\d{2})\b/g, (match, hours, minutes) =>
`${minPad(hours, 24)}:${minPad(minutes, 59)}`);
console.log(toTimeString('0174 3412')); // 01:59 24:12
There is an overload of replace which takes a function which you can use to do any logic you need, such as:
let myString = "1234";
function formatTime(input){
return input.replace(/\b(\d{2})(\d{2})/, (_,hh,mm) => {
return `${Math.min(hh,24)}:${Math.min(mm,59)}`
})
}
console.log(formatTime("1234"));
console.log(formatTime("3412"));
console.log(formatTime("0174"));
I do not see any good reason to use regex in your case.
Just a simple function will do the job.
function transformTextToHHMMTimeFormat(text) {
const firstNumber = Number(text.slice(0, 2))
const secondNumber = Number(text.slice(2, 4))
const hour = Math.min(firstNumber, 24)
const minute = Math.min(secondNumber, 59)
return `${hour}:${minute}`
}
I'm working with numbers like 0.3333333333333333, 0.1111111111111111, 0.6666666666666666 and I'd like to round them to the nearest hundredth while keeping numbers like 0.14285714285714285 intact.
Sorry if this question's been asked. It's gotta be a common question, but I can't seem to come up with the right keywords.
There may be a mathematical way to detect those, but you can detect those on the string version of the number using a regular expression like this: /^0\.(\d)\1+$/. That says
^0. Must start with 0.
(\d) Followed by a digit (we capture this digit)
\1+ Followed by the same digit (\1 refers back to the capture group) one or more times (+
Then grab just the first four characters of the string if you want to truncate (0.6666666666666666 becomes 0.66) or the first five and convert back to number and round (0.6666666666666666 becomes 0.67).
Live Example:
const numbers = [
0.3333333333333333,
0.1111111111111111,
0.6666666666666666,
0.14285714285714285,
0.1444444444444444,
];
for (const number of numbers) {
const str = String(number);
if (/^0\.(\d)\1+$/.test(str)) {
const truncated = Number(str.substring(0, 4));
const rounded = Math.round(Number(str.substring(0, 5) * 100)) / 100;
console.log(`Truncated: ${truncated}, rounded: ${rounded}`);
} else {
console.log(`Unchanged; ${number}`);
}
}
(Convert back to number if you need the number value.)
In modern environments, you can make that expression a bit clearer by using a named capture group rather than the traditional anonymous version above: /^0\.(?<digit>\d)\k<digit>+$/ In that, (?<digit>\d) is the capture group named "digit" and \d<digit> is a backreference to that capture group.
Live Example:
const numbers = [
0.3333333333333333,
0.1111111111111111,
0.6666666666666666,
0.14285714285714285,
0.1444444444444444
];
for (const number of numbers) {
const str = String(number);
if (/^0\.(?<digit>\d)\k<digit>+$/.test(str)) {
const truncated = Number(str.substring(0, 4));
const rounded = Math.round(Number(str.substring(0, 5) * 100)) / 100;
console.log(`Truncated: ${truncated}, rounded: ${rounded}`);
} else {
console.log(str);
}
}
There's question what you want done with 0.1444444444444444 (do you want 0.1444444444444444 or 0.14)? All your examples repeat starting at the ., but you're rounding to hundreths. Now, those are two separate things, but people are interpreting your question to say that 0.1444444444444444 should become 0.14. If so, it's a trivial change to allow any digit in the tens place: Just add \d after . in the expression: /^0\.\d(\d)\1+$/
Live Example:
const numbers = [
0.3333333333333333,
0.1111111111111111,
0.6666666666666666,
0.14285714285714285,
0.1444444444444444,
0.1555555555555555,
];
for (const number of numbers) {
const str = String(number);
if (/^0\.\d(\d)\1+$/.test(str)) {
const truncated = Number(str.substring(0, 4));
const rounded = Math.round(Number(str.substring(0, 5) * 100)) / 100;
console.log(`Truncated: ${truncated}, rounded: ${rounded}`);
} else {
console.log(str);
}
}
Since your question is not so clear, I took it as follows:
If there is a repeating chunk of digits, keep the pattern twice and
truncate the rest. If there is not repeating digits, rounding to hundreths.
For example, the following is what you get by running the proposed code. The suffix ... is to indicate the repeated digits.
0.14285714285714285 => 0.142857142857... (repeating pattern = 142857)
0.1444444444444444 => 0.144... (repeating pattern = 4)
0.3333333333333333 => 0.33... (repeating pattern = 3)
0.1428824114288241 => 0.1428824114288241... (repeating pattern = 14288241)
0.1288241128824112 => 0.12882411288241... (repeating pattern = 1288241)
0.12128824112882411 => 0.1212882411288241... (repeating pattern = 1288241)
0.1231231231231231 => 0.123123... (repeating pattern = 123)
0.101010101010101 => 0.1010... (repeating pattern = 10)
0.12300123123123123 => 0.12300123123... (repeating pattern = 123)
0.4254250042542542 => 0.42542500425425... (repeating pattern = 425)
0.1232435213443346 => 0.12 (repeating pattern = None)
I had to create the test case to make sure the code works for various patterns. The nums array contains the input and the expected answer.
You can use the code as
const {result, pattern} = testRepeatingDigits (0.1444444)
If you want to round the answer, you can modify the code where it returns the number string with ....
If you give me your requirement I can always edit and improve the answer.
Here is the complete code that you can run and see.
/**
* Returns the logest repeating substring from the beginning.
*/
function findLongestSubstring (str) {
let candidate = "";
for (let i = 1; i <= str.length - i; i++) {
if (str.indexOf(str.substring(0, i), i) === i)
candidate = str.substring(0, i);
}
return candidate;
}
/**
* Rotate the substring and find the left most matched point
*/
function rotateAndMoveLeft (str, substr, fromIndex) {
const rotate = (str) => `${str[str.length-1]}${str.slice(0, str.length-1)}`;
const lastIndex = substr.length - 1;
let rotatedStr = substr;
let pos;
// console.log(`str=${str}, substr=${substr}, fromIndex=${fromIndex}`);
for (pos = fromIndex - 1; pos >= 0; pos--) {
if (rotatedStr[lastIndex] === str[pos]) {
rotatedStr = rotate(rotatedStr);
} else {
pos++;
break;
}
}
const from = pos !== -1 ? pos : 0;
return {
subStr: rotatedStr,
from,
numMoved: fromIndex - from
};
}
function shrinkPattern (pattern) {
const _shrink = (head, tail) => {
if (tail.length === 0)
return head;
return tail.split(head).every(item => item.length === 0) ?
head : _shrink(`${head}${tail[0]}`, tail.slice(1));
}
return _shrink(pattern[0], pattern.slice(1));
}
function testRepeatingDigits (num) {
const str = num.toString();
const idx = str.indexOf('.');
if (idx < 0)
return false;
const digitStr = str.substring(idx + 1);
const [...digits] = digitStr;
// the first guess of repeating pattern from the right-most digit
const init = [...findLongestSubstring(digits.slice(0).reverse().join(''))].reverse().join('');
// no repeating patterns found
if (init.length === 0)
return {
result: (Math.round(num * 100) / 100).toString(),
pattern: "None"
};
// rotate the first guessed pattern to the left to find the beginning of the repeats
const searchFrom = digitStr.length - (init.length * 2);
const { subStr, from, numMoved } = searchFrom > 0 ?
rotateAndMoveLeft(digitStr, init, searchFrom) : { subStr: init, from: 0, numMoved: 0 };
// shrink the pattern to minimum
const pattern = shrinkPattern(subStr);
// truncate the digits overflows the two repeatings of the pattern
return {
result: `${str.substring(0, idx+1)}${digitStr.substring(0, from + pattern.length * 2)}...`,
pattern
};
}
// test cases
const nums = [{
num: 0.14285714285714285, // rep: 142857, truncated: [14285]
str: '0.142857142857...'
},
{
num: 0.1444444444444444, // rep: 4, truncated: [4444444444444]
str: '0.144...'
},
{
num: 0.3333333333333333, // rep: 3, truncated: [33333333333333]
str: '0.33...'
},
{
num: 0.1428824114288241, // rep: 14288241, truncated: []
str: '0.1428824114288241...'
},
{
num: 0.1288241128824112, // 1288241, [12]
str: '0.12882411288241...'
},
{
num: 0.12128824112882411, // 1288241, [1]
str: '0.1212882411288241...'
},
{
num: 0.1231231231231231, // 123, [1]
str: '0.123123...'
},
{
num: 0.1010101010101010, // 10, [101010101010]
str: '0.1010...'
},
{
num: 0.12300123123123123, // 123, [123123]
str: '0.12300123123...'
},
{
num: 0.4254250042542542, // 425, [42]
str: '0.42542500425425...'
},
{
num: 0.1232435213443346, // no repeat
str: '0.12'
},
];
nums.forEach(({ num, str }) => {
const { result, pattern } = testRepeatingDigits(num);
console.log(`${num} => ${result} (repeating pattern = ${pattern}) ${result === str ? 'OK' : 'Incorrect!'} `);
});
Not perfectly clear but here is my take.
It feels like you would like to test the floating part of given number against a repeating pattern. So perhaps you can do like;
function truncAtRepeat(n){
var [is,fs] = String(n).split("."),
index = (fs + fs).indexOf(fs,1);
return index === fs.length ? n
: parseFloat(is + "." + fs.slice(0,index));
}
console.log(truncAtRepeat(1.177177177177177));
console.log(truncAtRepeat(1.17717717717717));
console.log(truncAtRepeat(3.14159265359));
use a list and iterate over each number in the string and find the repeating numbers
def find_repeating_numbers(string):
# create a list of numbers
numbers = [int(x) for x in string]
# create a list of repeating numbers
repeating_numbers = []
# iterate through the list of numbers
for i in range(len(numbers)):
# if the number is already in the list of repeating numbers, continue
if numbers[i] in repeating_numbers:
continue
# if the number is not in the list of repeating numbers, check if it is repeated
else:
# if the number is repeated, add it to the list of repeating numbers
if numbers.count(numbers[i]) > 1:
repeating_numbers.append(numbers[i])
# return the list of repeating numbers
return repeating_numbers
data=[0.14285714285714285,0.1444444444444444,0.3333333333333333
,0.1428824114288241,0.1288241128824112,0.12128824112882411,0.1231231231231231
,0.101010101010101,0.12300123123123123,0.4254250042542542,0.1232435213443346
]
# print the list of repeating numbers
#print(find_repeating_numbers('14285714285714285'))
for item in data:
item=re.sub('0.','',str(item))
result=find_repeating_numbers(item)
repeating_number=''.join([str(n) for n in result])
print(item,repeating_number)
output:
14285714285714285 142857
1444444444444444 4
3333333333333333 3
1428824114288241 1428
1288241128824112 1284
12128824112882411 1284
1231231231231231 123
1
123123123123123 123
42542542542542 425
1232435213443346 1234
Using vanilla javascript, how do you select the first string value left of a specified section of a string and not include the empty space?
For example, there is a string 1 hr 30 min
How do you get the 1 value left of hr and how do you get the 30 value left of min?
var string = "1 hr 30 min";
if (string.includes("hr")) {
// get the 1 value
}
if (string.includes("min")) {
// get the 30 value
}
this function will split your string into two string, containing only hours and win, then you just have to parse it ton integer :
const getHoursAndMin = () => {
const _string = string.replace('min','')
const [hours,min] = _string.split('hr')
console.log(parseInt(hours),parseInt(min))
}
It can be done this way, you will have good control on the way out.
If you can guarantee the input string in the function.
"use strict";
function zeroFill(str, width) {
width -= str.toString().length;
if (width > 0) {
return new Array(width + (/\./.test(str) ? 2 : 1)).join('0') + str;
}
return str + ""; // always return a string
}
function getHoursOrMin(str, extract, returnType) {
const _str = str.split(' ');
let result = "";
switch (extract) {
case 'hr':
const iHr = _str.indexOf('hr');
result = _str[iHr - 1];
break;
case 'min':
const iMin = _str.indexOf('min');
result = _str[iMin - 1];
break;
}
switch (returnType) {
case 'number':
return Number(result);
case 'string':
return result;
case 'stringZeroFill':
return zeroFill(result, 2);
}
}
const str = "1 hr 30 min";
console.log(`${getHoursOrMin(str, 'hr', 'number')}:${getHoursOrMin(str, 'min', 'number')}`);
console.log(`${getHoursOrMin(str, 'hr', 'string')}:${getHoursOrMin(str, 'min', 'string')}`);
console.log(`${getHoursOrMin(str, 'hr', 'stringZeroFill')}:${getHoursOrMin(str, 'min', 'stringZeroFill')}`);
I have this string:
002 2.0 (100aa) 95-97
I then want regex the 95-97 portion of it and paste it with relevant two numbers so I get a year.
In example, 96-97 should become 1995-1997, but 00-05 should become 2000-2005 (all numbers between 0 and 16 should be pasted with 20, but all other numbers with 19).
Then, when I have i.e. 1995-1997 I want to check if a year (i.e. 1996) is present inside 1995-1997 interval or not, and return a bolean.
How would one wright such code?
Best Regards
You could use the callback variant of replace:
function parseString(str) {
function padYear(year) {
return year < 30 ? 2000+year :
year < 100 ? 1900+year : year;
}
var result = {};
result.hasCurrentYear = false;
result.str = str.replace(/(\d\d)-(\d\d)$/g, function (match, yr1, yr2) {
yr1 = padYear(+yr1);
yr2 = padYear(+yr2);
var yrNow = (new Date).getFullYear();
result.hasCurrentYear = yrNow >= yr1 && yrNow <= yr2;
return yr1 + '-' + yr2;
});
return result;
}
var str = '002 2.0 (100aa) 95-16';
console.log(parseString(str));
Note that I made the split at year 30, as the solution will become outdated soon if you use 16 as split year.
I suppose there's a much simpler way to check if a certain year is in "range".The solution using String.split, Array.map functions and Number constructor:
var str = "002 2.0 (100aa) 95-97";
function checkYearInRange(str, year) {
year = Number(year);
var range = str.split(" ").pop().split('-').map((v) => Number((Number(v) > 16)? "19"+v : "20"+v));
return (range[0] <= year && year <= range[1]);
}
console.log(checkYearInRange(str, "1996")); // true
console.log(checkYearInRange(str, "2015")); // false