Zamień wiele ciągów w jednym przebiegu


11

Szukam sposobu na zastąpienie ciągów znaków zastępczych w pliku szablonu konkretnymi wartościami za pomocą popularnych narzędzi uniksowych (bash, sed, awk, może perl). Ważne jest, aby zastąpienie odbywało się w jednym przejściu, co oznacza, że ​​to, co już zostało zeskanowane / wymienione, nie może być brane pod uwagę przy kolejnej wymianie. Na przykład te dwie próby kończą się niepowodzeniem:

echo "AB" | awk '{gsub("A","B");gsub("B","A");print}'
>> AA

echo "AB" | sed 's/A/B/g;s/B/A/g'
>> AA

Poprawnym wynikiem w tym przypadku jest oczywiście BA.

Ogólnie rzecz biorąc, rozwiązanie powinno być równoważne skanowaniu danych wejściowych od lewej do prawej w celu uzyskania najdłuższego dopasowania do jednego z podanych ciągów zastępczych oraz dla każdego dopasowania, wykonania zamiany i kontynuowania od tego momentu na wejściu (żaden z już odczytane dane wejściowe ani wykonane zamiany nie powinny być brane pod uwagę w przypadku dopasowań). W rzeczywistości szczegóły nie mają znaczenia, tylko to, że wyniki zamiany nigdy nie są brane pod uwagę przy kolejnej wymianie, w całości lub w części.

UWAGA Szukam tylko poprawnych ogólnych rozwiązań. Proszę nie proponować rozwiązań, które zawodzą w przypadku niektórych danych wejściowych (pliki wejściowe, wyszukiwanie i zamiana par), choć mogą się wydawać mało prawdopodobne.


Zakładam, że są dłuższe niż jedna postać? Do tego możesz użyć tr AB BA.
Kevin

3
I szczerze mówiąc, nie byłbym zaskoczony, gdyby ktoś uznał twoją notatkę za niegrzeczną.
peterph

1
Jak oczekujesz, że „dostaniesz tylko prawidłowe rozwiązania”, jeśli nie podałeś przykładowego wejścia lub wyjścia?
jasonwryan

1
Obawiam się, że musisz to zrobić dokładnie tak, jak to opisujesz - parsuj od początku i zamieniaj w miarę upływu czasu - tzn. Nie używając wyrażeń regularnych.
peterph

2
To jest uczciwe pytanie, ale odpowiedź brzmi: potrzebujesz parsera maszyny stanów , który zapewnia odpowiedź rici (chyba w prawdziwym stylu hakera). Innymi słowy, nie doceniasz złożoności zadania, ala „Chcę ogólnie parsować (HT | X) ML za pomocą wyrażeń regularnych” -> Odpowiedź brzmi NIE. Nie możesz (po prostu) użyć sed. Nie możesz (po prostu) użyć awk. AFAIK nie ma istniejącego narzędzia, które zrobiłoby to po wyjęciu z pudełka. Wykorzystując Sans rici, musisz napisać kod.
goldilocks,

Odpowiedzi:


10

OK, ogólne rozwiązanie. Następująca funkcja bash wymaga 2kargumentów; każda para składa się z elementu zastępczego i zamiennika. Od Ciebie zależy, czy odpowiednio podasz ciągi znaków, aby przekazać je do funkcji. Jeśli liczba argumentów jest nieparzysta, zostanie dodany domyślny pusty argument, który skutecznie usunie wystąpienia ostatniego symbolu zastępczego.

Ani symbole zastępcze, ani zamienniki nie mogą zawierać znaków NUL, ale możesz użyć standardowych \krajobrazów C, na przykład, \0jeśli potrzebujesz NULs (a zatem musisz pisać, \\jeśli chcesz \).

Wymaga standardowych narzędzi do budowania, które powinny być obecne w systemie podobnym do POSIX-a (lex i cc).

replaceholder() {
  local dir=$(mktemp -d)
  ( cd "$dir"
    { printf %s\\n "%option 8bit noyywrap nounput" "%%"
      printf '"%s" {fputs("%s", yyout);}\n' "${@//\"/\\\"}"
      printf %s\\n "%%" "int main(int argc, char** argv) { return yylex(); }"
    } | lex && cc lex.yy.c
  ) && "$dir"/a.out
  rm -fR "$dir"
}

Zakładamy, że \jeśli jest to konieczne w argumentach , jest już poprzedzane znakiem ucieczki, ale musimy unikać podwójnych cudzysłowów, jeśli są obecne. Tak właśnie działa drugi argument do drugiego printf. Ponieważ lexdomyślną akcją jest ECHO, nie musimy się tym martwić.

Przykładowy przebieg (z czasami sceptycznymi; to tylko tani laptop na towary):

$ time echo AB | replaceholder A B B A
BA

real    0m0.128s
user    0m0.106s
sys     0m0.042s
$ time printf %s\\n AB{0000..9999} | replaceholder A B B A > /dev/null

real    0m0.118s
user    0m0.117s
sys     0m0.043s

W przypadku większych danych wejściowych przydatne może być dostarczenie flagi optymalizacji cc, a dla obecnej zgodności Posix lepiej byłoby użyć c99. Jeszcze bardziej ambitna implementacja może próbować buforować wygenerowane pliki wykonywalne zamiast generować je za każdym razem, ale generowanie ich nie jest drogie.

Edytować

Jeśli masz tcc , możesz uniknąć kłopotów z utworzeniem katalogu tymczasowego i cieszyć się szybszym czasem kompilacji, który pomoże na wejściach normalnej wielkości:

treplaceholder () { 
  tcc -run <(
  {
    printf %s\\n "%option 8bit noyywrap nounput" "%%"
    printf '"%s" {fputs("%s", yyout);}\n' "${@//\"/\\\"}"
    printf %s\\n "%%" "int main(int argc, char** argv) { return yylex(); }"
  } | lex -t)
}

$ time printf %s\\n AB{0000..9999} | treplaceholder A B B A > /dev/null

real    0m0.039s
user    0m0.041s
sys     0m0.031s

Nie jestem pewien, czy to żart, czy nie;)
Ambroz Bizjak,

3
@ambrozbizjak: Działa, jest szybki dla dużych nakładów i akceptowalnie szybki dla małych nakładów. Może nie używać narzędzi, o których myślałeś, ale są to standardowe narzędzia. Dlaczego miałby to być żart?
rici

4
+1 Za to, że nie jesteś żartem! : D
goldilocks

To byłoby przenośne jak POSIX fn() { tcc ; } <<CODE\n$(gen code)\nCODE\n. Czy mogę jednak zapytać - to niesamowita odpowiedź i przegłosowałem ją, gdy tylko ją przeczytałem - ale nie rozumiem, co się dzieje z tablicą powłokową? Co "${@//\"/\\\"}"to robi?
mikeserv

@mikeserv: «Dla każdego argumentu jako wartości cytowanej („ $ @ ”) zamień wszystkie wystąpienia (//) cytatu (\”) na (/) ukośnik odwrotny (\\), a następnie cytat (\ ”) ». Zobacz Rozszerzanie parametrów w podręczniku bash.
rici

1
printf 'STRING1STRING1\n\nSTRING2STRING1\nSTRING2\n' |
od -A n -t c -v -w1 |
sed 's/ \{1,3\}//;s/\\$/&&/;H;s/.*//;x
     /\nS\nT\nR\nI\nN\nG\n1/s//STRING2/
     /\nS\nT\nR\nI\nN\nG\n2/s//STRING1/
     /\\n/!{x;d};s/\n//g;s/./\\&/g' |
     xargs printf %b

###OUTPUT###

STRING2STRING2

STRING1STRING2
STRING1

Coś takiego zawsze zastępuje każde wystąpienie docelowych ciągów tylko raz, ponieważ występują one sedw strumieniu z jednym bitem na linię. To najszybszy sposób, w jaki mogę sobie wyobrazić, że to zrobisz. Z drugiej strony, nie piszę C. Ale to niezawodnie radzi sobie z ogranicznikami zerowymi, jeśli chcesz. Zobacz tę odpowiedź za, jak to działa. Nie ma problemów z żadnymi zawartymi specjalnymi znakami powłoki lub podobnymi - ale jest to specyficzne dla ustawień regionalnych ASCII, lub innymi słowy, odnie będzie wypisywać znaków wielobajtowych w tym samym wierszu i wykona tylko jeden na. Jeśli jest to problem, chcesz go dodać iconv.


+1 Dlaczego mówisz, że to zastępuje „najwcześniejsze wystąpienie ciągów docelowych”? Na wyjściu wygląda na to, że zastępuje je wszystkie. Nie pytam o to, ale czy można to zrobić w ten sposób, nie wpisując wartości na stałe?
goldilocks

@goldilocks - Tak - ale tylko jak tylko się pojawią. Może powinienem to przeredagować. I tak - możesz po prostu dodać środek sedi zapisać do zera lub coś takiego, a następnie sednapisać ten skrypt; lub umieść go w funkcji powłoki i nadaj jej wartości po jednym kęsie w wierszu, na przykład "/$1/"... "/$2/"- może też napiszę te funkcje ...
mikeserv

To nie wydaje się działać w przypadku, gdy są to symbole zastępcze PLACE1, PLACE2i PLA. PLAzawsze zwycięża. OP mówi: „odpowiednik skanowania wejścia od lewej do prawej w celu uzyskania najdłuższego dopasowania do jednego z podanych ciągów zastępczych” (podkreślenie dodane)
rici,

@rici - dzięki. Potem będę musiał wykonać ograniczniki zerowe. Z powrotem w mgnieniu oka.
mikeserv

@rici - Właśnie miałem opublikować inną wersję, która poradzi sobie z tym, co opisujesz, ale patrząc na to jeszcze raz i nie sądzę, że powinienem. Mówi najdłużej dla jednego z podanych ciągów zastępczych. To robi to. Nic nie wskazuje na to, że jeden ciąg znaków jest podzbiorem drugiego, tyle że może być zastąpiona wartością. Nie sądzę też, aby iteracja po liście była dobrym sposobem na rozwiązanie problemu. Biorąc pod uwagę problem, jaki rozumiem, jest to skuteczne rozwiązanie.
mikeserv

1

perlRozwiązaniem. Nawet jeśli niektórzy stwierdzili, że nie jest to możliwe, znalazłem jeden, ale generalnie proste dopasowanie i zamiana nie jest możliwe, a nawet pogarsza się z powodu wycofania NFA, wynik może być nieoczekiwany.

Ogólnie rzecz biorąc, i trzeba to powiedzieć, problem wywołuje różne wyniki, które zależą od kolejności i długości zastępczych krotek. to znaczy:

A B
AA CC

a dane wejściowe AAAskutkują w BBBlub CCB.

Oto kod:

#!/usr/bin/perl

$v='if (0) {} ';
while (($a,$b)=split /\s+/, <DATA>) {
  $k.=$a.'|';
  $v.='elsif ($& eq \''.$a.'\') {print \''.$b.'\'} ';
}
$k.='.';
$v.='else {print $&;}';

eval "
while (<>) {
  \$_ =~ s/($k)/{$v}/geco;
}";  
print "\n";


__DATA__
A    B
B    A
abba baab
baab abbc
abbc aaba

Checkerbunny:

$ echo 'ABBabbaBBbaabAAabbc'|perl script
$ BAAbaabAAabbcBBaaba
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.