[javascript] 문자열이 완전히 동일한 하위 문자열로 만들어 졌는지 어떻게 확인합니까?

I 문자열을 취하는 함수를 작성해야하고, 그 반환해야 true또는 false입력이 반복되는 문자 시퀀스를 구성하는지 여부에 따라. 주어진 문자열의 길이는 항상보다 길고 1문자 시퀀스에는 적어도 하나의 반복이 있어야합니다.

"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)

아래 기능을 만들었습니다.

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

이것을 확인하는 것은 실제 문제의 일부입니다. 이와 같은 비효율적 인 솔루션을 감당할 수 없습니다. 우선, 문자열의 절반을 반복합니다.

두 번째 문제는 replace()각 루프에서 사용하므로 속도가 느려진다는 것입니다. 성능과 관련하여 더 나은 솔루션이 있습니까?



답변

이와 같은 문자열에 대한 멋진 작은 정리가 있습니다.

문자열은 문자열 자체가 사소한 회전 인 경우에만 여러 번 반복되는 동일한 패턴으로 구성됩니다.

여기서 회전이란 문자열 앞쪽에서 몇 개의 문자를 삭제하고 뒤쪽으로 이동하는 것을 의미합니다. 예를 들어 문자열 hello을 회전하여 다음 문자열 중 하나를 형성 할 수 있습니다.

hello (the trivial rotation)
elloh
llohe
lohel
ohell 

이것이 왜 작동하는지 확인하려면 먼저 문자열이 문자열 w의 k 반복 사본으로 구성되어 있다고 가정하십시오. 그런 다음 반복 된 패턴의 첫 번째 사본 (w)을 끈 앞면에서 삭제하고 뒷면에 ​​타킹하면 같은 문자열이 나타납니다. 반대 방향은 약간 까다로울 수 있지만 문자열을 회전하고 시작한 항목을 다시 가져 오면 동일한 회전의 패턴을 가진 여러 복사본으로 문자열을 바둑판 식으로 배열하기 위해 회전을 반복적으로 적용 할 수 있다는 아이디어입니다. 회전을 위해 끝으로 이동 해야하는 문자열).

이제 질문은 이것이 사실인지 확인하는 방법입니다. 이를 위해 사용할 수있는 또 다른 아름다운 정리가 있습니다.

x와 y가 같은 길이의 문자열이면 x가 yy의 하위 문자열 인 경우에만 x는 y의 회전입니다.

예를 들어, 다음과 같이 lohel회전 함을 알 수 있습니다 hello.

hellohello
   ^^^^^

우리의 경우 모든 문자열 x는 항상 xx의 하위 문자열이됩니다 (x의 각 복사본마다 한 번씩 두 번 나타남). 따라서 기본적으로 문자열 x가 첫 번째 또는 중간 문자와 일치하지 않고 문자열 x가 xx의 하위 문자열인지 확인하면됩니다. 여기에 하나의 라이너가 있습니다.

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

indexOf빠른 문자열 일치 알고리즘을 사용하여 구현 한다고 가정하면 시간 O (n)에서 실행되며 여기서 n은 입력 문자열의 길이입니다.

도움이 되었기를 바랍니다!


답변

캡처 그룹역 참조를 통해이를 수행 할 수 있습니다 . 처음 캡처 된 값이 반복되는지 확인하십시오.

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

위의 RegExp에서 :

  1. ^$에 대한 스탠드 시작과 끝 앵커 위치를 예측한다.
  2. (.+)모든 패턴을 캡처하고 값을 제외합니다 (제외 \n).
  3. \1첫 번째 캡처 된 값의 역 참조이며 캡처 된 값의 \1+반복을 확인합니다.

여기에 정규식 설명

RegExp 디버깅의 경우 https://regex101.com/r/pqlAuP/1/debugger

성능 : https://jsperf.com/reegx-and-loop/13


답변

아마도 가장 빠른 알고리즘 접근법은 선형 시간에 Z 함수 를 작성하는 것입니다.

이 문자열의 Z- 함수는 길이 n의 배열이며, i 번째 요소는 s의 첫 문자와 일치하는 위치 i에서 시작하여 최대 문자 수와 같습니다.

즉, z [i]는 s와 i에서 시작하는 접미사 사이의 가장 긴 공통 접두사 길이입니다.

참조를위한 C ++ 구현 :

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;
}

JavaScript 구현
최적화 추가-z-array의 절반 및 조기 종료 빌드

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"));

그런 다음 in을 나누는 인덱스를 확인해야합니다 . 당신이 그런 발견하면 i것을 i+z[i]=n다음 문자열 s길이로 압축 할 수 있습니다 i당신은 반환 할 수 있습니다 true.

예를 들어

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

Z- 배열은

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

우리는 그것을 찾을 수 있습니다

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

그래서 s길이 4의 3 회 반복 하위 문자열로 표현 될 수 있습니다.


답변

gnasher729의 답변을 읽고 구현했습니다. 아이디어는 반복이있는 경우 소수의 반복이 있어야한다는 것입니다.

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
}

약간 다른 알고리즘은 다음과 같습니다.

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
}

이 페이지에서 사용 된 알고리즘이 포함 된 jsPerf 페이지 를 업데이트했습니다 .


답변

문자열 S의 길이가 N이고 하위 문자열 s의 복제본으로 구성된 다음 s의 길이는 N을 나눕니다. 예를 들어 S의 길이가 15 인 경우 하위 문자열의 길이는 1, 3 또는 5입니다.

S를 s의 (p * q) 사본으로 만들어 보자. 그런 다음 S는 또한 p 개의 사본으로 만들어집니다 (s, 반복 된 q 번). 따라서 N이 소수이거나 1 인 경우 S는 길이가 1 인 부분 문자열의 복사본으로 만 만들 수 있습니다. N이 복합 인 경우 길이가 N / p 인 부분 문자열의 소수를 검사하여 소수를 나누면됩니다. S의 길이

따라서 N = S의 길이를 결정한 다음 시간 O (sqrt (N))에서 모든 주요 요소를 찾으십시오. 요인 N이 하나만있는 경우 S가 동일한 문자열로 N 번 반복되는지 확인하고, 그렇지 않으면 각 소수 요인 p에 대해 S가 첫 번째 N / p 문자의 p 반복으로 구성되어 있는지 확인하십시오.


답변

재귀 함수도 매우 빠를 것이라고 생각합니다. 첫 번째 관찰은 최대 반복 패턴 길이가 총 스트링 길이의 절반이라는 것입니다. 그리고 가능한 모든 반복 패턴 길이를 테스트 할 수 있습니다 : 1, 2, 3, …, str.length / 2

재귀 함수 isRepeating (p, str)은이 패턴이 str에서 반복되는지 테스트합니다.

str이 패턴보다 긴 경우 재귀는 첫 번째 부분 (p와 동일한 길이)이 반복 및 나머지 str의 길이 여야합니다. 따라서 str은 길이 p.length의 조각으로 효과적으로 나뉩니다.

테스트 된 패턴과 str의 크기가 같으면 재귀가 여기서 끝납니다.

길이가 다르거 나 ( “aba”및 패턴 “ab”에 대해 발생 함) 조각이 다른 경우 false가 반환되어 재귀를 전파합니다.

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

성능 : https://jsperf.com/reegx-and-loop/13


답변

이것을 파이썬으로 작성했습니다. 나는 그것이 플랫폼이 아니라는 것을 알고 있지만 30 분의 시간이 걸렸습니다. PS => 피톤

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")