Frage Vim - Regex verwenden, um Zeichenfolgen lexikographisch zu vergleichen (um frühere / spätere Daten zu finden)


Ich möchte eine einfache Regex in Vim schreiben, die alle Strings findet lexikografisch kleiner als eine andere Zeichenfolge.

Insbesondere möchte ich dies verwenden, um Daten zu vergleichen, die als 2014-02-17 formatiert sind. Diese Daten sind lexikographisch sortierbar, weshalb ich sie verwende.

Mein spezifischer Anwendungsfall: Ich versuche, ein Skript durchzugehen und alle Daten zu finden, die früher als heute sind.

Ich bin auch OK, wenn ich diese als Zahlen oder irgendeine andere Lösung vergleiche.


5
2018-02-17 19:14


Ursprung


Antworten:


Ich denke nicht, dass es sowieso leicht ist, dies in Regex zu tun. Um ein beliebiges Datum zu finden, das vor dem aktuellen Datum liegt, können Sie die folgende Funktion ausführen (ein Teil des Materials wurde von BenjiFisher gestohlen)

function! Convert_to_char_class(cur) 
    if a:cur =~ '[2-9]'
        return '[0-' . (a:cur-1) . ']'
    endif
    return '0'
endfunction

function! Match_number_before(num)
    let branches = []
    let init = ''
    for i in range(len(a:num))
        if a:num[i] =~ '[1-9]'
            call add(branches, init . Convert_to_char_class(a:num[i]) . repeat('\d', len(a:num) - i - 1))
        endif 
        let init .= a:num[i]
    endfor
    return '\%(' . join(branches, '\|') .'\)'
endfunction

function! Match_date_before(date)
    if a:date !~ '\v\d{4}-\d{2}-\d{2}'
        echo "invalid date"
        return
    endif

    let branches =[]

    let parts = split(a:date, '-')
    call add(branches, Match_number_before(parts[0]) . '-\d\{2}-\d\{2}')
    call add(branches, parts[0] . '-' . Match_number_before(parts[1]) . '-\d\{2}')
    call add(branches, parts[0] . '-' . parts[1] . '-' .Match_number_before(parts[2]))

    return '\%(' . join(branches, '\|') .'\)'
endfunction

Um Sie zu verwenden, um nach allen Übereinstimmungen vorher zu suchen 2014-02-24.

/<C-r>=Match_date_before('2014-02-24')

Sie können es möglicherweise in eine Funktion einfügen, um das Suchregister festzulegen, wenn Sie das möchten.

Die generierte Regex für Daten davor 2014-02-24 ist das Folgende.

\%(\%([0-1]\d\d\d\|200\d\|201[0-3]\)-\d\{2}-\d\{2}\|2014-\%(0[0-1]\)-\d\{2}\|2014-02-\%([0-1]\d\|2[0-3]\)\)

Es macht keine Validierung von Daten. Es geht davon aus, wenn Sie in diesem Format sind, dass Sie ein Datum sind.


Äquivalenter Satz von Funktionen zum Abgleich nach dem übergebenen Datum.

function! Convert_to_char_class_after(cur) 
    if a:cur =~ '[0-7]'
        return '[' . (a:cur+1) . '-9]'
    endif
    return '9'
endfunction

function! Match_number_after(num)
    let branches = []
    let init = ''
    for i in range(len(a:num))
        if a:num[i] =~ '[0-8]'
            call add(branches, init . Convert_to_char_class_after(a:num[i]) . repeat('\d', len(a:num) - i - 1))
        endif 
        let init .= a:num[i]
    endfor
    return '\%(' . join(branches, '\|') .'\)'
endfunction

function! Match_date_after(date)
    if a:date !~ '\v\d{4}-\d{2}-\d{2}'
        echo "invalid date"
        return
    endif

    let branches =[]

    let parts = split(a:date, '-')
    call add(branches, Match_number_after(parts[0]) . '-\d\{2}-\d\{2}')
    call add(branches, parts[0] . '-' . Match_number_after(parts[1]) . '-\d\{2}')
    call add(branches, parts[0] . '-' . parts[1] . '-' .Match_number_after(parts[2]))

    return '\%(' . join(branches, '\|') .'\)'
endfunction

Die erzeugte Regex war

\%(\%([3-9]\d\d\d\|2[1-9]\d\d\|20[2-9]\d\|201[5-9]\)-\d\{2}-\d\{2}\|2014-\%([1-9]\d\|0[3-9]\)-\d\{2}\|2014-02-\%([3-9]\d\|2[5-9]\)\)

3
2018-02-24 20:08



Sie sagen nicht, wie Sie das verwenden möchten; Bist du sicher, dass du wirklich einen regulären Ausdruck willst? Vielleicht könntest du damit durchkommen

if DateCmp(date, '2014-02-24') < 0
  " ...
endif

Versuchen Sie in diesem Fall diese Funktion.

" Compare formatted date strings:
" @param String date1, date2
"   dates in YYYY-MM-DD format, e.g. '2014-02-24'
" @return Integer
"   negative, zero, or positive according to date1 < date2, date1 == date2, or
"   date1 > date2
function! DateCmp(date1, date2)
  let [year1, month1, day1] = split(a:date1, '-')
  let [year2, month2, day2] = split(a:date2, '-')
  if year1 != year2
    return year1 - year2
  elseif month1 != month2
    return month1 - month2
  else
    return day1 - day2
  endif
endfun

Wenn Sie einen regulären Ausdruck wirklich wollen, versuchen Sie Folgendes:

" Construct a pattern that matches a formatted date string if and only if the
" date is less than the input date.  Usage:
" :echo '2014-02-24' =~ DateLessRE('2014-03-12')
function! DateLessRE(date)
  let init = ''
  let branches = []
  for c in split(a:date, '\zs')
    if c =~ '[1-9]'
      call add(branches, init . '[0-' . (c-1) . ']')
    endif
    let init .= c
  endfor
  return '\d\d\d\d-\d\d-\d\d\&\%(' . join(branches, '\|') . '\)'
endfun

Zählt das als "einfache" Regex? Eine Möglichkeit wäre das Tippen :g/ und dann CRTL-R und = und dann DateLessRE('2014-02-24') und Enter, gefolgt von dem Rest Ihres Befehls. Mit anderen Worten,

:g/<C-R>=DateLessRE('2014-02-24')<CR>/s/foo/bar

EDIT: Ich habe eine concat (:help /\&), die einer vollständigen "formatierten Datumszeichenkette" entspricht. Jetzt muss das Muster nicht mehr verankert werden.


3
2018-02-24 18:28



Verwenden Sie verschachtelte Untermuster. Es beginnt einfach, mit dem Jahrhundert:

[01]\d\d\d-\d\d-\d\d|20

Verwenden Sie für jede folgende Ziffer eines der folgenden Muster; Vielleicht möchten Sie ersetzen .* durch eine geeignete Abfolge von \d und -.

for 0:   (0
for 1:   (0.*|1
for 2:   ([01].*|2
for 3:   ([0-2].*|3
for 4:   ([0-3].*|4
for 5:   ([0-4].*|5
for 6:   ([0-5].*|6
for 7:   ([0-6].*|7
for 8:   ([0-7].*|8
for 9:   ([0-8].*|9

Für die letzte Ziffer benötigen Sie nur den Ziffernbereich, z. B .:

[0-6]

Schließlich sollten alle Klammern geschlossen werden:

)))))

Im Beispiel von 2014-02-17, das wird:

[01]\d\d\d-\d\d-\d\d|20
(0\d-\d\d-\d\d|1
([0-3]-\d\d-\d\d|4
-
(0
([01]-\d\d|2
-
(0\d|1
[0-6]
)))))

Jetzt in einer Zeile:

[01]\d\d\d-\d\d-\d\d|20(0\d-\d\d-\d\d|1([0-3]-\d\d-\d\d|4-(0([01]-\d\d|2-(0\d|1[0-6])))))

Vergessen wir für VIM nicht zu entkommen (, ) und |:

[01]\d\d\d-\d\d-\d\d\|20\(0\d-\d\d-\d\d\|1\([0-3]-\d\d-\d\d\|4-\(0\([01]-\d\d\|2-\(0\d\|1[0-6]\)\)\)\)\)

Wäre am besten, dies zu versuchen (ähnlich wie in FDinoffs Antwort), anstatt es selbst zu schreiben ...

Aktualisieren: Hier ist ein Beispiel AWK-Skript, um die richtige Regex für ein beliebiges Datum zu generieren JJJJ-MM-TT.

#!/usr/bin/awk -f

BEGIN {                 # possible overrides for non-VIM users
    switch (digit) {
        case "ascii"     : digit = "[0-9]";     break;
        case "posix"     : digit = "[:digit:]"; break;
        default          : digit = "\\d";
    }
    switch (metachar) {
        case "unescaped" : escape = "";         break;
        default          : escape = "\\";
    }
}

/^[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]$/ {
    print BuildRegex($0);
}

function BuildRegex(s) {
    if (s ~ /^[1-9][^1-9]*$/) {
        regex = LessThanOnFirstDigit(s);
    }
    else {
        regex = substr(s, 1, 1) BuildRegex(substr(s, 2));    # recursive call
        if (s ~ /^[1-9]/) {
            regex = escape "(" LessThanOnFirstDigit(s) escape "|" regex escape ")";
        }
    }
    return regex;
}

function LessThanOnFirstDigit(s) {
    first = substr(s, 1, 1) - 1;
    rest = substr(s, 2);
    gsub(/[0-9]/, digit, rest);
    return (first ? "[0-" first "]" : "0") rest;
}

Nenn es so:

echo 2014-02-17 | awk -f genregex.awk

Natürlich können Sie einen so einfachen Generator in jeder beliebigen Sprache schreiben. Wäre nett, es in Vimscript zu machen, aber ich habe keine Erfahrung damit, also werde ich das als eine Heimaufgabe verlassen.


1
2018-02-25 23:46



Wenn Sie nach allen Daten suchen möchten, die weniger als 2014-11-23 inklusive waren, verwenden Sie die folgende Regex.

2014 - (?: [1-9] | 1 [0-1]) - (?: [1-9] | 1 [0-9] | 2 [0-3])

Für eine bessere Erklärung der Regex besuchen Sie regex101.com und fügen Sie die Regex ein. Sie können es auch testen, indem Sie diese Seite verwenden.

Die Grundlagen der Regex sind alle Daten zu suchen, die:

start with 2014-
either contain a single character from 1 - 9 
    or a 1 and a single character from 0 - 1, i.e. numbers from 1 - 11
finished by - and numbers from 1 - 23 done in the same style as the second term

0
2018-02-17 19:38