Programing

정규식 대체를 사용하여 숫자를 증가시킬 수 있습니까?

lottogame 2020. 12. 5. 09:07
반응형

정규식 대체를 사용하여 숫자를 증가시킬 수 있습니까?


정규식 대체를 사용하여 숫자를 증가시킬 수 있습니까? 물론 평가 / 함수 기반 대체를 사용하지 않습니다 .

이 질문은 질문자가 텍스트 편집기에서 숫자를 증가시키려는 또 다른 질문에서 영감을 받았습니다 . 완전한 스크립팅을 지원하는 것보다 정규식 대체를 지원하는 텍스트 편집기가 더 많을 것이므로 정규식이 존재하는 경우 이동하는 것이 편리 할 수 ​​있습니다.

또한 영리한 해결책부터 거의 쓸모없는 문제까지 깔끔한 것을 자주 배워서 궁금합니다.

우리가 음이 아닌 십진 정수, 즉 \d+.

  • 단일 대체로 가능합니까? 아니면 제한된 수의 대체?

  • 그렇지 않다면, 적어도 9999까지의 숫자와 같이 상한이 주어 졌 습니까?

물론 while-loop (일치하는 동안 대체)이 주어지면 가능하지만 여기서는 루프없는 솔루션을 사용합니다.


이 질문의 주제는 제가 이전에 한 특정 구현에 대해 저를 즐겁게했습니다. 내 솔루션은 두 가지 대체물이므로 게시하겠습니다.

내 구현 환경은 solaris입니다. 전체 예 :

echo "0 1 2 3 7 8 9 10 19 99 109 199 909 999 1099 1909" |
perl -pe 's/\b([0-9]+)\b/0$1~01234567890/g' |
perl -pe 's/\b0(?!9*~)|([0-9])(?=9*~[0-9]*?\1([0-9]))|~[0-9]*/$2/g'

1 2 3 4 8 9 10 11 20 100 110 200 910 1000 1100 1910

설명을 위해 분리 :

s/\b([0-9]+)\b/0$1~01234567890/g

각 번호 (#)에 대해 0 # ~ 01234567890으로 바꿉니다. 처음 0은 9에서 10으로 반올림해야하는 경우입니다. 01234567890 블록은 증분 용입니다. "9 10"의 예제 텍스트는 다음과 같습니다.

09~01234567890 010~01234567890

다음 정규식의 개별 조각은 개별적으로 설명 할 수 있으며 파이프를 통해 결합되어 대체 횟수를 줄입니다.

s/\b0(?!9*~)/$2/g

반올림 할 필요가없는 모든 숫자 앞에있는 "0"자리를 선택하고 버립니다.

s/([0-9])(?=9*~[0-9]*?\1([0-9]))/$2/g

(? =)는 긍정적 인 예측이고, \ 1은 일치 그룹 # 1입니다. 따라서 이것은 '~'표시가 나올 때까지 뒤에 9가 오는 모든 숫자를 일치시킨 다음 조회 테이블로 이동하여이 숫자 다음의 숫자를 찾습니다. 조회 테이블에서 다음 숫자로 바꿉니다. 따라서 정규식 엔진이 숫자를 구문 분석 할 때 "09 ~"는 "19 ~"가되고 "10 ~"이됩니다.

s/~[0-9]*/$2/g

이 정규식은 ~ 조회 테이블을 삭제합니다.


와우, 가능하다는 것이 밝혀졌습니다 (추악하지만)!

시간이 없거나 전체 설명을 읽을 시간이없는 경우 다음과 같은 코드를 사용합니다.

$str = '0 1 2 3 4 5 6 7 8 9 10 11 12 13 19 20 29 99 100 139';
$str = preg_replace("/\d+/", "$0~", $str);
$str = preg_replace("/$/", "#123456789~0", $str);
do
{
$str = preg_replace(
    "/(?|0~(.*#.*(1))|1~(.*#.*(2))|2~(.*#.*(3))|3~(.*#.*(4))|4~(.*#.*(5))|5~(.*#.*(6))|6~(.*#.*(7))|7~(.*#.*(8))|8~(.*#.*(9))|9~(.*#.*(~0))|~(.*#.*(1)))/s",
    "$2$1",
    $str, -1, $count);
} while($count);
$str = preg_replace("/#123456789~0$/", "", $str);
echo $str;

이제 시작하겠습니다.

따라서 먼저 다른 사람들이 언급했듯이 루프를 반복하더라도 단일 교체가 불가능합니다 (해당 증분을 단일 숫자에 삽입하는 방법 때문에). 그러나 먼저 문자열을 준비하면 반복 할 수있는 단일 대체가 있습니다. 다음은 PHP를 사용한 데모 구현입니다.

이 테스트 문자열을 사용했습니다.

$str = '0 1 2 3 4 5 6 7 8 9 10 11 12 13 19 20 29 99 100 139';

우선, 마커 문자를 추가하여 증분하려는 모든 숫자를 표시해 보겠습니다 (저는를 사용 ~하지만 대상 문자열에서 확실히 발생하지 않는 미친 유니 코드 문자 또는 ASCII 문자 시퀀스를 사용해야합니다.

$str = preg_replace("/\d+/", "$0~", $str);

한 번에 숫자 당 하나의 숫자 (오른쪽에서 왼쪽으로)를 교체 할 것이기 때문에 모든 숫자 뒤에 마킹 문자를 추가 할 것입니다.

이제 주요 해킹이 나옵니다. 문자열 끝에 약간의 '조회'를 추가합니다 (또한 문자열에서 발생하지 않는 고유 한 문자로 구분됩니다. 단순화를 위해 #).

$str = preg_replace("/$/", "#123456789~0", $str);

이를 사용하여 숫자를 해당 후계자로 대체합니다.

이제 루프가 있습니다.

do
{
$str = preg_replace(
    "/(?|0~(.*#.*(1))|1~(.*#.*(2))|2~(.*#.*(3))|3~(.*#.*(4))|4~(.*#.*(5))|5~(.*#.*(6))|6~(.*#.*(7))|7~(.*#.*(8))|8~(.*#.*(9))|9~(.*#.*(~0))|(?<!\d)~(.*#.*(1)))/s",
    "$2$1",
    $str, -1, $count);
} while($count);

좋아, 무슨 일이야? 일치 패턴에는 가능한 모든 숫자에 대해 하나의 대안이 있습니다. 이것은 숫자를 후속 작업에 매핑합니다. 예를 들어 첫 번째 대안을 사용하십시오.

0~(.*#.*(1))

이것은 0증분 마커가 뒤 따르는 any ~와 일치하고, 치트 구분 기호 및 해당 후속 항목까지 모든 것을 일치 시킵니다 (그래서 모든 숫자를 거기에 넣는 이유입니다). 교체품을 살펴보면 $2$1다음으로 교체됩니다 (그런 다음 다시 제자리에 배치하기 위해 1일치하는 모든 항목 ~이됩니다). 우리 ~는 프로세스에서 삭제합니다 . 숫자를에서 0까지 증가시키는 1것으로 충분합니다. 번호가 성공적으로 증가했으며 이월이 없습니다.

다음 8 개의 대안은에 대한 숫자 1대해 정확히 동일 합니다 8. 그런 다음 두 가지 특별한 경우를 처리합니다.

9~(.*#.*(~0))

를 대체 할 때 9증분 마커를 삭제하지 않고 0대신 결과 왼쪽에 배치합니다 . 이것은 (주변 루프와 결합 된) 캐리 오버 전파를 구현하기에 충분합니다. 이제 특별한 경우가 하나 남았습니다. 9s 로만 구성된 모든 숫자의 경우 숫자 앞에이 ~표시됩니다. 이것이 마지막 대안입니다.

(?<!\d)~(.*#.*(1))

~숫자가 앞에 나오지 않는 a 만나면 (그러므로 음의 룩 비하인드가 있음) 숫자를 통해 끝까지 전달되었을 것이므로 간단히 1. 나는 우리가 부정적인 룩백 (negative lookbehind)이 필요하지 않다고 생각하지만 (이것이 마지막으로 확인 된 대안이기 때문에),이 방법이 더 안전하다고 느낍니다.

(?|...)전체 패턴 주변 에 대한 짧은 메모 . 이것은 우리가 항상 같은 참조 $1$2(문자열 아래의 더 큰 숫자 대신) 대안의 두 일치를 찾는 것을 보장합니다 .

마지막으로 DOTALL수정 자 ( s)를 추가하여 줄 바꿈이 포함 된 문자열에서 작동하도록합니다 (그렇지 않으면 마지막 줄의 숫자 만 증가됨).

이것은 상당히 간단한 교체 문자열을 만듭니다. 먼저 작성 $2(후속 자 및 가능하면 이월 마커)을 작성한 다음 일치하는 다른 모든 항목을 $1.

그게 다야! 문자열 끝에서 해킹을 제거하기 만하면됩니다.

$str = preg_replace("/#123456789~0$/", "", $str);
echo $str;
> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 20 21 30 100 101 140

그래서 우리는 정규 표현식으로 전적으로 이것을 할 수 있습니다. 그리고 우리가 가진 유일한 루프는 항상 동일한 정규식을 사용합니다. 나는 이것이 우리가 preg_replace_callback().

물론 문자열에 소수점이있는 숫자가 있으면 끔찍한 일을 할 것입니다. 그러나 그것은 아마도 첫 번째 준비-교체에 의해 처리 될 수있을 것입니다.

업데이트 : 방금 깨달았습니다.이 접근 방식은 즉시 임의의 증분으로 확장됩니다 +1. 첫 번째 교체를 변경하기 만하면됩니다. ~추가 하는 숫자는 모든 숫자에 적용하는 증분과 같습니다. 그래서

$str = preg_replace("/\d+/", "$0~~~", $str);

문자열의 모든 정수를 3.


나는 그것을 3 번의 대체 (루프 없음)에서 작동하도록 관리했습니다.

tl; dr

s/$/ ~0123456789/

s/(?=\d)(?:([0-8])(?=.*\1(\d)\d*$)|(?=.*(1)))(?:(9+)(?=.*(~))|)(?!\d)/$2$3$4$5/g

s/9(?=9*~)(?=.*(0))|~| ~0123456789$/$1/g

설명

텍스트의 어느 곳에도 나타나지 않을 것으로 예상 ~되는 특수 문자로 합시다 .

  1. 텍스트에서 문자를 찾을 수없는 경우 마술처럼 보이게 할 방법이 없습니다. 그래서 먼저 우리가 신경 쓰는 문자를 맨 끝에 삽입합니다.

    s/$/ ~0123456789/
    

    예를 들면

    0 1 2 3 7 8 9 10 19 99 109 199 909 999 1099 1909
    

    된다 :

    0 1 2 3 7 8 9 10 19 99 109 199 909 999 1099 1909 ~0123456789
    
  2. 다음으로 각 숫자에 대해 (1) 마지막 non- 9(또는 모두s 인 1경우 앞에 추가 )를 증가 시키고 (2) s의 각 후행 그룹을 "표시"합니다 .99

    s/(?=\d)(?:([0-8])(?=.*\1(\d)\d*$)|(?=.*(1)))(?:(9+)(?=.*(~))|)(?!\d)/$2$3$4$5/g
    

    예를 들어, 우리의 예는 다음과 같습니다.

    1 2 3 4 8 9 19~ 11 29~ 199~ 119~ 299~ 919~ 1999~ 1199~ 1919~ ~0123456789
    
  3. 마지막으로, (1) 각 "표시된" 9s 그룹을 s 로 교체하고 0, (2) ~s를 제거하고 , (3) 끝에있는 문자 집합을 제거합니다.

    s/9(?=9*~)(?=.*(0))|~| ~0123456789$/$1/g
    

    예를 들어, 우리의 예는 다음과 같습니다.

    1 2 3 4 8 9 10 11 20 100 110 200 910 1000 1100 1910
    

PHP 예

$str = '0 1 2 3 7 8 9 10 19 99 109 199 909 999 1099 1909';
echo $str . '<br/>';
$str = preg_replace('/$/', ' ~0123456789', $str);
echo $str . '<br/>';
$str = preg_replace('/(?=\d)(?:([0-8])(?=.*\1(\d)\d*$)|(?=.*(1)))(?:(9+)(?=.*(~))|)(?!\d)/', '$2$3$4$5', $str);
echo $str . '<br/>';
$str = preg_replace('/9(?=9*~)(?=.*(0))|~| ~0123456789$/', '$1', $str);
echo $str . '<br/>';

산출:

0 1 2 3 7 8 9 10 19 99 109 199 909 999 1099 1909
0 1 2 3 7 8 9 10 19 99 109 199 909 999 1099 1909 ~0123456789
1 2 3 4 8 9 19~ 11 29~ 199~ 119~ 299~ 919~ 1999~ 1199~ 1919~ ~0123456789
1 2 3 4 8 9 10 11 20 100 110 200 910 1000 1100 1910

단일 대체로 가능합니까?

아니.

If not, is it at least possible in a single substitution given an upper bound, e.g. numbers up to 9999?

No.

You can't even replace the numbers between 0 and 8 with their respective successor. Once you have matched, and grouped this number:

/([0-8])/

you need to replace it. However, regex doesn't operate on numbers, but on strings. So you can replace the "number" (or better: digit) with twice this digit, but the regex engine does not know it is duplicating a string that holds a numerical value.

Even if you'd do something (silly) as this:

/(0)|(1)|(2)|(3)|(4)|(5)|(6)|(7)|(8)/

so that the regex engine "knows" that if group 1 is matched, the digit '0' is matched, it still cannot do a replacement. You can't instruct the regex engine to replace group 1 with the digit '1', group '2' with the digit '2', etc. Sure, some tools like PHP will let you define a couple of different patterns with corresponding replacement strings, but I get the impression that is not what you were thinking about.


It is not possible by regular expression search and substitution alone.

You have to use use something else to help achieve that. You have to use the programming language at hand to increment the number.

Edit:

The regular expressions definition, as part of Single Unix Specification doesn't mention regular expressions supporting evaluation of aritmethic expressions or capabilities for performing aritmethic operations.


Nonetheless, I know some flavors ( TextPad, editor for Windows) allows you to use \i as a substitution term which is an incremental counter of how many times has the search string been found, but it doesn't evaluate or parse found strings into a number nor does it allow to add a number to it.


I needed to increment indices of output files by one from a pipeline I can't modify. After some searches I got a hit on this page. While the readings are meaningful, they really don't give a readable solution to the problem. Yes it is possible to do it with only regex; no it is not as comprehensible.

Here I would like to give a readable solution using Python, so that others don't need to reinvent the wheels. I can imagine many of you may have ended up with a similar solution.

The idea is to partition file name into three groups, and format your match string so that the incremented index is the middle group. Then it is possible to only increment the middle group, after which we piece the three groups together again.

import re
import sys
import argparse
from os import listdir
from os.path import isfile, join



def main():
    parser = argparse.ArgumentParser(description='index shift of input')
    parser.add_argument('-r', '--regex', type=str,
            help='regex match string for the index to be shift')
    parser.add_argument('-i', '--indir', type=str,
            help='input directory')
    parser.add_argument('-o', '--outdir', type=str,
            help='output directory')

    args = parser.parse_args()
    # parse input regex string
    regex_str = args.regex
    regex = re.compile(regex_str)
    # target directories
    indir = args.indir
    outdir = args.outdir

    try:
        for input_fname in listdir(indir):
            input_fpath = join(indir, input_fname)
            if not isfile(input_fpath): # not a file
                continue

            matched = regex.match(input_fname)
            if matched is None: # not our target file
                continue
            # middle group is the index and we increment it
            index = int(matched.group(2)) + 1
            # reconstruct output
            output_fname = '{prev}{index}{after}'.format(**{
                'prev'  : matched.group(1),
                'index' : str(index),
                'after' : matched.group(3)
            })
            output_fpath = join(outdir, output_fname)

            # write the command required to stdout
            print('mv {i} {o}'.format(i=input_fpath, o=output_fpath))
    except BrokenPipeError:
        pass



if __name__ == '__main__': main()

I have this script named index_shift.py. To give an example of the usage, my files are named k0_run0.csv, for bootstrap runs of machine learning models using parameter k. The parameter k starts from zero, and the desired index map starts at one. First we prepare input and output directories to avoid overriding files

$ ls -1 test_in/ | head -n 5
k0_run0.csv
k0_run10.csv
k0_run11.csv
k0_run12.csv
k0_run13.csv
$ ls -1 test_out/

To see how the script works, just print its output:

$ python3 -u index_shift.py -r '(^k)(\d+?)(_run.+)' -i test_in -o test_out | head -n5
mv test_in/k6_run26.csv test_out/k7_run26.csv
mv test_in/k25_run11.csv test_out/k26_run11.csv
mv test_in/k7_run14.csv test_out/k8_run14.csv
mv test_in/k4_run25.csv test_out/k5_run25.csv
mv test_in/k1_run28.csv test_out/k2_run28.csv

It generates bash mv command to rename the files. Now we pipe the lines directly into bash.

$ python3 -u index_shift.py -r '(^k)(\d+?)(_run.+)' -i test_in -o test_out | bash

Checking the output, we have successfully shifted the index by one.

$ ls test_out/k0_run0.csv
ls: cannot access 'test_out/k0_run0.csv': No such file or directory
$ ls test_out/k1_run0.csv
test_out/k1_run0.csv

You can also use cp instead of mv. My files are kinda big, so I wanted to avoid duplicating them. You can also refactor how many you shift as input argument. I didn't bother, cause shift by one is most of my use cases.

참고URL : https://stackoverflow.com/questions/12941362/is-it-possible-to-increment-numbers-using-regex-substitution

반응형