Skip to content

Instantly share code, notes, and snippets.

@think49
Last active November 2, 2019 15:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save think49/3f6e788c02d4983b82173f7ae4c9df9d to your computer and use it in GitHub Desktop.
Save think49/3f6e788c02d4983b82173f7ae4c9df9d to your computer and use it in GitHub Desktop.
replace-by-index.js: 指定したインデックス範囲の文字列を置換

replace-by-index.js

概要

下記を引数として、置換処理を実行します。

  • 置換元の開始インデックス
  • 置換元の文字数
  • 置換先文字列

replaceByIndex (string, ...replacementList)

第一引数に「置換元の文字列」を、第二引数に下記要素値を持つ配列を指定して、置換跡の文字列を返します。

  • index=0 の要素値: 置換元の開始インデックス (Number 型)
  • index=1 の要素値: 置換元の文字数 (Number 型)
  • index=2 の要素値: 置換先文字列 (String 型)
replaceByIndex('Hello, World!', [7, 5, 'JavaScript']); // "Hello, JavaScript!"

第二引数以降は可変引数です。 第三引数以降に「第二引数と同じ構造を持つ配列」を指定する事で複数の置換を一括処理できます。

replaceByIndex('ABC', [0, 1, 'AAA'], [1, 1, 'BBB'], [2, 1, 'CCC']); // "AAABBBCCC"

可変引数の順番は自由です。 インデックスの若い順に並べる必要はありません。

replaceByIndex('ABC', [2, 1, 'CCC'], [1, 1, 'BBB'], [0, 1, 'AAA']); // "AAABBBCCC"

可変引数では、他と重複した範囲を指定することは出来ません。

replaceByIndex('AB', [0, 1, 'AAA'], [0, 2, 'BBBBBB']); // RangeError: The range of firstIndex and lastIndex must not overlap with other ranges: 0-12 and 7-12

この場合は、"AAA" の置換処理を無視し、"BBBBBB" の置換処理だけ実行すれば良いのですが、置換対象範囲が部分的に重複しているケースがあります。

replaceByIndex('AABB', [0, 2, 'AAA'], [1, 3, 'BBBBBB']); // RangeError: The range of firstIndex and lastIndex must not overlap with other ranges: 0-2 and 1-4

置換対象("AA"の半分だけ重複しており、置換先文字列("AAA")の半分、つまり、「"A" の1.5文字分」に置換することは不可能な為、重複範囲がある場合には置換できない仕様としました。

(余談) 固定長文字列用の実装案

「置換元の文字列長」と「置換先の文字列長」が同値となる固定長文字列を対象とする置換では、置換範囲が重複するケースに対応する事ができます。

  • 置換データ: [0, 6, 'AAAAAA'], [1, 4, 'BBBB'], [2, 2, 'CC']
  • 置換前: "000000"
  • 置換後: "ABCCBA"

1:1の対応関係で置換処理が実行されるので、重複範囲の切り出しが実現可能になります。 本関数では可変文字列で置換可能な設計とした為、上記仕様は採用できませんでした。

/**
* replace-by-index.js
* Replace a string in a specified range with another string
*
* @version 1.0.0
* @author think49
* @url https://gist.github.com/think49/3f6e788c02d4983b82173f7ae4c9df9d
* @license http://www.opensource.org/licenses/mit-license.php (The MIT License)
*/
const replaceByIndex = (() => {
'use strict';
function compareFirstIndex (replacementA, replacementB) {
return replacementA[0] - replacementB[0] || replacementA[1] - replacementB[1];
}
return function replaceByIndex (string, ...replacementList) {
let result = '', previousFirstIndex, lastIndex = 0;
replacementList.sort(compareFirstIndex);
for (let replacement of replacementList) {
const firstIndex = replacement[0];
if (firstIndex === previousFirstIndex || firstIndex < lastIndex) {
throw new RangeError('The range of firstIndex and lastIndex must not overlap with other ranges: ' + previousFirstIndex + '-' + lastIndex + ' and ' + firstIndex + '-' + (firstIndex + replacement[1]));
}
result += string.slice(lastIndex, firstIndex) + replacement[2];
previousFirstIndex = firstIndex;
lastIndex = firstIndex + replacement[1];
}
return result + string.slice(lastIndex);
};
})();
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<title>replace-by-index-1.0.0.js</title>
<script src="replace-by-index-1.0.0.js"></script>
</head>
<body>
<ul>
<li><a href="https://gist.github.com/think49/3f6e788c02d4983b82173f7ae4c9df9d">replace-by-index.js: 指定したインデックス範囲の文字列を置換 - GitHub</a></li>
</ul>
<script>
'use strict';
console.assert(replaceByIndex('Hello, World!Hello, World!', [7, 5, 'AAAAA'], [20, 5, 'BBBBB']), 'Hello, AAAAA!Hello, BBBBB!');
console.assert(replaceByIndex('Hello, World!Hello, World!', [20, 5, 'BBBBB'], [7, 5, 'AAAAA']), 'Hello, AAAAA!Hello, BBBBB!');
console.assert(replaceByIndex('ABC', [0, 1, 'XXX'], [1, 1, 'YYY'], [2, 1, 'ZZZ']), 'XXXYYYZZZ');
replaceByIndex('Hello, World!', [7, 5, 'AAAAA'], [0, 12, 'BBBBB']); // RangeError: The range of firstIndex and lastIndex must not overlap with other ranges:7-12 and 0-12
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment