Замена n-го экземпляра совпадения регулярного выражения в Javascript

Я пытаюсь написать функцию регулярного выражения, которая будет определять и заменять один экземпляр совпадения в строке, не затрагивая другие экземпляры. Например, у меня есть такая строка:

12||34||56

Я хочу заменить второй набор каналов на амперсанды, чтобы получить эту строку:

12||34&&56

Функция регулярного выражения должна иметь возможность обрабатывать x количество каналов и позволять мне заменять n-й набор каналов, чтобы я мог использовать ту же функцию для выполнения этих замен:

23||45||45||56||67 -> 23&&45||45||56||67

23||34||98||87 -> 23||34||98&&87

Я знаю, что могу просто разделить / заменить / объединить строку в каналах, и я также знаю, что могу сопоставить на /\|\|/ и перебирать полученный массив, но мне интересно узнать, можно ли написать одно выражение, которое может это сделать. Обратите внимание, что это будет для Javascript, поэтому можно сгенерировать регулярное выражение во время выполнения с помощью eval(), но невозможно использовать какие-либо специфичные для Perl инструкции регулярного выражения.

Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
27
0
16 409
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

вот что работает:

"23||45||45||56||67".replace(/^((?:[0-9]+\|\|){n})([0-9]+)\|\|/,"&&")

где n меньше n-го канала (конечно, вам не нужно это первое подвыражение, если n = 0)

И если вы хотите, чтобы функция выполняла это:

function pipe_replace(str,n) {
   var RE = new RegExp("^((?:[0-9]+\|\|){" + (n-1) + "})([0-9]+)\|\|");
   return str.replace(RE,"&&");
}

function pipe_replace(str,n) {
    m = 0;
    return str.replace(/\|\|/g, function (x) {
        //was n++ should have been m++
        m++;
        if (n==m) {
            return "&&";
        } else {
            return x;
        }
    });
}

Функция более общего назначения

Я столкнулся с этим вопросом, и, хотя заголовок носит очень общий характер, принятый ответ касается только конкретного варианта использования вопроса.

Мне нужно было более универсальное решение, поэтому я написал его и решил поделиться им здесь.

использование

Эта функция требует, чтобы вы передали ей следующие аргументы:

Примеры

// Pipe examples like the OP's
replaceNthMatch("12||34||56", /(\|\|)/, 2, '&&') // "12||34&&56"
replaceNthMatch("23||45||45||56||67", /(\|\|)/, 1, '&&') // "23&&45||45||56||67"

// Replace groups of digits
replaceNthMatch("foo-1-bar-23-stuff-45", /(\d+)/, 3, 'NEW') // "foo-1-bar-23-stuff-NEW"

// Search value can be a string
replaceNthMatch("foo-stuff-foo-stuff-foo", "foo", 2, 'bar') // "foo-stuff-bar-stuff-foo"

// No change if there is no match for the search
replaceNthMatch("hello-world", "goodbye", 2, "adios") // "hello-world"

// No change if there is no Nth match for the search
replaceNthMatch("foo-1-bar-23-stuff-45", /(\d+)/, 6, 'NEW') // "foo-1-bar-23-stuff-45"

// Passing in a function to make the replacement
replaceNthMatch("foo-1-bar-23-stuff-45", /(\d+)/, 2, function(val){
  //increment the given value
  return parseInt(val, 10) + 1;
}); // "foo-1-bar-24-stuff-45"

Код

  var replaceNthMatch = function (original, pattern, n, replace) {
    var parts, tempParts;

    if (pattern.constructor === RegExp) {

      // If there's no match, bail
      if (original.search(pattern) === -1) {
        return original;
      }

      // Every other item should be a matched capture group;
      // between will be non-matching portions of the substring
      parts = original.split(pattern);

      // If there was a capture group, index 1 will be
      // an item that matches the RegExp
      if (parts[1].search(pattern) !== 0) {
        throw {name: "ArgumentError", message: "RegExp must have a capture group"};
      }
    } else if (pattern.constructor === String) {
      parts = original.split(pattern);
      // Need every other item to be the matched string
      tempParts = [];

      for (var i=0; i < parts.length; i++) {
        tempParts.push(parts[i]);

        // Insert between, but don't tack one onto the end
        if (i < parts.length - 1) {
          tempParts.push(pattern);
        }
      }
      parts = tempParts;
    }  else {
      throw {name: "ArgumentError", message: "Must provide either a RegExp or String"};
    }

    // Parens are unnecessary, but explicit. :)
    indexOfNthMatch = (n * 2) - 1;

  if (parts[indexOfNthMatch] === undefined) {
    // There IS no Nth match
    return original;
  }

  if (typeof(replace) === "function") {
    // Call it. After this, we don't need it anymore.
    replace = replace(parts[indexOfNthMatch]);
  }

  // Update our parts array with the new value
  parts[indexOfNthMatch] = replace;

  // Put it back together and return
  return parts.join('');

  }

Альтернативный способ его определения

Наименее привлекательной частью этой функции является то, что она принимает 4 аргумента. Его можно упростить, потребовав только 3 аргумента, добавив его как метод к прототипу String, например:

String.prototype.replaceNthMatch = function(pattern, n, replace) {
  // Same code as above, replacing "original" with "this"
};

Если вы это сделаете, вы можете вызвать метод для любой строки, например:

"foo-bar-foo".replaceNthMatch("foo", 2, "baz"); // "foo-bar-baz"

Прохождение тестов

Ниже приведены тесты Jasmine, которые проходит эта функция.

describe("replaceNthMatch", function() {

  describe("when there is no match", function() {

    it("should return the unmodified original string", function() {
      var str = replaceNthMatch("hello-there", /(\d+)/, 3, 'NEW');
      expect(str).toEqual("hello-there");
    });

  });

  describe("when there is no Nth match", function() {

    it("should return the unmodified original string", function() {
      var str = replaceNthMatch("blah45stuff68hey", /(\d+)/, 3, 'NEW');
      expect(str).toEqual("blah45stuff68hey");
    });

  });

  describe("when the search argument is a RegExp", function() {

    describe("when it has a capture group", function () {

      it("should replace correctly when the match is in the middle", function(){
        var str = replaceNthMatch("this_937_thing_38_has_21_numbers", /(\d+)/, 2, 'NEW');
        expect(str).toEqual("this_937_thing_NEW_has_21_numbers");
      });

      it("should replace correctly when the match is at the beginning", function(){
        var str = replaceNthMatch("123_this_937_thing_38_has_21_numbers", /(\d+)/, 2, 'NEW');
        expect(str).toEqual("123_this_NEW_thing_38_has_21_numbers");
      });

    });

    describe("when it has no capture group", function() {

      it("should throw an error", function(){
        expect(function(){
          replaceNthMatch("one_1_two_2", /\d+/, 2, 'NEW');
        }).toThrow('RegExp must have a capture group');
      });

    });


  });

  describe("when the search argument is a string", function() {

    it("should should match and replace correctly", function(){
      var str = replaceNthMatch("blah45stuff68hey", 'stuff', 1, 'NEW');
      expect(str).toEqual("blah45NEW68hey");
    });

  });

  describe("when the replacement argument is a function", function() {

    it("should call it on the Nth match and replace with the return value", function(){

      // Look for the second number surrounded by brackets
      var str = replaceNthMatch("foo[1][2]", /(\[\d+\])/, 2, function(val) {

        // Get the number without the [ and ]
        var number = val.slice(1,-1);

        // Add 1
        number = parseInt(number,10) + 1;

        // Re-format and return
        return '[' + number + ']';
      });
      expect(str).toEqual("foo[1][3]");

    });

  });

});

Может не работать в IE7

Этот код может дать сбой в IE7, потому что этот браузер неправильно разбивает строки с помощью регулярного выражения, как обсуждалось здесь. [качает кулаком IE7]. Я считаю, что это - это решение; Если вам нужна поддержка IE7, удачи. :)

Браво! Это должен быть пакет Node.js и Bower.

vaughan 24.01.2014 11:14

@vaughan - Спасибо! Не стесняйтесь сделать это, если хотите.

Nathan Long 24.01.2014 15:35

Что, если регулярное выражение содержит более одной пары круглых скобок?

principal-ideal-domain 06.06.2014 22:45

@ Principal-ideal-domain имеет хороший смысл. Я не могу использовать это, потому что в моем шаблоне есть круглые скобки. Я пытаюсь найти изменение в коде, чтобы исправить это, но теперь мне повезло.

Goose 22.07.2015 16:10

Другие вопросы по теме