Incrementing specific numbers contained within a string

Viewed 90

I'm working with strings that look like this:

"do3mi3so3 la3 ti4 do5 re5 mi5 /2 x2 fa4"

I'd like to increment each number in the string past a specific point exempting numbers next to '/' or 'x'. For example, if I want to increment all the numbers of the aforementioned string past the point do3mi3so3, I'd expect to get this:

"do3mi3so3 la4 ti5 do6 re6 mi6 /2 x2 fa5"

Here is my code:

function is_numeric(str){
    return /^\d+$/.test(str);
}


function change_octave(notes,split_point){
  for(var i=(split_point-1);i<notes.length;i++){
    if(is_numeric(notes[i])==true){
      notes[i]='' + parseInt(notes[i])+1;
      //console.log(parseInt(notes[i])+1) //these numbers are incrementing
    }
    if(notes[i]=='/'||notes[i]=='x'){
      i = i+3;
    }
  }
  return notes;
}
var notes = "do3mi3so3 la3 ti4 do5 re5 mi5 /2 x2 fa4";
console.log(change_octave(notes,4));

Despite the numbers successfully incrementing, the value of the string does not change.

Thanks.

5 Answers

You can't actually set the character of a string at a specific index using something like notes[i] = "a". What you can do is split the string into an array, change the values in that array, and then join it back together into a string when you're done.

function is_numeric(str){
    return /^\d+$/.test(str);
}

function change_octave(notesStr,split_point){
  const notes = notesStr.split('');
  for(var i=(split_point-1);i<notes.length;i++){
    if(is_numeric(notes[i])==true){
      const newValue = parseInt(notes[i]) + 1;
      notes[i] = '' + newValue;
    }
    if(notes[i]=='/'||notes[i]=='x'){
      i = i+3;
    }
  }
  return notes.join('');
}
var notes = "do3mi3so3 la3 ti4 do5 re5 mi5 /2 x2 fa4";
console.log(change_octave(notes,4));

You can't change the character in the string by index directly. For example:

   notes="aaa"
   notes[1] = "b"
   console.log(notes)

The solution is:

function is_numeric(str){
    return /^\d+$/.test(str);
}


function change_octave(notes,split_point){
  modified_notes = notes.split('')
  for(var i=(split_point-1);i<notes.length;i++){
    if(is_numeric(notes[i])==true){
      modified_notes[i]=''+(parseInt(notes[i])+1);
      //console.log(parseInt(notes[i])+1) //these numbers are incrementing
    }
    if(notes[i]=='/'||notes[i]=='x'){
      i = i+3;
    }
  }
  
  return  modified_notes.join('')  
}
var notes = "do3mi3so3 la3 ti4 do5 re5 mi5 /2 x2 fa4";
console.log(change_octave(notes,4));

In JavaScript, a string is a primitive type and is therefore immutable. By default, in so-called "sloppy mode", assigning a value to an element of a string silently fails, but in strict mode it will throw an error:

"use strict";
let notes = "do3mi3so3 la3 ti4 do5 re5 mi5 /2 x2 fa4";
notes[4] = "o";

In order to accomplish this, I would suggest making use of String.prototype.replace() like this:

function change_octave(notes, split_point) {
  return notes.replace(/\d+/g, (str, i) => {
    if (--split_point > 0) {
      return str;
    }

    if (i > 0 && (notes[i - 1] === '/' || notes[i - 1] === 'x')) {
      return str;
    }

    return Number(str) + 1;
  });
}

let notes = "do3mi3so3 la3 ti4 do5 re5 mi5 /2 x2 fa4";

console.log(change_octave(notes, 4));

I modified your code a bit, and I think it works now!

 function is_numeric(str) {
        return /^\d+$/.test(str);
    }

    function change(note, point) {
        var arr = note.split("")
        var ind = note.indexOf(point)
        ind = ind + point.length -1;
        for (var i = 0; i < arr.length; i++) {
            if (i > ind && is_numeric(arr[i])) {
                if (arr[i - 1] !== undefined && arr[i - 1] !== "/" && arr[i - 1] !== "x") {
                    arr[i] = "" + (parseInt(arr[i]) + 1);
                }
            }
        }
        return arr.join("")
    }
    var notes = "do3mi3so3 la3 ti4 do5 re5 mi5 /2 x2 fa4";
    console.log(change(notes, "do3mi3so3"))

Output:

do3mi3so3 la4 ti5 do6 re6 mi6 /2 x2 fa5

If you want to use index position as point, then

function change(note, point) {
        var arr = note.split("")
        var ind = point
        for (var i = 0; i < arr.length; i++) {
            if (i > ind && is_numeric(arr[i])) {
                if (arr[i - 1] !== undefined && arr[i - 1] !== "/" && arr[i - 1] !== "x") {
                    arr[i] = "" + (parseInt(arr[i]) + 1);
                }
            }
        }
        return arr.join("")
    }

If you want to increment the specific part of the example string, you might use a pattern to list the exact matches \b([ds]o|re|mi|fa|la|ti)\b and use word boundaries to prevent partial matches.

In the callback of replace, you can increment the group match for the digits.

let regex = /\b([ds]o|re|mi|fa|la|ti)(\d+)\b/g;
const change_octave = notes => notes.replace(regex, (_, g1, g2) => g1 + ++g2);

console.log(change_octave("do3mi3so3 la3 ti4 do5 re5 mi5 /2 x2 fa4"));

For specifying the point as the number of characters that should be after the start of the string, you could use a lookbehind for which is this browser support and a dynamic pattern to set the point

const change_octave = (notes, point) => {
  let regex = new RegExp(`(?<=^.{${point}}.*\\b(?:[ds]o|re|mi|fa|la|ti))\\d+\\b`, "g");
  return notes.replace(regex, m => ++m);
}

console.log(change_octave("do3mi3so3 la3 ti4 do5 re5 mi5 /2 x2 fa4", 4));
console.log(change_octave("la3 ti4 do5 re5 mi5 /2 x2 fa4", 4));

Related