HHeLiBeXの日記 正道編

日々の記憶の記録とメモ‥

各言語で正規表現「^」「$」「\A」「\z」を試してみる

ということで、あちこちから突っ込みが来ないことを祈りつつ(謎)、手元にある各言語でテストプログラムを書いてみたメモ。

入力は以下のような文字列。

abc
123
*+=

最大4つのパターンを試すが、すべてのパターンでマッチすると、

1234

のように出力される。逆に、マッチしないパターンや、そもそも存在しないマッチ方法の場合は「0」や「-」をそれぞれ出力する。

環境

手元にあるものということで、環境は以下のものに限定する。

Java

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.Reader;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Main {
    private static boolean matches(String str, String pattern, int flags) {
        Pattern p = Pattern.compile(pattern, flags);
        Matcher m = p.matcher(str);
        return m.matches();
    }

    public static void main(String[] args) {
        try (Reader in = new InputStreamReader(System.in);
            PrintWriter out = new PrintWriter(System.out)
        ) {
            char[] buf = new char[1024];
            int len = in.read(buf);
            String s = new String(buf, 0, len);
//          s = s.trim();

            if (matches(s, "^[0-9]+$", 0)) {
                out.print("1");
            } else {
                out.print("0");
            }

            if (matches(s, "^[0-9]+$", Pattern.MULTILINE)) {
                out.print("2");
            } else {
                out.print("0");
            }

            if (matches(s, "\\A[0-9]+\\z", 0)) {
                out.print("3");
            } else {
                out.print("0");
            }

            if (matches(s, "\\A[0-9]+\\z", Pattern.MULTILINE)) {
                out.print("4");
            } else {
                out.print("0");
            }

            out.println();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

出力。

0000

どのパターンでもマッチしない。完全にマッチしないとダメなようだ。

C

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <regex.h>

int matches(const char* str, const char* pattern, int flags) {
    regex_t rb;
    if (regcomp(&rb, pattern, flags)) {
        perror(pattern);
        exit(1);
    }

    regmatch_t rm;
    int res;
    if (!regexec(&rb, str, 1, &rm, 0)) {
        res = 1;
    } else {
        res = 0;
    }

    regfree(&rb);

    return res;
}

int main(int argc, char** argv) {
    char str[1024];
    memset(str, '\0', sizeof(str));

    fread(str, sizeof(str), sizeof(char), stdin);
//  while (str[strlen(str) - 1] == '\n' || str[strlen(str) - 1] == '\r') {
//      str[strlen(str) - 1] = '\0';
//  }

    if (matches(str, "^[0-9]+$", REG_EXTENDED)) {
        printf("1");
    } else {
        printf("0");
    }

    // そもそも複数行モードが無い
    printf("-");

    // 文字列の先頭・末尾という正規表現が無い
    printf("-");

    // そもそも複数行モードが無い
    printf("-");

    printf("\n");

    return 0;
}

出力。

0---

まぁ、C言語は仕方がない。パターンが1つしかないので。

C++

#include <iostream>
#include <locale>
#include <string>
#include <boost/regex.hpp>

using namespace std;

bool matches(string str, const char* pattern, boost::match_flag_type flags) {
    boost::regex re(pattern);

    boost::smatch sm;
    return boost::regex_search(str, sm, re, flags);
}

int main(int argc, char** argv) {
    istreambuf_iterator<char> it(cin);
    istreambuf_iterator<char> last;
    string str(it, last);
//  str.erase(str.find_last_not_of("\r\n") + 1);

    if (matches(str, "^[0-9]+$", boost::regex_constants::match_single_line)) {
        cout << "1";
    } else {
        cout << "0";
    }

    if (matches(str, "^[0-9]+$", boost::regex_constants::match_default)) {
        cout << "2";
    } else {
        cout << "0";
    }

    if (matches(str, "\\A[0-9]+\\z", boost::regex_constants::match_single_line)) {
        cout << "3";
    } else {
        cout << "0";
    }

    if (matches(str, "\\A[0-9]+\\z", boost::regex_constants::match_default)) {
        cout << "4";
    } else {
        cout << "0";
    }

    cout << endl;

    return EXIT_SUCCESS;
}

出力。

0200

これがうわさに聞く、複数行モードで「^」「$」を使うと部分文字列にマッチするというものか。

最初のパターンでわざわざ「boost::regex_constants::match_single_line」をフラグに指定していることから分かるように、C++(Boost)のデフォルトは複数行モードのようだ。

PHP

<?php

$s = file_get_contents('php://stdin');
//$s = trim($s);

if (preg_match("/^[0-9]+$/", $s)) {
    echo '1';
} else {
    echo '0';
}

if (preg_match("/^[0-9]+$/m", $s)) {
    echo '2';
} else {
    echo '0';
}

if (preg_match("/\A[0-9]+\z/", $s)) {
    echo '3';
} else {
    echo '0';
}

if (preg_match("/\A[0-9]+\z/m", $s)) {
    echo '4';
} else {
    echo '0';
}

echo PHP_EOL;

出力。

0200

同様に、複数行モードだと「^」「$」を使うと部分文字列にマッチする。

Python 2 / 3

import sys
import re

s = sys.stdin.read()
#s = s.strip()

if re.search(r'^[0-9]+$', s):
    sys.stdout.write('1')
else:
    sys.stdout.write('0')

if re.search(r'^[0-9]+$', s, re.MULTILINE):
    sys.stdout.write('2')
else:
    sys.stdout.write('0')

if re.search(r'\A[0-9]+\Z', s):
    sys.stdout.write('3')
else:
    sys.stdout.write('0')

if re.search(r'\A[0-9]+\Z', s, re.MULTILINE):
    sys.stdout.write('4')
else:
    sys.stdout.write('0')

sys.stdout.write("\n")

出力。

0200

同様に、複数行モードだと「^」「$」を使うと部分文字列にマッチする。

なお、文字列の末尾を表す正規表現が「\z」ではなく「\Z」となることに注意。

Ruby

s = STDIN.read
#s.chomp!

# 単一行モードが無いので。
print "-"

if s.match(/^[0-9]+$/)
    print "2"
else
    print "0"
end

# 単一行モードが無いので。
print "-"

if s.match(/\A[0-9]+\z/)
    print "4"
else
    print "0"
end

print "\n"

出力。

-2-0

調べた限りでは複数行モードしかなかったので、複数行モードのみの出力。

確かに、「^」「$」で部分文字列にマッチする。

Perl

my $s;
{
    local $/ = undef;
    $s = <STDIN>;
}
#chomp($s);

if ($s =~ /^[0-9]+$/) {
    print '1';
} else {
    print '0';
}

if ($s =~ /^[0-9]+$/m) {
    print '2';
} else {
    print '0';
}

if ($s =~ /\A[0-9]+\z/) {
    print '3';
} else {
    print '0';
}

if ($s =~ /\A[0-9]+\z/m) {
    print '4';
} else {
    print '0';
}

print "\n";

出力。

0200

PHPと同様に、mフラグを付けてやると複数行モードで、「^」「$」を使用すると部分文字列にマッチする。

Go

package main

import (
    "fmt"
    "os"
    "regexp"
    "bufio"
    "io/ioutil"
//  "strings"
)

func main() {
    stdin := bufio.NewReader(os.Stdin)
    b, _ := ioutil.ReadAll(stdin)
    s := string(b)
//  s = strings.Trim(s, "\r\n")

    {
        m := regexp.MustCompile(`^[0-9]+$`)
        if m.MatchString(s) {
            fmt.Print("1")
        } else {
            fmt.Print("0")
        }
    }

    {
        m := regexp.MustCompile(`(?m)^[0-9]+$`)
        if m.MatchString(s) {
            fmt.Print("2")
        } else {
            fmt.Print("0")
        }
    }

    {
        m := regexp.MustCompile(`\A[0-9]+\z`)
        if m.MatchString(s) {
            fmt.Print("3")
        } else {
            fmt.Print("0")
        }
    }

    {
        m := regexp.MustCompile(`(?m)\A[0-9]+\z`)
        if m.MatchString(s) {
            fmt.Print("4")
        } else {
            fmt.Print("0")
        }
    }

    fmt.Println()
}

出力。

0200

PHPと同様に、mフラグを付けてやると複数行モードで、「^」「$」を使用すると部分文字列にマッチする。

まとめ

表にまとめると、以下のような感じか。マッチするケースに「○」、マッチしないケースに「×」を入れている。存在しないパターンは「-」としている。

  • (1) 単一行モードで、「^」「$」を使ったパターン
  • (2) 複数行モードで、「^」「$」を使ったパターン
  • (3) 単一行モードで、「\A」「\z」を使ったパターン
  • (4) 複数行モードで、「\A」「\z」を使ったパターン
(1) (2) (3) (4)
Java × × × ×
C ×
C++ × × ×
PHP × × ×
Python 2 / 3 × × ×
Ruby ×
Perl × × ×
Go × × ×

自分も「^」「$」をついつい使ってしまっていたので、気に留めておくことにしよう。