Truncate values

Hi, from this number:

914.563
or any longer number as:
914.563 12345
914.563 42
914.563 5567
914.563 616
914.563 71
914.563 8156
914.563 93356

(there is a dot after 3 digits and a space after 6 digits)

I’m trying:

a) to truncate them to the first 3 digits, so to 914.

b) when the number starts with 914.563 2 (so
914.563 2 or longer), I would like to convert it to:

914.5.

Assuming that:
const field = document.querySelector('#collocazione');

I think the script would be something like:

if( /^914.563 [1,3-9]/.test(field.value)){
field.value = field.value.substr(0, field.value.indexOf('.') +0);
}
...............................

Could you help me, please?
Thank you!

Does this do what you want?

if (field.value.substr(0, 9) == "914.563 2") field.value = "914.5";
else field.value = "914";

Thank you, Archibald.
What about truncating any other number not starting with 914.563 to the first 3 digits?

Thanks again!

How about this?

if (field.value.substr(0, 9) == "914.563 2") field.value = "914.5";
else field.value = field.value.split(".")[0];

Or to truncate specifically to three characters (other than 914.5):

if (field.value.substr(0, 9) == "914.563 2") field.value = "914.5";
else field.value = field.value.substr(0,3);
1 Like

Perfect, thank you again Archibald!

1 Like

Hi, may I ask a question realated to the first one? I have this script that truncates values to the first digit after dot:

if( /^8[4-6][0-9]*\./.test(field.value)){
field.value = field.value.substr(0, field.value.indexOf('.') +2);
}

So, for ex. 853.914 09 is truncated to 953.9, the original number has again a dot after 3 digits and a space after 6 digits)

Is it possible to make the script work only for numbers?
The problem is that for ex. this value is cut badly:
861.44 CU —> 861.4
(should be converted to 861.4 CU)

Thank you!

Am I right that you want something like this?

861.44 CU —> 861.4 CU
861.44 09 —> 861.4

You could use a regex. regex101 is quite useful for testing.

An example with Javascript

const truncateCode = (code = '') => {
  return code.replace(/(\d+\.\d)(\d+)(\s(?<= )\d+)?/, '$1')
}

console.log(truncateCode('861.4456 CU')) // 861.4 CU
console.log(truncateCode('861.4456 09')) // 861.4

(\d+\.\d)
A capture group $1: digits 1 or many times (+) followed by a dot and one digit

(\d+)
Another capture group $2: one or many digits (+)

(\s(?<= )\d+)?
Another optional capture group $3: a space, then one or many digits preceeded by a space

We replace those captures with just the first capture $1. In other words ditch the the captures we don’t want.

I did go down the regex path, I am sure there are maybe simpler alternatives.

1 Like

For readability, this would be my approach (when first digit is 8) . . .

let firstAlpha = field.value.search(/[A-Z]|[a-z]/);
if (firstAlpha>0) field.value = field.value.substr(0, 5) + " " + field.value.substr(firstAlpha);
else field.value = field.value.substr(0, 5); // assumes always 3 digits before "." and at least one digit after
1 Like

I agree with Archibald. My version started as a simple regex, but became a little bit more involved.

Here is another alternative. Hopefully more readable.

const truncCode = (code) => {
  // split code on the space
  const [first, second] = code.split(' ')
  // truncate the first part to one decimal place
  const truncFirst = Number(first).toFixed(1)

  // test if the second part is letters
  return (/[A-Z]+/.test(second))
    ? `${truncFirst} ${second}` // true: return with letters
    : `${truncFirst}` // false: return without
}

console.log(truncCode('861.44 09')) // 861.4
console.log(truncCode('861.44 CU')) // 861.4 CU

Thank you both, it’s not very simple… :frowning: Could you exemplify in more detail? I have many combination of numbers to truncate, some to the “integer” part, some to first decimal digit, some to second decimal, some to third. Say I have this combination of numbers starting with:

8[4-6][0-9].9
(es. 847.967 4 || 850.967 456 || 869.999 3 etc.)

and assuming I have
const field = document.querySelector('#collocazione');

what would be the entire script?
Thanks again!

I have to head out in a bit.

Maybe it’s me being dense, and Archibald has a better grasp of what you are after, but I think it would help if you gave us a full list of examples of inputs and expected outputs. A bit like you did before e.g. 861.44 CU —> 861.4 CU

Wrapping what you require in a function or functions, much like I did with the truncCode example, I think would make things more straight forward for you.

One group could start with 8[4-6][0-9].*, for example:

847.467 4 > 847.4
850.767 456 > 850.7
869.999 3 > 869.9
861.39 CU —> 861.3 CU
863.456 CL > 863.4 CL
861.444 05 AR > 861.4 AR
869.347 5 BR > 869.3 BR

Another group starting with 80[1-7]*, shoud be truncated to integer, for ex:
802.556 667567 > 802
805.875 4 > 805

Another group starting with 839.3[1,6]*, shoud be truncated to second decimal, for ex:
839.315 6 > 839.31
839.366 87 > 839.36

Is this getting closer?

let fieldV = document.querySelector("#collocazione").value;
let digits, alpha = "";
let firstAlpha = fieldV.search(/[A-Z]|[a-z]/);
if(firstAlpha>0) alpha = fieldV.substr(firstAlpha-1);  // includes initial space character
  
if(/^80[1-7]/.test(fieldV))     digits = fieldV.substring(0,3);
if(/^839.3[1,6]/.test(fieldV))  digits = fieldV.substring(0,6);
if(/^8[4-6][0-9]/.test(fieldV)) digits = fieldV.substring(0,5);
  
if(/^914/.test(fieldV))         digits = fieldV.substring(0,3);
if(/^914.563 2/.test(fieldV))   digits = "914.5";
  
let truncated = digits + alpha;

I have assumed there will always be a space before any alphanumeric characters.

Thank you, Archibald, but it doesn’t seem working on this (saved) page, in “Collocazione” field: link

Ok here is my approach. I have broken it down into functions.

Helper function:

So the first function is a helper function to truncate numbers to a given number of decimal places. The toFixed method I used yesterday rounds numbers up and we don’t want that.

const trunc = (num, places = 1) => {
  // using the RexExp constructor to dynamically create a regex
  // so places = 2 -> regex = \d+\.\d{1,2}
  const regex = new RegExp(`\\d+\\.\\d{1,${places}}`)
  const match = num.match(regex)

  return (match !== null) ? match[0] : null
}

console.log(trunc('861.44 7', 1)) // 861.4
console.log(trunc('861.4563', 2)) // 861.45
console.log(trunc('861.42', 5)) // 861.42

Truncate group methods:

Secondly I have grouped the functions/methods we want to call into an object.

const truncate = {
  
  first: (code) => {
    // we can split a string using a regex
    // / ([A-Z]{2})/ splits code on a space followed by
    // 2 letters and captures those letters e.g.
    // 861.44 CU -> ['861.44', 'CU']
    // 861.44 7 -> ['861.44 7']
    const [num, letters] = code.split(/ ([A-Z]{2})/)
    
    // truncate number to 1 decimal place
    const truncNumber = trunc(num, 1)

    // if letters exist?
    return (letters)
      ? `${truncNumber} ${letters}` // true: return with letters
      : `${truncNumber}` // false: return without
  },
  
  second: (code) => parseInt(code, 10), // return integer (may need to fix this)
  
  third: (code) => trunc(code, 2) // return to 2 decimal places
}

We will be able to call them by name e.g. truncate.third(‘12.234’) → ‘12.23’

Group matches:

Next I have grouped the matches into an object. Note the key names, first, second etc correspond with the truncate functions we want to call if that match is found.

const groupMatches = {
  first: /8[4-6][0-9].*/, // e.g. if match will call truncate.first(code)
  second: /80[1-7]/,
  third: /839.3[16]*/
}

formatCode function:

Finally the formatCode function. This will loop through the groupMatches checking them against the given code and will call the corresponding truncate function returning a value.

// I would prefer to pass in groupMatches as an argument, 
// but trying to keep it simple
const formatCode = (code) => {
  // loop through groupMatches separating key and value into
  // groupName and regex e.g. ['first', /8[4-6][0-9].*/]
  for (const [groupName, regex] of Object.entries(groupMatches)) {
    // test the regex against the code
    if (regex.test(code)) {
      // if a match, check to see if truncate has a matching groupName
      // e.g. first, second, third
      if (Object.hasOwn(truncate, groupName)) {
        // if it does call that method and return the truncated code
        return truncate[groupName](code)
      }
    }
  }
}

We haven’t handled non matches, but it is a start.

Check console for outputs.

I cannot find the JavaScript on your (saved) page.

Is the script supposed to be executed when the Conferma button is clicked?

Note my code in Post #17 puts the truncated string into a variable named ‘truncated’. If you wish to update the value in the input edit box you will need to use instead:

document.querySelector('#collocazione').value = digits + alpha;

Just a bit of a refactor. We can combine the regex and truncate method into one collection.

// truncate group methods
const truncGroups = [
  {
    groupRx: /8[4-6][0-9].*/, 
    truncate: (code) => {
      // we can split a string using a regex
      // / ([A-Z]{2})/ splits code on a space followed by
      // 2 letters and captures those letters e.g.
      // 861.44 CU -> ['861.44', 'CU']
      // 861.44 7 -> ['861.44 7']
      const [num, letters] = code.split(/ ([A-Z]{2})/)

      // truncate number to 1 decimal place
      const truncNumber = trunc(num, 1)

      // if letters exist?
      return (letters)
        ? `${truncNumber} ${letters}` // true: return with letters
        : `${truncNumber}` // false: return without
    }
  },
  
  {
    groupRx: /80[1-7]/,
    truncate: (code) => trunc(code), // return integer
  },
  
  {
    groupRx: /839.3[16]*/,
    truncate: (code) => trunc(code, 2) // return to 2 decimal places
  }
]

const formatCode = (code) => {
  // loop through truncGroups
  for (const { groupRx, truncate } of truncGroups) {
    // test the regex against the code
    if (groupRx.test(code)) {
      return truncate(code)
    }
  }
  // didn't match any of these so return original.
  return code
}

edit: You could also use a Map and have the regex as keys to the truncate methods, but will leave it at this for now :slight_smile:

If the code isnt matched by any test, shouldnt you return the original, for safety? (Currently the function will eject undefined, as it hits the bottom without returning.)

1 Like

Yes you are right. I did comment on that in the previous post.

We haven’t handled non matches, but it is a start.

When I have a moment I’ll take a look.

Edit: also have to fix the second truncate method (parseInt) returning a number and the others returning a string.

Edit2:
Have amended the trunc helper function to handler integers too. See Math.trunc

const trunc = (num, places = 0) => {
  // if places is 0 or not supplied
  // return an integer as a String
  if (places <= 0) {
    return String(Math.trunc(num.split(' ')[0]))
  }
  
  // using the RexExp constructor to dynamically create a regex
  // so places = 2 -> regex = \d+\.\d{1,2}
  const regex = new RegExp(`\\d+\\.\\d{1,${places}}`)
  const match = num.match(regex)

  return (match !== null) ? match[0] : null
}

Again may need to deal with off inputs.

I have update post#21 and it’s codepen accordingly

1 Like

Thanks, Archibald, I tried your code but no value is truncated. The script is supposed to be executed after clicking on “Collocazione” field (so before clicking “Conferma” button).

function truncate() {
  let fieldV = document.querySelector("#collocazione").value;
  let digits, alpha = "";
  let firstAlpha = fieldV.search(/[A-Z]|[a-z]/);
  if(firstAlpha>0) alpha = fieldV.substr(firstAlpha-1);  // includes initial space character
  
  if(/^80[1-7]/.test(fieldV))     digits = fieldV.substring(0,3);
  if(/^839.3[1,6]/.test(fieldV))  digits = fieldV.substring(0,6);
  if(/^8[4-6][0-9]/.test(fieldV)) digits = fieldV.substring(0,5);
  
  if(/^914/.test(fieldV))         digits = fieldV.substring(0,3);
  if(/^914.563 2/.test(fieldV))   digits = "914.5";
  
  let truncated = digits + alpha;
  
  alert("Truncated string: " + truncated);
}