0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

変数を正規表現に組み込みたい場合・先読みアサーション ( new RegExp() ・ ?= )

Posted at

今回は paiza の「文字列の重複カウント」の問題に挑戦!

久しぶりに正規表現を使った!


問題概要

入力

  • 1行目:文字列 s(探したいパターン)
  • 2行目:文字列 t(検索対象の文字列)
  • 両方とも半角アルファベットで構成される

目的

  • 文字列 t の中で s が何回出現するかをカウントする
  • 重なり部分も含めて数える(例えば "AA" が "AAA" に 2 回含まれる)

出力
  • st に出現する回数を整数で出力



入力例:

AA
abdeeAAbAAAbfde

出力例:

3






❌ NG例:

const rl = require('readline').createInterface({ input: process.stdin });

const lines = [];

rl.on('line', (input) => lines.push(input));

rl.on('close', () => {
    const s = lines[0]; 
    const t = lines[1]; 

    // ❌
    const result = t.match(/(?=${s})/g).length;
    
    console.log(result);
});
  • /…/ は 正規表現リテラル で、文字列展開(${s})は効かない

  • つまり s 変数の内容は反映されず、文字列 ${s} を探すことになる






✅ OK例:

const rl = require('readline').createInterface({ input: process.stdin });

const lines = [];

rl.on('line', (input) => lines.push(input));

rl.on('close', () => {
    const s = lines[0]; // 探したいパターン
    const t = lines[1]; // 対象の文字列

    // 動的に正規表現を生成
    const regex = new RegExp('(?=${s})', 'g');
    const matches = t.match(regex);
    
    console.log(matches ? matches.length : 0);
});

1️⃣ 入力データの取り出し

const s = lines[0]; // 探したいパターン
const t = lines[1]; // 対象の文字列
  • s が検索する部分文字列。
  • t がその中で何回出現するかをカウントする対象文字列。



2️⃣ 正規表現を動的に作成

const regex = new RegExp(`(?=${s})`, 'g');

  • (?=…) は 先読みアサーション(後ろに続く文字列が s である位置をマッチ)

  • 先読みアサーションは文字を消さずにマッチ位置だけを検出できるので、重複して出現するパターンも正しく数えられる。

  • ‘(?=${s})’ のように テンプレートリテラル を使うことで、変数 s を正規表現に組み込んでいる。

  • ‘g’ フラグで 全体検索、マッチするすべての位置を取得。



3️⃣ マッチングしてカウント

const matches = t.match(regex);
console.log(matches ? matches.length : 0);
  • t.match(regex) は、マッチした全ての位置の配列を返す。

  • matches.length が出現回数。


  • マッチがなければ matchesnull になるので、三項演算子で 0 を返している。



4️⃣ ポイント

  • 先読みアサーションを使うことで、文字列を消さずに重なりも含めて数えられる

  • new RegExp で動的に文字列を正規表現に組み込む

  • .match() の戻り値は配列、ない場合は null






💡先読みアサーションとは


  • 正規表現の中で (?=pattern) の形で使う。

  • 「この位置の後ろに pattern があるかを確認する」という意味。

  • 実際には文字を消費しない(マッチしても文字列自体は消えない)。



🔍 ポイント

  • 普通の正規表現はマッチすると文字を「読んだ」とみなす。

  • 先読みは文字を読まずに「ここから pattern が続いているか」をチェックするだけ。

  • だから重なっているパターンも正しく数えられる。



🔍 例

const t = "AAAA";
const regex = /(?=AA)/g;
const matches = t.match(regex);
console.log(matches.length); // 3

解説:

  • t = “AAAA” の中で “AA” は重なりながら出現している:
    • 1文字目から: “AA”
    • 2文字目から: “AA”
    • 3文字目から: “AA”

  • 📌 通常の /AA/g だとマッチした “AA” の文字を消費するので 2回しかカウントできない。

  • 先読み (?=AA) は文字を消費しないので、すべての開始位置をカウントできる → 3






🗒️ まとめ


  • /…/ の中では ${…} は変数展開されない

  • 変数を正規表現に組み込みたい場合は new RegExp(s, “g”) のように書く。

  • 例えば:
const regex = new RegExp(`(?=${s})`, "g");
const matches = t.match(regex);

これなら s の値が反映され、正しくマッチングできる


  • 先読みアサーション (?=pattern) は「この位置の後ろに pattern があるかチェックするが文字は消費しない」ので、重なった部分も含めてマッチを数えられる。




僕の失敗談(´;ω;`)と解決法🐈






おまけ:正規表現を使わない

const fs = require("fs");

// 入力を読み込み、改行で分割して s と t に代入
const input = fs.readFileSync("/dev/stdin", "utf-8").trim();
const [s, t] = input.split("\n");

let count = 0;

// t の先頭から順に s と同じ長さの部分文字列を比較
for (let i = 0; i + s.length <= t.length; i++) {
  if (t.substr(i, s.length) === s) {
    count++;
  }
}

// 出現回数を出力
console.log(count);
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?