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


128

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()각 루프에서 사용하므로 속도가 느려진다는 것입니다. 성능과 관련하여 더 나은 솔루션이 있습니까?


19
이 링크는 당신에게 유용 할 수 있습니다. 나는 항상 알고리즘 문제에 대한 좋은 소스로 geekforgeeks을 찾을 - geeksforgeeks.org/...
Leron_says_get_back_Monica

9
이것을 빌려 프로그래밍 골프 교환 사이트에서 코딩 과제로 삼을 수 있습니까?
ouflak

7
@ouflak 당신이 할 수 있습니다.
Maheer Ali


24
@Shidersz 신경망을 사용하는 것은 대포를 사용하여 모기를 쏘는 것과 같은 느낌입니다.
JAD

답변:


186

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

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

여기서 회전이란 문자열 앞쪽에서 몇 개의 문자를 삭제하고 뒤쪽으로 이동하는 것을 의미합니다. 예를 들어 문자열 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은 입력 문자열의 길이입니다.

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


13
아주 좋아요! jsPerf 벤치 마크 페이지에 추가했습니다 .
user42723

10
@ user42723 쿨! 정말, 정말 빠른 것 같습니다.
templatetypedef

5
참고 : "문자열은 동일한 패턴으로 여러 번 반복 된 경우에만 문자열 자체가 중요하지 않습니다."라는 문구를 뒤집을 때까지이 문장을 믿기가 힘들었습니다. 그림을 이동.
Axel Podehl

11
그 정리에 대한 언급이 있습니까?
HRK44

4
첫 번째 진술은 dox.org/10.1016/j.tcs.2008.04.020의 " Lemma 2.3 : x와 x의 회전이 같으면 x는 반복적"과 같다고 생각 합니다. 또한보십시오 : stackoverflow.com/a/2553533/1462295
BurnsBA

67

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

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


2
이 줄이 무엇을하고 있는지 설명해 주실 수 있습니까? /^(.+)\1+$/.test(str)
Thanveer Shah

34
또한이 솔루션의 복잡성은 무엇입니까? 나는 확실하지 않지만 OP가 가지고있는 것보다 훨씬 빠르지는 않습니다.
Leron_says_get_back_Monica 19

8
@PranavCBalan 나는 알고리즘에 익숙하지 않기 때문에 주석 섹션에 글을 쓴다. 그러나 몇 가지 언급 할 사항이 있습니다. OP에는 이미 작동하는 솔루션이 있으므로 더 나은 성능을 제공 할 솔루션을 요구하고 있으며 솔루션이 어떻게 성능을 발휘하는지 설명하지 않았습니다. 짧다는 것이 더 빠른 것은 아닙니다. 또한, 당신이 준 링크에서 : If you use normal (TCS:no backreference, concatenation,alternation,Kleene star) regexp and regexp is already compiled then it's O(n).당신이 쓴대로 역 참조를 사용하고 있으므로 여전히 O (n)입니까?
Leron_says_get_back_Monica 19

5
다른 문자와 같은 방식으로 줄 바꿈 문자를 일치시켜야하는 경우 [\s\S]대신 사용할 수 있습니다 .. 점 문자는 개행에서 일치하지 않습니다. 대안은 모든 공백 문자와 공백이 아닌 문자를 검색합니다. 즉, 개행 문자가 일치합니다. (이것은보다 직관적 인 것보다 빠릅니다 (.|[\r\n]).) 그러나 문자열에 개행 문자가 포함되어 있지 않으면 단순 .이 가장 빠릅니다. dotall 플래그 가 구현 되면 훨씬 간단 해집니다 .
HappyDog

2
하지가 /^(.+?)\1+$/조금 더 빨리? (12 단계 vs 20 단계)
온라인 Thomas

29

아마도 가장 빠른 알고리즘 접근법은 선형 시간에 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 회 반복 하위 문자열로 표현 될 수 있습니다.


3
return z.some((zi, i) => (i + zi) === n && n % i === 0)
Pranav C Balan

2
Salman A와 Pranav C Balan에 JavaScript를 추가해 주셔서 감사합니다.
MBo

1
추가 반복을 피하여 대체 방법const 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
z- 함수를 사용하는 것은 좋은 생각이지만 '정보가 무겁다'는 정보는 많이 사용되지 않습니다.
Axel Podehl

@Axel Podehl 그럼에도 불구하고 문자열을 O (n) 시간으로 처리합니다 (각 문자는 최대 두 번 사용됨). 어쨌든 우리는 모든 문자를 확인해야하므로 이론적으로 더 빠른 알고리즘은 없습니다 (최적화 된 내장 메소드가 성능을 능가 할 수 있음). 또한 마지막 편집에서 문자열 길이의 1/2로 계산을 제한했습니다.
MBo

23

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 페이지 를 업데이트했습니다 .


불필요한 검사를 건너 뛰기 때문에 정말 빠릅니다.
Pranav C Balan

1
아주 좋은 것은 하위 문자열 호출을하기 전에 첫 번째 문자가 지정된 위치에서 다시 발생하는지 확인하는 것입니다.
Ben Voigt

function*나와 같이 처음으로 넘어지는 사람들 에게는 일반 함수가 아닌 생성기를 선언하기위한 것입니다.
Julien Rousé를

17

문자열 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 반복으로 구성되어 있는지 확인하십시오.


다른 솔루션을 확인하지는 않았지만 매우 빠릅니다. "특별한 경우가 아니기 때문에"인수 N이 하나만있는 경우 ...을 검사하고 그렇지 않으면 "을 생략 할 수 있습니다. 다른 구현 옆에 jsPerf에서 실행될 수있는 Javascript 구현을 보는 것이 좋을 것입니다.
user42723

1
나는 이제 내 대답에
user42723

10

재귀 함수도 매우 빠를 것이라고 생각합니다. 첫 번째 관찰은 최대 반복 패턴 길이가 총 스트링 길이의 절반이라는 것입니다. 그리고 가능한 모든 반복 패턴 길이를 테스트 할 수 있습니다 : 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


1
if( str===p.repeat(str.length/i) ) return true;재귀 함수를 사용하는 대신 확인하는 것이 더 빠릅니까?
Chronocidal

1
console.logs를 jsperf 테스트에 넣지 말고, globals 섹션 내에서 함수를 준비하고, globals 섹션에서 테스트 문자열을 준비하십시오 (죄송합니다, jsperf를 편집 할 수 없습니다)
Salman A

@ 살만-좋은 지적. 방금 전임 도구 (Pranav C)에서 jsperf를 수정했는데 처음으로 멋진 도구 인 jsperf를 사용했습니다.
Axel Podehl

@SalmanA : 업데이트 : jsperf.com/regex-and-loop/1 ... 정보 주셔서 감사합니다 ... 심지어 그것에 익숙하지 않습니다 (Jsperf) ... 정보 주셔서 감사합니다
Pranav C Balan

안녕 Salman, jsperf.com/reegx-and-loop/10에 대해 대단히 감사합니다 -예, 새로운 성능 테스트는 훨씬 더 의미가 있습니다. 기능 설정은 준비 코드에 들어가야합니다.
Axel Podehl

7

이것을 파이썬으로 작성했습니다. 나는 그것이 플랫폼이 아니라는 것을 알고 있지만 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")

6

내 접근 방식은 gnasher729와 비슷합니다. 잠재적 인 부분 문자열 길이를 주요 초점으로 사용하지만 수학 및 프로세스 집약도가 적습니다.

L : 원래 줄의 길이

S : 유효한 부분 문자열의 잠재적 길이

L / 2에서 1의 정수 부분으로 S를 반복합니다. L / S가 정수인 경우 L / S 번 반복 된 원래 문자열의 주먹 S 문자에 대해 원래 문자열을 확인하십시오.

1이 아닌 L / 2에서 뒤로 루프하는 이유는 가능한 가장 큰 부분 문자열을 얻는 것입니다. 1에서 L / 2까지 가능한 가장 작은 부분 문자열 루프를 원할 경우. 예 : "abababab"에는 가능한 하위 문자열로 "ab"와 "abab"이 모두 있습니다. 참 / 거짓 결과에만 관심이 있다면 둘 중 어느 것이 더 빠를 것인가? 이것은 적용 할 문자열 / 하위 문자열의 유형에 따라 다릅니다.


5

다음 Mathematica 코드 는 목록이 적어도 한 번 반복되는지 거의 감지합니다. 문자열이 적어도 한 번 반복되면 true를 반환하지만 문자열이 반복 된 문자열의 선형 조합 인 경우 true를 반환 할 수도 있습니다.

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

반복되는 문자열에 0이되어야 "전체 길이"기여에 대한이 코드 보이지만, 문자열 accbbd은 두 개의 반복 된 문자열의 합이기 때문에 또한, 반복 된 것으로 간주 ababab하고 012012.

아이디어는 고속 푸리에 변환을 사용하고 주파수 스펙트럼을 찾는 것입니다. 다른 주파수를 살펴보면이 이상한 시나리오도 감지 할 수 있어야합니다.


4

여기서 기본 아이디어는 길이 1에서 시작하여 원래 문자열 길이의 절반에서 멈추는 잠재적 하위 문자열을 검사하는 것입니다. 우리는 원래 문자열 길이를 균등하게 나누는 하위 문자열 길이 만 봅니다 (예 : str.length % substring.length == 0).

이 구현에서는 두 번째 문자로 이동하기 전에 가능한 각 하위 문자열 반복의 첫 번째 문자를 확인하므로 하위 문자열이 길면 시간을 절약 할 수 있습니다. 전체 하위 문자열을 검사 한 후 불일치가 없으면 true를 반환합니다.

검사 할 잠재적 인 하위 문자열이 부족하면 false를 반환합니다.

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


-1

JavaScript에 익숙하지 않으므로 이것이 얼마나 빠를 지 모르겠지만 여기에는 내장 만 사용하는 선형 시간 솔루션 (합리적인 내장 구현 가정)이 있습니다. 의사 코드로 알고리즘을 설명하겠습니다.

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

이 아이디어는 MBo의 답변과 비슷합니다. i길이를 나누는 각각에 대해 , 문자를 이동 한 후에도 동일하게 유지되는 경우에만 str첫 번째 i문자 가 반복 됩니다 i.

그러한 내장 기능을 사용할 수 없거나 비효율적 일 수 있습니다. 이 경우 KMP 알고리즘을 수동으로 구현하는 것이 항상 가능 하며 MBo의 답변에있는 알고리즘과 거의 동일한 코드를 필요로합니다.


OP는 반복 이 존재 하는지 알고 싶어 합니다 . 함수의 두 번째 줄은 반복 횟수를 계산 합니다. 설명해야 할 부분입니다. 예를 들어 "abcabcabc"에는 "abc"가 3 번 반복되지만 두 번째 줄 은 반복이 있는지 어떻게 확인 했 습니까?
Lawrence

@Lawrence 나는 당신의 질문을 이해하지 못합니다. 이 알고리즘은 문자열을 경우에만 길이의 일부 제수에 대한 경우의 문자열의 반복이라는 생각을 기반으로 i, s[0:n-i] == s[i:n],, 또는 동등 s == s[i:n] + s[0:i]. 두 번째 줄에 반복이 있는지 여부를 해결해야하는 이유는 무엇입니까?
infmagic2047

알고리즘을 이해하는지 알려 드리겠습니다. 먼저 strform에 추가 t한 다음 inside t를 찾기 위해 스캔 합니다 . 좋아, 이것은 작동 할 수있다 (나는 downvote를 철회했다). 그러나 strlen (str)에서는 선형이 아닙니다. 길이가 L이라고 가정합니다 . 그런 다음 각 위치 p = 0,1,2, ...에서 str [0..L-1] == t [p..p + L-1]이 O (L ) 시각. p의 값을 거칠 때 O (L) 검사를 수행해야하므로 O (L ^ 2)입니다. strtstr
로렌스

-10

간단한 아이디어 중 하나는 문자열을 ""의 하위 문자열로 바꾸는 것입니다. 텍스트가 존재하면 거짓이고 그렇지 않은 경우에도 마찬가지입니다.

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


예, abc 또는 unicorn의 경우 사용자가 / abc / 또는 / unicorn /으로 확인하지 않습니다. 컨텍스트가없는 경우 죄송합니다.
Vinod kumar G

3
질문은 더 명확 할 수 있지만 요청하는 것은 문자열이 다른 문자열의 2 개 이상의 반복으로 완전히 구성되는지 여부를 결정하는 방법입니다. 특정 하위 문자열을 검색하지 않습니다.
HappyDog

2
질문에 약간의 설명을 추가하여 지금 명확하게해야합니다.
HappyDog

@Vinod 이미 정규식을 사용하려는 경우 일치를 고정하고 테스트를 사용해야합니다. 조건을 확인하기 위해 문자열을 수정할 이유가 없습니다.
Marie
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.