Jak najít čáry?

Příkaz FINDSTR se používá k hledání textového řetězce v jednom nebo více souborech pomocí regulárních výrazů. Ve srovnání s příkazem FIND vám tento příkaz umožňuje provádět mnohem flexibilnější vyhledávání v souladu s pravidly zadanými jako parametry příkazového řádku. Regulární výrazy jsou druhem jazyka používajícího regulární a speciální znaky, které definují vzor a vyhledávací algoritmus. Obyčejné znaky (literály) jsou běžné textové znaky – písmena, čísla, interpunkční znaménka atd. Speciální znaky (metasymboly) jsou prvky pravidel záznamu a parametrů zpracování pro běžné znaky. Tedy například symbol tečky. znamená „libovolný znak“, hranaté závorky představují sadu znaků v nich uzavřených, sekvence d je libovolný číselný znak, D je jakýkoli nečíselný znak.

Když je nutné zacházet s metaznaky jako s běžnými textovými prvky, používají regulární výrazy znak escape – zpětné lomítko. Zápis [ znamená běžný znak v hranaté závorce, nikoli metaznak pro začátek psaní. Chcete-li uniknout více metaznakům, použijte sekvenci:

Q. . . sada metaznaků. . .E

Zpětné lomítko před běžným znakem znamená, že je interpretováno jako speciální znak:

s – odpovídá znaku mezery.

Při použití v regulárních výrazech se rozlišují malá a velká písmena.

S — libovolný znak, nikoli mezera.

Formát příkazového řádku FINDSTR:

FINDSTR [/B] [/E] [/L] [/R] [/S] [/I] [/X] [/V] [/N] [/M] [/O] [/P] [ /F:soubor] [/C:řádek] [/G:soubor] [/D:seznam_složek] [/A:barvy] [/OFF[LINE]] řádky [[jednotka:][cesta]název souboru[ . ]]

/B – Hledat vzor pouze na začátku řádků.
/E – Vzor hledat pouze na konci řádků.
/L – Hledat řetězce doslovně.
/R – Hledání řetězců jako regulárních výrazů.
/S – Vyhledá soubory v aktuální složce a všech jejích podsložkách.
/I – Určuje, že vyhledávání nebude rozlišovat malá a velká písmena.
/X – Vytiskne řádky, které se přesně shodují.
/V – Vytiskne řádky, které neobsahují shody s hledanými.
/N – Vytiskne číslo řádku, kde byla nalezena shoda, a její obsah.
/M – Vytiskne pouze název souboru, ve kterém byla nalezena shoda.
/O – Vytiskne nalezený řádek oddělený prázdným řádkem.
/P – Přeskočí řádky obsahující netisknutelné znaky.
/OFF[LINE] – Nepřeskakuje soubory s nastaveným atributem „Offline“.
/A:colors – Dvě hexadecimální číslice – atributy barev. Viz “BARVA /?”
/F:soubor – Načte seznam souborů z daného souboru (/ pro konzolu).
/C:string – používá zadaný řetězec jako vyhledávací frázi.
/G:file – Získá řádky z daného souboru (/ pro konzolu).
/D:seznam_složek – Hledání v seznamu složek (oddělených středníkem).
řetězec – Vyhledat text.
[jednotka:][cesta]název souboru – Určuje název souboru nebo souborů.

Mezera se používá k oddělení více vyhledávacích řetězců, pokud argument nemá předponu /C. Například,

FINDSTR “Ahoj světe” file.txt vyhledejte “Hello” nebo “world” v souboru.txt

FINDSTR /C: “Hello world” file.txt hledá řetězec “Hello world” v souboru file.txt.

Pro rychlou nápovědu k použití příkazu FINDSTR použijte /? :

Kromě možností příkazového řádku je nápověda doplněna stručným shrnutím syntaxe regulárních výrazů:

. – Jakýkoli symbol.
* – Opakovat: žádný nebo více výskytů předchozího znaku nebo třídy
^ — Pozice v řádku: začátek řádku
$ — Pozice v řádku: konec řádku
[class] – Třída znaků: libovolný jednotlivý znak ze sady
[^class] – Reverzní třída znaků: libovolný jednotlivý znak z doplňku
[xy] — Rozsah: libovolné znaky ze zadaného rozsahu
x — Symbol služby: symbolické označení symbolu služby x
— Pozice ve slově: na začátku slova
xyz > – Pozice ve wordu: na konci slova

Úplné informace o regulárních výrazech FINDSTR naleznete v dostupné online dokumentaci.

Příklady použití FINDSTR:

findstr /M [0-9] %temp%*.* – zobrazí seznam souborů (klávesa /M) obsahující čísla (nastavena 0-9) z adresáře dočasných souborů (definovaného pomocí %TEMP%)

findstr /P /I “Chyba” %temp%*.* – Zobrazí řádky obsahující slovo Error . Vyhledávání řetězců bez zohlednění velikosti písmen (klávesa /I), řádky obsahující netisknutelné znaky se nezobrazují (klávesa /P).

findstr /M /I /C:”chyba sítě” %windir%system32*.exe – zobrazí seznam spustitelných souborů ze systémového adresáře Windowssystem32, které obsahují řetězec “chyba sítě”

findstr /s /I /A:f4 /O /C:»failed» C:*.log – zobrazení řádků souborů s příponou log obsahující slovo selhalo . Název souboru a odsazení řádku vzhledem k jeho počátku jsou zobrazeny červenými znaky na bílém pozadí (klávesa /A:F4). Vyhledávání se provádí ve všech souborech .log kořenového adresáře disku C: a všech jeho podadresářích (přepínač /S)

findstr /A:FC /N /s /i” – zobrazí řádky obsahující slovo začínající na “comput” (počítač, počítač, počítače atd.), stejně jako názvy souborů a čísla řádků (klíč /N).

findstr /A:FC /N /s /i ” – jako v předchozím případě, ale hledá se řetězec obsahující slovo začínající podřetězcem. Při používání znaků ruského jazyka je třeba vzít v úvahu jejich kódování, protože kódy znaků v kódování DOS a Windows se liší. V dávkových souborech, když je nutné hledat řetězce obsahující znaky národní abecedy, musí být vyhledávací vzor reprezentován ve stejném kódování jako obsah souboru. Před vyhledáváním můžete použít přepínání kódové stránky:

REM přepnout na kódování Windows
chcp 1251
REM Provádění vyhledávání
findstr /A:FC /N /s /i “

Zvažte problém, který nastane pokaždé, když stisknete ctrl+f:

Je tam velký text (t). Musíme v něm najít všechny výskyty řetězců (řetězců).

Naivní řešení s porovnáváním všech podřetězců (t) délky (|s|) s řetězci (s) běží v (O(|t| cdot |s|)) . Pokud je text velký, hledání dlouhých slov v něm zabere hodně času.

Existuje však mnoho způsobů, jak tento problém vyřešit v (O(|s| + |t|)) , dva z nejběžnějších a nejjednodušších jsou: funkce předpony a skrz z-funkce (poznámka: ne „zi“, ale „zet“).

Funkce předpony

Definice. Předponová funkce z řetězce (s) je pole (p), kde (p_i) se rovná délce největší předpony řetězce (s_0 s_1 s_2 ldots s_i), což je také přípona (i) předpona (nepočítá se celá (i) -tá předpona).

Například největší předpona, která se rovná příponě pro řetězec “aataataa” je “aataa”; funkce předpony pro tento řetězec je ([0, 1, 0, 1, 2, 3, 4, 5]) .

 vectorint> slow_prefix_function(string s) int n = (int) s.size(); vectorint> p(n, 0); for (int i = 1; i n; i++) for (int len = 1; len i; len++) // если префикс длины len равен суффиксу длины len if (s.substr(0, len) == s.substr(i - len + 1, len)) p[i] = len; return p; >

Tento algoritmus aktuálně běží v (O(n^3)), ale později jej urychlíme.

Jak to pomůže vyřešit původní problém?

Věřme zatím, že funkci předpony můžeme považovat za lineární ve velikosti řetězce a naučíme se ji používat k hledání podřetězce v řetězci.

Spojme podřetězce (s) a (t) nějakým symbolem, který se nevyskytuje ani tam, ani tam – označme tento symbol #. Podívejme se na funkci předpony výsledného řetězce (s#t) .

 string s = "choose"; string t = "choose life. choose a job. choose a career. choose a family. choose a fu. "; cout s + "#" + t endl; cout slow_prefix_function(s + "#" + t) endl;
 choose#choose life. choose a job. choose a career. choose a family. choose a fu. 0000000123456000000012345600000000123456000100000001234560000000000012345600000000

Můžete vidět, že všechna místa, kde se hodnoty rovnají 6 (délka (s)), jsou konce výskytů (s) v textu (t).

Takový algoritmus (vypočítejte prefixovou funkci z (s#t) a podívejte se, na jakých pozicích se rovná (|s|)) se nazývá Knuth-Morris-Prattův algoritmus.

Jak to rychle spočítat

Podívejme se na několik dalších příkladů funkcí prefixů a pokusíme se najít vzory:

 aaaaa 01234 abcdef 000000 abacabadava 00101230101

Můžete si všimnout následující vlastnosti: (p_) je nejvýše o jednu větší než (p_i) .

Důkaz. Pokud existuje předpona rovna příponě řetězce (s_) , length (p_) , pak zahozením posledního znaku můžete získat správnou příponu pro řetězec (s_), jehož délka bude přesně o jednu kratší. .

Pokusme se problém vyřešit pomocí dynamiky: najděte vzorec pro (p_i) přes předchozí hodnoty.

Všimněte si, že (p_ = p_i + 1) právě tehdy, když (s_ =s_) . V tomto případě můžeme jednoduše aktualizovat (p_) a jít dál.

Například v řádku (underbracetoverbrace) je zvýrazněna maximální předpona, která se rovná příponě: (p_ = 5) . Pokud je další znak roven (t), pak (p_ = p_ + 1 = 6) .

Ale co se stane, když (s_neq s_) ? Nechť další znak ve stejném příkladu není roven (t), ale (b).

  • (implicitně) Délka předpony rovnající se příponě nového řádku bude přesně menší než 5.
  • (implikuje) Kromě toho, že novou předponu, kterou hledáme, je přípona „aabaab“, je to také předpona podřetězce „aabaa“.
  • (implicitně) Dalším kandidátem na ověření je tedy hodnota prefixové funkce z „aabaa“, tedy (p_4 = 2), kterou jsme již vypočítali.
  • (implicitně) Jestliže (s_2 = s_) (tj. nový znak odpovídá znaku, který následuje za kandidátskou předponou), pak (p_ = p_2 + 1 = 2 + 1 = 3) .

V tomto případě tomu tak skutečně je (požadovaná předpona je „aab“). Ale co když v obecném případě (p_ neq p_) ? Pak provedeme stejnou úvahu a získáme nového kandidáta kratší délky – (p_>) . Pokud tento nefunguje, kontrolujeme stejným způsobem menší, dokud tento index nebude nulový.

 vectorint> prefix_function(string s) int n = (int) s.size(); vectorint> p(n, 0); for (int i = 1; i n; i++) // префикс функция точно не больше этого значения + 1 int cur = p[i - 1]; // уменьшаем cur значение, пока новый символ не сматчится while (s[i] != s[cur] && cur > 0) cur = p[cur - 1]; // здесь либо s[i] == s[cur], либо cur == 0 if (s[i] == s[cur]) p[i] = cur + 1; > return p; >

Asymptotika. V nejhorším případě může tento while běžet (O(n)) krát za iteraci, ale v průměru každý while běží v (O(1)) .

Funkce předpony se v každém kroku zvýší maximálně o jednu a po každé iteraci while se sníží alespoň o jednu. To znamená, že celkový počet operací nebude větší než (O(n)).

Z-funkce

Poněkud srozumitelnější alternativou k funkci předpony je funkce z.

Z-funkce řetězce (s) je definována jako pole (z) takové, že (z_i) se rovná délce maximálního podřetězce, spouštění z (i)té pozice, která se rovná předponě (s).

[underbracecoverbracedaba hspace (z_4 = 3)]

 vectorint> slow_z_function (string s) int n = (int) s.size(); vectorint> z(n, 0); // z[0] считается не определенным for (int i = 1; i n; i++) // если мы не вышли за границу и следующие символы совпадают while (i + z[i] n && s[z[i]] == s[i + z[i]]) z[i]++; return z; >
 aaaaa 04321 abcdef 000000 abacabadava 00103010101

Z-funkci lze použít místo prefixové funkce v algoritmu Knuth-Morris-Pratt – pouze nyní budou požadované pozice začínat (|s|) spíše než končit. Zbývá se jen naučit, jak to hledat v (O(n)) .

Jak to rychle spočítat

Půjdeme zleva doprava a uložíme z-blok — pravý podřetězec rovný prefixu, který se nám podařilo objevit. Jeho hranice budeme označovat jako (l) a (r) včetně.

Pojďme nyní chtít najít (z_i) , a už jsme našli všechny předchozí. Nový (i)tý znak může ležet buď napravo od z-bloku nebo uvnitř něj:

  • Pokud vpravo, pak jednoduše naivně vyhledáme (z_i) (maximální segment začínající (s_i) a rovný prefixu) a prohlásíme jej za nový z-blok.
  • Pokud (i)-tý prvek leží uvnitř z-boxu, pak se můžeme podívat na hodnotu (z_) a použít ji k inicializaci (z_i) na něco, možná nenulového. Pokud je (z_) vlevo od pravého okraje (z)-bloku, pak (z_i = z_) – nemůže jich být více (z_i). Pokud narazí na hranici, pak ji k ní „přiřízneme“ a zvýšíme o jednu.
 vectorint> z_function (string s) int n = (int) s.size(); vectorint> z(n, 0); int l = 0, r = 0; for (int i = 1; i n; i++) // если мы уже видели этот символ if (i r) // то мы можем попробовать его инициализировать z[i - l], // но не дальше правой границы: там мы уже ничего не знаем z[i] = min(r - i + 1, z[i - l]); // дальше каждое успешное увеличение z[i] сдвинет z-блок на единицу while (i + z[i] n && s[z[i]] == s[i + z[i]]) z[i]++; // проверим, правее ли мы текущего z-блока if (i + z[i] - 1 > r) r = i + z[i] - 1; l = i; > > return z; >

Asymptotika. V algoritmu provedeme stejný počet akcí, jako se posune pravá hranice z-bloku – a to je (O(n)).

Porovnání

Obecně jsou funkce z a předpony velmi podobné, ale algoritmus Knuth-Morris-Pratt je ve všech klasických učebnicích programování a z nějakého důvodu o funkci z kromě programátorů olympiád ví jen málo lidí.

O funkci prefixu je také důležité vědět, že je online – stačí přečíst další znak a hned zjistíte význam.

Cvičení 1. Je dána řada prefixových funkcí. Původní řetězec není uveden. Vypočtěte v (O(n)) čase z-funkci tohoto řetězce.

Cvičení 2. Je dáno pole z-funkcí. Původní řetězec není uveden. Vypočtěte v čase (O(n)) funkci předpony tohoto řetězce.

Napsat komentář