Jak sprawdzić, czy ciąg jest w całości wykonany z tego samego podciągu?

128

Muszę utworzyć funkcję, która przyjmuje ciąg znaków i powinna zwracać truelub w falseoparciu o to, czy dane wejściowe składają się z powtarzającej się sekwencji znaków. Długość podanego ciągu jest zawsze większa niż, 1a sekwencja znaków musi mieć co najmniej jedno powtórzenie.

"aa" // true(entirely contains two strings "a")
"aaa" //true(entirely contains three string "a")
"abcabcabc" //true(entirely containas three strings "abc")

"aba" //false(At least there should be two same substrings and nothing more)
"ababa" //false("ab" exists twice but "a" is extra so false)

Stworzyłem poniższą funkcję:

function check(str){
  if(!(str.length && str.length - 1)) return false;
  let temp = '';
  for(let i = 0;i<=str.length/2;i++){
    temp += str[i]
    //console.log(str.replace(new RegExp(temp,"g"),''))
    if(!str.replace(new RegExp(temp,"g"),'')) return true;
  }
  return false;
}

console.log(check('aa')) //true
console.log(check('aaa')) //true
console.log(check('abcabcabc')) //true
console.log(check('aba')) //false
console.log(check('ababa')) //false

Sprawdzenie tego jest częścią prawdziwego problemu. Nie stać mnie na takie nieefektywne rozwiązanie. Po pierwsze, przechodzi przez połowę łańcucha.

Drugi problem polega na tym, że używa replace()w każdej pętli, co powoduje jej powolność. Czy istnieje lepsze rozwiązanie dotyczące wydajności?

Maheer Ali
źródło
19
Ten link może być przydatny. Zawsze uważam geekforgeeks za dobre źródło problemów z algorytmem - geeksforgeeks.org/…
Leron_says_get_back_Monica
9
Czy masz coś przeciwko, jeśli to pożyczę i sprawię, że będzie to wyzwanie dla kodowania w witrynie wymiany Programming Golf?
ouflak
7
@ouflak, możesz to zrobić.
Maheer Ali
12
Na wypadek, gdybyś był
ouflak
24
@Shidersz Używanie do tego sieci neuronowych przypomina trochę użycie armaty do strzelania do komara.
JAD

Odpowiedzi:

187

Istnieje sprytne, małe twierdzenie na temat takich strun.

Łańcuch składa się z tego samego wzorca powtarzanego wielokrotnie wtedy i tylko wtedy, gdy struna jest nietrywialną rotacją samego siebie.

Tutaj obrót oznacza usunięcie pewnej liczby znaków z przodu ciągu i przeniesienie ich do tyłu. Na przykład ciąg hellomożna obrócić, aby utworzyć dowolny z następujących ciągów:

hello (the trivial rotation)
elloh 
llohe 
lohel 
ohell 

Aby zobaczyć, dlaczego to działa, załóżmy najpierw, że ciąg składa się z k powtórzonych kopii łańcucha w. Następnie usunięcie pierwszej kopii powtarzanego wzoru (w) z przodu sznurka i przyklejenie go do tyłu da z powrotem tę samą strunę. Odwrotny kierunek jest nieco trudniejszy do udowodnienia, ale chodzi o to, że jeśli obrócisz strunę i wrócisz do tego, od czego zacząłeś, możesz zastosować ten obrót wielokrotnie, aby podzielić ciąg z wieloma kopiami tego samego wzoru (ten wzór jest ciąg, który musiałeś przejść do końca, aby wykonać obrót).

Teraz pytanie brzmi, jak sprawdzić, czy tak jest. W tym celu możemy użyć innego pięknego twierdzenia:

Jeśli x i y są łańcuchami o tej samej długości, to x jest obrotem y wtedy i tylko wtedy, gdy x jest podłańcuchem yy.

Jako przykład widzimy, że loheljest to rotacja w hellonastępujący sposób:

hellohello
   ^^^^^

W naszym przypadku wiemy, że każdy ciąg x zawsze będzie podłańcuchem xx (pojawi się dwa razy, raz w każdej kopii x). Więc po prostu musimy sprawdzić, czy nasz łańcuch x jest podłańcuchem xx, nie pozwalając mu dopasować się do pierwszego lub połowy znaku. Oto jeden wiersz na to:

function check(str) {
    return (str + str).indexOf(str, 1) !== str.length;
}

Zakładając, że indexOfjest zaimplementowany przy użyciu algorytmu szybkiego dopasowywania ciągów, będzie to działać w czasie O (n), gdzie n jest długością ciągu wejściowego.

Mam nadzieję że to pomoże!

templatetypedef
źródło
13
Bardzo dobrze! Dodałem go do strony testów porównawczych jsPerf .
user42723
10
@ user42723 Super! Wygląda na to, że jest naprawdę, bardzo szybki.
templatetypedef
5
FYI: Trudno mi było uwierzyć w to zdanie, dopóki nie odwróciłem sformułowania: „Ciąg jest nietrywialną rotacją samego siebie wtedy i tylko wtedy, gdy składa się z tego samego wzoru powtarzanego wielokrotnie”. Domyśl.
Axel Podehl
11
Czy masz odniesienia do tych twierdzeń?
HRK44,
4
Myślę, że pierwsze stwierdzenie jest takie samo jak „ Lemat 2.3 : Jeśli x i obrót x są równe, to x jest powtórzeniem” na doi.org/10.1016/j.tcs.2008.04.020 . Zobacz też: stackoverflow.com/a/2553533/1462295
BurnsBA
67

Możesz to zrobić za pomocą grupy przechwytującej i odwołania wstecznego . Po prostu sprawdź, czy jest to powtórzenie pierwszej przechwyconej wartości.

function check(str) {
  return /^(.+)\1+$/.test(str)
}

console.log(check('aa')) //true
console.log(check('aaa')) //true
console.log(check('abcabcabc')) //true
console.log(check('aba')) //false
console.log(check('ababa')) //false

W powyższym RegExp:

  1. ^i $oznacza kotwicę początkową i końcową do przewidywania pozycji.
  2. (.+)przechwytuje dowolny wzorzec i przechwytuje wartość (z wyjątkiem \n).
  3. \1jest odwołaniem wstecznym do pierwszej przechwyconej wartości i \1+sprawdzałby powtórzenie przechwyconej wartości.

Wyjaśnienie Regex tutaj

Do debugowania RegExp użyj: https://regex101.com/r/pqlAuP/1/debugger

Wydajność: https://jsperf.com/reegx-and-loop/13

Pranav C Balan
źródło
2
Czy możesz nam wyjaśnić, co robi ta linia return /^(.+)\1+$/.test(str)
Thanveer Shah
34
Jaka jest też złożoność tego rozwiązania? Nie jestem do końca pewien, ale wydaje się, że nie jest dużo szybszy niż ten, który ma OP.
Leron_says_get_back_Monica
8
@PranavCBalan Nie jestem dobry w algorytmach, dlatego piszę w komentarzach. Muszę jednak wspomnieć o kilku rzeczach - OP ma już działające rozwiązanie, więc prosi o takie, które zapewni mu lepszą wydajność, a ty nie wyjaśniłeś, w jaki sposób Twoje rozwiązanie będzie lepsze od jego. Krótszy nie znaczy szybszy. Również z linku, który podałeś: If you use normal (TCS:no backreference, concatenation,alternation,Kleene star) regexp and regexp is already compiled then it's O(n).ale jak pisałeś używasz odwołania wstecznego, czy nadal jest to O (n)?
Leron_says_get_back_Monica
5
Możesz użyć [\s\S]zamiast, .jeśli chcesz dopasować znaki nowego wiersza w taki sam sposób, jak inne znaki. Znak kropki nie pasuje do nowych linii; alternatywne wyszukuje wszystkie znaki spacji i inne niż białe znaki, co oznacza, że ​​nowe wiersze są uwzględniane w dopasowaniu. (Zauważ, że jest to szybsze niż bardziej intuicyjne (.|[\r\n]).) Jeśli jednak ciąg na pewno nie zawiera znaków nowej linii, to proste .będzie najszybsze. Zauważ, że będzie to dużo prostsze, jeśli zaimplementowana zostanie flaga dotall .
HappyDog
2
Czy nie /^(.+?)\1+$/jest trochę szybszy? (12 kroków vs 20 kroków)
online Thomas
29

Być może najszybszym podejściem algorytmicznym jest zbudowanie funkcji Z w czasie liniowym:

Funkcja Z dla tego ciągu jest tablicą o długości n, w której i-ty element jest równy największej liczbie znaków, zaczynając od pozycji i, która pokrywa się z pierwszymi znakami s.

Innymi słowy, z [i] jest długością najdłuższego wspólnego przedrostka między s a sufiksem s zaczynającym się od i.

Implementacja C ++ w celach informacyjnych:

vector<int> z_function(string s) {
    int n = (int) s.length();
    vector<int> z(n);
    for (int i = 1, l = 0, r = 0; i < n; ++i) {
        if (i <= r)
            z[i] = min (r - i + 1, z[i - l]);
        while (i + z[i] < n && s[z[i]] == s[i + z[i]])
            ++z[i];
        if (i + z[i] - 1 > r)
            l = i, r = i + z[i] - 1;
    }
    return z;
}

Implementacja JavaScript
Dodano optymalizacje - zbudowanie połowy tablicy z i wczesne wyjście

function z_function(s) {
  var n = s.length;
  var z = Array(n).fill(0);
  var i, l, r;
  //for our task we need only a half of z-array
  for (i = 1, l = 0, r = 0; i <= n/2; ++i) {
    if (i <= r)
      z[i] = Math.min(r - i + 1, z[i - l]);
    while (i + z[i] < n && s[z[i]] == s[i + z[i]])
      ++z[i];

      //we can check condition and return here
     if (z[i] + i === n && n % i === 0) return true;
    
    if (i + z[i] - 1 > r)
      l = i, r = i + z[i] - 1;
  }
  return false; 
  //return z.some((zi, i) => (i + zi) === n && n % i === 0);
}
console.log(z_function("abacabacabac"));
console.log(z_function("abcab"));

Następnie musisz sprawdzić indeksy, iktóre dzielą n. Jeśli znajdziesz takie i, i+z[i]=nto ciąg smożna skompresować do długości ii możesz wrócić true.

Na przykład dla

string s= 'abacabacabac'  with length n=12`

tablica z to

(0, 0, 1, 0, 8, 0, 1, 0, 4, 0, 1, 0)

i możemy to znaleźć dla

i=4
i+z[i] = 4 + 8 = 12 = n
and
n % i = 12 % 4 = 0`

więc smoże być reprezentowany jako podciąg o długości 4 powtórzony trzy razy.

MBo
źródło
3
return z.some((zi, i) => (i + zi) === n && n % i === 0)
Pranav C Balan
2
Dziękujemy za dodanie elementów JavaScript do Salman A i Pranav C Balan
MBo
1
Alternatywne podejście, unikając dodatkowej iteracjiconst check = (s) => { let n = s.length; let z = Array(n).fill(0); for (let i = 1, l = 0, r = 0; i < n; ++i) { if (i <= r) z[i] = Math.min(r - i + 1, z[i - l]); while (i + z[i] < n && s[z[i]] == s[i + z[i]]) ++z[i]; // check condition here and return if (z[i] + i === n && n % i === 0) return true; if (i + z[i] - 1 > r) l = i, r = i + z[i] - 1; } // or return false return false; }
Pranav C Balan
2
Używanie funkcji z jest dobrym pomysłem, ale jest to „informacja-ciężka”, zawiera wiele informacji, które nigdy nie są używane.
Axel Podehl
@Axel Podehl Niemniej jednak traktuje string w czasie O (n) (każdy znak jest używany najwyżej dwa razy). W każdym razie musimy sprawdzić każdy znak, więc nie ma teoretycznie szybszego algorytmu (podczas gdy zoptymalizowane wbudowane metody mogą działać lepiej). Również w ostatniej edycji ograniczyłem obliczenia o 1/2 długości struny.
MBo
23

Przeczytałem odpowiedź gnasher729 i zaimplementowałem ją. Chodzi o to, że jeśli są jakieś powtórzenia, to musi być (również) pierwsza liczba powtórzeń.

function* primeFactors (n) {
    for (var k = 2; k*k <= n; k++) {
        if (n % k == 0) {
            yield k
            do {n /= k} while (n % k == 0)
        }
    }
    if (n > 1) yield n
}

function check (str) {
    var n = str.length
    primeloop:
    for (var p of primeFactors(n)) {
        var l = n/p
        var s = str.substring(0, l)
        for (var j=1; j<p; j++) {
            if (s != str.substring(l*j, l*(j+1))) continue primeloop
        }
        return true
    }
    return false
}

Nieco inny algorytm to:

function check (str) {
    var n = str.length
    for (var p of primeFactors(n)) {
        var l = n/p
        if (str.substring(0, n-l) == str.substring(l)) return true
    }
    return false
}

Zaktualizowałem stronę jsPerf, która zawiera algorytmy używane na tej stronie.

user42723
źródło
Wydaje się to naprawdę szybkie, ponieważ pomija niepotrzebne kontrole.
Pranav C Balan
1
Bardzo fajnie, tylko myślę, że sprawdziłbym, czy pierwsza litera pojawia się ponownie w określonej lokalizacji przed wykonaniem wywołań podłańcucha.
Ben Voigt
Dla ludzi, function*którzy tak jak ja, natknęli się po raz pierwszy, chodzi o zadeklarowanie generatora, a nie zwykłej funkcji. Zobacz MDN
Julien Rousé
17

Załóżmy, że ciąg S ma długość N i składa się z duplikatów podciągu s, a następnie długość s dzieli N. Na przykład, jeśli S ma długość 15, to podciąg ma długość 1, 3 lub 5.

Niech S będzie wykonane z (p * q) kopii s. Wtedy S jest również zrobione z p kopii (s, powtórzone q razy). Mamy zatem dwa przypadki: Jeśli N jest liczbą pierwszą lub 1, to S można utworzyć tylko z kopii podciągu o długości 1. Jeśli N jest złożona, to wystarczy sprawdzić podciągi s o długości N / p dla liczb pierwszych p dzielących długość S.

Zatem określ N = długość S, a następnie znajdź wszystkie jego czynniki pierwsze w czasie O (sqrt (N)). Jeśli jest tylko jeden czynnik N, sprawdź, czy S jest tym samym łańcuchem powtórzonym N razy, w przeciwnym razie dla każdego czynnika pierwszego p sprawdź, czy S składa się z p powtórzeń pierwszych znaków N / p.

gnasher729
źródło
Nie sprawdzałem innych rozwiązań, ale wydaje się to bardzo szybkie. Możesz pominąć część „Jeśli jest tylko jeden czynnik N, sprawdź ..., w przeciwnym razie” dla uproszczenia, ponieważ nie jest to przypadek specjalny. Byłoby miło zobaczyć implementację Javascript, którą można uruchomić w jsPerf obok innych implementacji.
user42723
1
Zaimplementowałem to w mojej odpowiedzi
user42723
10

Myślę, że funkcja rekurencyjna może być również bardzo szybka. Pierwszą obserwacją jest to, że maksymalna długość powtarzanego wzoru jest o połowę mniejsza niż długość całego ciągu. Moglibyśmy po prostu przetestować wszystkie możliwe powtarzające się długości wzorów: 1, 2, 3, ..., długość str. / 2

Funkcja rekurencyjna isRepeating (p, str) sprawdza, czy ten wzorzec jest powtarzany w str.

Jeśli str jest dłuższy niż wzorzec, rekurencja wymaga, aby pierwsza część (taka sama długość jak p) była powtórzeniem, a także reszta str. Zatem str jest efektywnie dzielony na kawałki o długości p. Długość.

Jeśli testowany wzorzec i str mają taką samą wielkość, rekursja kończy się tutaj pomyślnie.

Jeśli długość jest inna (dzieje się to dla „aba” i wzorca „ab”) lub jeśli części są różne, zwracane jest fałsz, propagując rekursję.

function check(str)
{
  if( str.length==1 ) return true; // trivial case
  for( var i=1;i<=str.length/2;i++ ) { // biggest possible repeated pattern has length/2 characters

    if( str.length%i!=0 ) continue; // pattern of size i doesn't fit
    
    var p = str.substring(0, i);
    if( isRepeating(p,str) ) return true;
  }
  return false;
}


function isRepeating(p, str)
{
  if( str.length>p.length ) { // maybe more than 2 occurences

    var left = str.substring(0,p.length);
    var right = str.substring(p.length, str.length);
    return left===p && isRepeating(p,right);
  }
  return str===p; 
}

console.log(check('aa')) //true
console.log(check('aaa')) //true 
console.log(check('abcabcabc')) //true
console.log(check('aba')) //false
console.log(check('ababa')) //false

Wydajność: https://jsperf.com/reegx-and-loop/13

Axel Podehl
źródło
1
Czy sprawdzanie byłoby szybsze if( str===p.repeat(str.length/i) ) return true;zamiast korzystania z funkcji rekurencyjnej?
Chronocidal
1
Nie umieszczaj console.logs w testach jsperf, przygotuj funkcje w sekcji globals, przygotuj również ciągi testowe w sekcji globals (przepraszamy, nie można edytować jsperf)
Salman A
@Salman - słuszna uwaga. Właśnie zmodyfikowałem jsperf z mojego poprzednika (Pranav C), pierwszy raz użyłem jsperf, fajnego narzędzia.
Axel Podehl
@SalmanA: zaktualizowano: jsperf.com/regex-and-loop/1 ... dzięki za informację ... nawet ja nie jestem z nią zaznajomiony (Jsperf) ... dzięki za informację
Pranav C Balan
Cześć Salman, wielkie dzięki za jsperf.com/reegx-and-loop/10 - tak, ten nowy test wydajności ma o wiele więcej sensu. Konfiguracja funkcji powinna przejść do kodu przygotowawczego.
Axel Podehl
7

Napisałem to w Pythonie. Wiem, że to nie jest platforma, ale zajęło to 30 minut. PS => PYTHON

def checkString(string):
    gap = 1 
    index= 0
    while index < len(string)/2:
        value  = [string[i:i+gap] for i in range(0,len(string),gap) ]

        x = [string[:gap]==eachVal for eachVal in value]

        if all(x):
            print("THEY ARE  EQUAL")
            break 

        gap = gap+1
        index= index+1 

checkString("aaeaaeaaeaae")
JustABeginner
źródło
6

Moje podejście jest podobne do gnasher729, ponieważ koncentruje się na potencjalnej długości podciągu, ale jest mniej matematyczne i intensywne:

L: Długość oryginalnego sznurka

S: Potencjalne długości prawidłowych podciągów

Pętla S od (część całkowita) L / 2 do 1. Jeśli L / S jest liczbą całkowitą, sprawdź swój oryginalny ciąg względem pierwszych S znaków oryginalnego ciągu powtórzonych L / S razy.

Powodem tworzenia pętli od L / 2 do tyłu, a nie od 1 w górę, jest uzyskanie największego możliwego podciągu. Jeśli chcesz mieć najmniejszą możliwą pętlę podciągu od 1 do L / 2. Przykład: „abababab” ma zarówno „ab”, jak i „abab” jako możliwe podciągi. Który z nich byłby szybszy, gdybyś dbał tylko o wynik prawda / fałsz, zależy od typu ciągów / podciągów, do których zostanie zastosowany.

SunKnight0
źródło
5

Poniższy kod Mathematica prawie wykrywa, czy lista została powtórzona przynajmniej raz. Jeśli ciąg zostanie powtórzony co najmniej raz, zwraca wartość true, ale może również zwrócić wartość true, jeśli łańcuch jest liniową kombinacją powtarzających się ciągów.

IsRepeatedQ[list_] := Module[{n = Length@list},
   Round@N@Sum[list[[i]] Exp[2 Pi I i/n], {i, n}] == 0
];

Ten kod szuka wkładu „pełnej długości”, który w powtarzającym się ciągu musi wynosić zero, ale ciąg accbbdjest również uważany za powtórzony, ponieważ jest sumą dwóch powtarzających się ciągów abababi 012012.

Chodzi o to, aby użyć szybkiej transformaty Fouriera i poszukać widm częstotliwości. Patrząc na inne częstotliwości, powinno być również możliwe wykrycie tego dziwnego scenariusza.

Per Alexandersson
źródło
4

Podstawową ideą tutaj jest zbadanie dowolnego potencjalnego podciągu, zaczynając od długości 1 i kończąc na połowie długości oryginalnego łańcucha. Patrzymy tylko na długości podciągów, które równo dzielą oryginalną długość łańcucha (np. Str.length% substring.length == 0).

Ta implementacja sprawdza pierwszy znak każdej możliwej iteracji podłańcucha przed przejściem do drugiego znaku, co może zaoszczędzić czas, jeśli oczekuje się, że podciągi będą długie. Jeśli po zbadaniu całego podciągu nie zostanie znaleziona żadna niezgodność, zwracamy wartość true.

Zwracamy false, gdy zabraknie nam potencjalnych podciągów do sprawdzenia.

function check(str) {
  const len = str.length;
  for (let subl = 1; subl <= len/2; ++subl) {
    if ((len % subl != 0) || str[0] != str[subl])
      continue;
    
    let i = 1;
    for (; i < subl; ++i)
    {
      let j = 0;
      for (; j < len; j += subl)
        if (str[i] != str[j + i])
          break;
      if (j != len)
        break;
    }
    
    if (i == subl)
      return true;
  }
  return false;
}

console.log(check('aa')) //true
console.log(check('aaa')) //true
console.log(check('abcabcabc')) //true
console.log(check('aba')) //false
console.log(check('ababa')) //false

Austin Mullins
źródło
-1

Nie jestem zaznajomiony z JavaScriptem, więc nie wiem, jak szybko to będzie, ale tutaj jest liniowe rozwiązanie czasowe (zakładając rozsądną wbudowaną implementację), używając tylko wbudowanych. Opiszę algorytm w pseudokodzie.

function check(str) {
    t = str + str;
    find all overlapping occurrences of str in t;
    for each occurrence at position i
        if (i > 0 && i < str.length && str.length % i == 0)
            return true;  // str is a repetition of its first i characters
    return false;
}

Pomysł jest podobny do odpowiedzi MBo. Dla każdego, iktóry dzieli długość, strjest powtórzeniem jego pierwszych iznaków wtedy i tylko wtedy, gdy pozostaje taki sam po przesunięciu o iznaki.

Przychodzi mi na myśl, że taka wbudowana funkcja może być niedostępna lub nieefektywna. W takim przypadku zawsze można zaimplementować algorytm KMP ręcznie, co zajmuje mniej więcej taką samą ilość kodu, jak algorytm w odpowiedzi MBo.

infmagic2047
źródło
PO chce wiedzieć, czy istnieje powtórzenie . Drugi wiersz (treści) twojej funkcji liczy liczbę powtórzeń - to jest bit, który należy wyjaśnić. Np. „Abcabcabc” ma 3 powtórzenia „abc”, ale w jaki sposób Twoja druga linijka zorientowała się, czy zawierała jakieś powtórzenia?
Lawrence
@Lawrence Nie rozumiem twojego pytania. Algorytm ten opiera się na założeniu, że ciąg jest powtórzeniem jego fragmentu, wtedy i tylko wtedy, gdy dla pewnego dzielnik jego długości i, s[0:n-i] == s[i:n]lub równoważnie s == s[i:n] + s[0:i]. Dlaczego druga linijka musi sprawdzać, czy miała jakieś powtórzenia?
infmagic2047
Zobaczę, czy rozumiem twój algorytm. Najpierw dołączasz strdo siebie, aby utworzyć formę t, a następnie skanujesz, taby spróbować znaleźć strwnętrze t. OK, to może zadziałać (wycofałem swój głos przeciw). Jednak nie jest to liniowe w strlen (str). Say strma długość L. Następnie w każdej pozycji p = 0,1,2, ..., sprawdzając, czy str [0..L-1] == t [p..p + L-1] przyjmuje O (L ) czas. Musisz sprawdzić O (L), przechodząc przez wartości p, więc jest to O (L ^ 2).
Lawrence
-10

Jednym z prostych pomysłów jest zastąpienie łańcucha podłańcuchem „” i jeśli jakikolwiek tekst istnieje, to jest fałszywy, w przeciwnym razie jest prawdziwy.

'ababababa'.replace(/ab/gi,'')
"a" // return false
'abababab'.replace(/ab/gi,'')
 ""// return true

Vinod kumar G.
źródło
tak, dla abc lub jednorożca użytkownik nie sprawdziłby z / abc / lub / unicorn /, przepraszam, jeśli brakuje mi twojego kontekstu
Vinod kumar G
3
Pytanie mogłoby być jaśniejsze, ale to, o co prosi, to sposób decydowania, czy ciąg składa się w całości z 2 lub więcej powtórzeń dowolnego innego ciągu. Nie szuka określonego podciągu.
HappyDog
2
Dodałem pewne wyjaśnienie do pytania, które powinno teraz uczynić je jaśniejszym.
HappyDog
@Vinod, jeśli zamierzasz już używać wyrażenia regularnego, zakotwicz swoje dopasowanie i użyj testu. Nie ma powodu, aby modyfikować łańcuch tylko po to, aby potwierdzić jakiś warunek.
Marie