28

他のインスタンスに影響を与えることなく、文字列内の一致の単一のインスタンスを識別して置き換える正規表現関数を作成しようとしています。たとえば、次の文字列があります。

12||34||56

この文字列を取得するには、パイプの2番目のセットをアンパサンドに置き換えたいと思います。

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固有の正規表現命令を使用することはできません。

4

3 に答える 3

31

より汎用的な関数

この質問に出くわしました。タイトルは非常に一般的ですが、受け入れられた回答は質問の特定のユースケースのみを扱います。

もっと汎用的なソリューションが必要だったので、それを書き、ここで共有したいと思いました。

使用法

この関数には、次の引数を渡す必要があります。

// 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 つの引数を取ることです。次のように、メソッドとして String プロトタイプに追加することで、必要な引数が 3 つだけになるように簡略化できます。

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 をサポートする必要がある場合は、頑張ってください。:)

于 2011-10-31T19:24:42.793 に答える
20

これが機能するものです:

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

ここで、nはn番目のパイプより1つ小さいパイプです(もちろん、n = 0の場合、最初の部分式は必要ありません)

そして、これを行う関数が必要な場合:

function pipe_replace(str,n) {
   var RE = new RegExp("^((?:[0-9]+\\|\\|){" + (n-1) + "})([0-9]+)\|\|");
   return str.replace(RE,"$1$2&&");
}
于 2008-08-30T17:40:39.960 に答える
0
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;
        }
    });
}
于 2009-07-25T07:42:01.490 に答える