2014-06-19 9 views
1

편집 : 비트와 문자열의 연속적인 위치를 추적하고 후자를 const ref로 전달하기 위해 반복자를 사용하는 것이 주요 변경되었습니다. 이제 시계를 테스트하기 위해 샘플 입력을 여러 번 복사하면 모든 것이 실제로 긴 비트와 문자열 및 심지어 최대 50 줄의 샘플 입력까지 10 초 내에 완료됩니다. 하지만 여전히 제출할 때 CodeEval은 프로세스가 10 초 후에 중단되었다고 말합니다. 내가 언급했듯이, 그들은 입력을 공유하지 않으므로 샘플 입력 작업의 "확장"이 어떻게 진행될 지 확신하지 못합니다. 재귀적인 성능을 향상시키기위한 추가 개선에 대한 생각은 대단히 감사하겠습니다.재귀 적 문자열 변환

참고 : Memoization은 좋은 제안 이었지만 정적 look-up 테이블에 bit-to-char 상관 관계를 저장하는 방법을 모르기 때문에이 경우 구현하는 방법을 이해할 수 없었습니다. 내가 생각한 유일한 것은 비트 값을 해당 정수로 변환하는 것이지만 긴 비트 문자열의 경우 정수 오버플로가 발생할 위험이 있으며 계산하는 데 너무 오래 걸리는 것처럼 보입니다. 여기 memoization에 대한 제안은 크게 감사 할 것입니다.

이것은 실제로 적절한 CodeEval 과제 중 하나입니다. 그들은 중대한 문제를 위해 샘플 입력이나 출력을 공유하지 않지만 출력 "실패 오류"는 단순히 "10 초 후에 중단되었습니다"라고 말하면서 코드가 어딘가에서 멈추게됩니다.

할당은 간단합니다. 단일 명령 행 인수로 파일 경로를 사용합니다. 파일의 각 행에는 공백으로 구분 된 0과 1의 순서와 As와 Bs의 순서가 포함됩니다. 다음 두 규칙에 따라 이진 시퀀스를 문자 시퀀스로 변환 할 수 있는지 여부를 결정해야합니다.

1) 각 0은 비어 있지 않은 As 시퀀스 (예 : 'A', 'AA ','AAA '등)

2) 각 1을 As 또는 B의 비어 있지 않은 시퀀스 (예 :'A ','AA '등 또는'B ' BB '등) (글자가 섞이지는 않음)

제약 조건은 파일에서 최대 50 줄을 처리하고 바이너리 시퀀스의 길이는 [1,150]이고 문자 시퀀스의 길이는 in [1,1000].

가장 확실한 시작 알고리즘은이 작업을 재귀 적으로 수행하는 것입니다. 내가 생각해내는 것은 각 비트마다 다음에 허용되는 문자 그룹 전체를 축소하고, 단축 된 비트와 문자열을 테스트하는 것입니다. 실패하면 한 번에 살해 된 문자 그룹의 문자 하나를 다시 추가하고 다시 호출하십시오.

여기 내 코드가 있습니다. 간결함을 위해 cmd 행 인수 오류 검사를 제거했습니다.|

1 :

샘플 입력 :

#include <iostream> 
#include <fstream> 
#include <string> 
#include <iterator> 

using namespace std; 

//typedefs 
typedef string::const_iterator str_it; 

//declarations 
//use const ref and iterators to save time on copying and erasing 
bool TransformLine(const string & bits, str_it bits_front, const string & chars, str_it chars_front); 

int main(int argc, char* argv[]) 
{ 
    //check there are at least two command line arguments: binary executable and file name 
    //ignore additional arguments 
    if(argc < 2) 
    { 
     cout << "Invalid command line argument. No input file name provided." << "\n" 
      << "Goodybe..."; 
     return -1; 
    } 

    //create input stream and open file 
    ifstream in; 
    in.open(argv[1], ios::in); 
    while(!in.is_open()) 
    { 
     char* name; 
     cout << "Invalid file name. Please enter file name: "; 
     cin >> name; 
     in.open(name, ios::in); 
    } 

    //variables 
    string line_bits, line_chars; 

    //reserve space up to constraints to reduce resizing time later 
    line_bits.reserve(150); 
    line_chars.reserve(1000); 

    int line = 0; 

    //loop over lines (<=50 by constraint, ignore the rest) 
    while((in >> line_bits >> line_chars) && (line < 50)) 
    { 
     line++;  
     //impose bit and char constraints 
     if(line_bits.length() > 150 || 
      line_chars.length() > 1000) 
      continue; //skip this line 

     (TransformLine(line_bits, line_bits.begin(), line_chars, line_chars.begin()) == true) ? (cout << "Yes\n") : (cout << "No\n"); 
    } 

    //close file 
    in.close(); 

    return 0; 
} 

bool TransformLine(const string & bits, str_it bits_front, const string & chars, str_it chars_front) 
{ 
    //using iterators so store current length as local const 
    //can make these const because they're not altered here 
    int bits_length = distance(bits_front, bits.end()); 
    int chars_length = distance(chars_front, chars.end()); 

    //check success rule 
    if(bits_length == 0 && chars_length == 0) 
     return true; 

    //Check fail rules: 
    //1. next bit is 0 but next char is B 
    //2. bits length is zero (but char is not, by previous if) 
    //3. char length is zero (but bits length is not, by previous if) 
    if((*bits_front == '0' && *chars_front == 'B') || 
     bits_length == 0 || 
     chars_length == 0) 
     return false; 

    //we now know that chars_length != 0 => chars_front != chars.end() 

    //kill a bit and then call recursively with each possible reduction of front char group 
    bits_length = distance(++bits_front, bits.end()); 

    //current char group tracker 
    const char curr_char_type = *chars_front; //use const so compiler can optimize 
    int curr_pos = distance(chars.begin(), chars_front); //position of current front in char string 

    //since chars are 0-indexed, the following is also length of current char group 
    //start searching from curr_pos and length is relative to curr_pos so subtract it!!!  
    int curr_group_length = chars.find_first_not_of(curr_char_type, curr_pos)-curr_pos; 

    //make sure this isn't the last group! 
    if(curr_group_length < 0 || curr_group_length > chars_length) 
     curr_group_length = chars_length; //distance to end is precisely distance(chars_front, chars.end()) = chars_length 

    //kill the curr_char_group 
    //if curr_group_length = char_length then this will make chars_front = chars.end() 
    //and this will mean that chars_length will be 0 on next recurssive call. 
    chars_front += curr_group_length; 
    curr_pos = distance(chars.begin(), chars_front); 

    //call recursively, adding back a char from the current group until 1 less than starting point 
    int added_back = 0; 
    while(added_back < curr_group_length) 
    { 
     if(TransformLine(bits, bits_front, chars, chars_front)) 
      return true; 

     //insert back one char from the current group 
     else 
     { 
      added_back++; 
      chars_front--; //represents adding back one character from the group 
     } 

    } 
    //if here then all recursive checks failed so initial must fail 
    return false; 
} 

그들은 내 코드가 올바르게 해결하는, 다음 테스트 케이스를 제공 1010 AAAAABBBBAAAA

2 | 00 AAAAAA

3 | 01001110 AAAABAAABBBBBBAAAAAAA

4 | 1100110 BBAABABBA

올바른 출력 :

1 | 예

2 | 예

3 | 예

4 | 아니

변환이 가능하기 때문에 사본이있는 경우에만 각 바이너리와 문자 시퀀스를 여러 번 복사하고 시계가 어떻게 나타나는지 보았습니다. 매우 긴 비트와 문자열 및 많은 줄까지도 10 초 이내에 끝났습니다.

내 질문은 : CodeEval은 여전히 ​​10 초 이상 실행되지만 입력 내용을 공유하지 않는다고 말하면 누구나이 재귀의 성능을 향상시키기위한 제안 사항이 있습니까? 아니면 전혀 다른 접근 방식일까요?

도움을 주셔서 감사합니다.

+0

'const'참조로 문자열을 전달하여 데이터 구조를 복사하는 데 소요되는 시간을 줄입니다. –

+0

'bits.length()'를 두 번 확인합니다. 함수 호출은 실행하는 데 시간이 걸립니다. 상태가 필요하면 주먹 호출로 변수에 복사하십시오. –

답변

0

는 여기에 내가 무엇을 발견 : 일정 참조
문자열과 다른 대형 데이터 구조에 의해

패스가 일정 참조에 의해 전달되어야한다.
이렇게하면 컴파일러는 데이터 구조의 복사본을 만드는 대신 원본 개체에 대한 포인터를 전달할 수 있습니다. 당신은 두 번 bits.length()를 호출

통화 기능을 한 번, 결과를 저장합니다. 한 번 호출하여 결과를 상수 변수에 저장해야합니다. 이렇게하면 함수를 호출하지 않고 상태를 다시 확인할 수 있습니다.

함수 호출은 시간이 중요한 프로그램의 경우 비용이 많이 듭니다.

사용 상수 변수
당신이 할당 후 변수를 수정하지 않을 경우는, 선언에 const를 사용

const char curr_char_type = chars[0]; 

const는 컴파일러가 높은 순서 최적화를 수행 할 수 있습니다 및 안전 점검을 제공합니다 .

변경 데이터 구조
당신은 어쩌면 문자열의 중간에 삽입을 수행하기 때문에, 당신은 문자에 대한 다른 데이터 구조를 사용한다.std::string 데이터 형식은 삽입 후 다시 할당하고 문자를 더 아래로 이동해야 할 수 있습니다. 연결된 목록은 포인터를 바꿔주기 때문에 삽입은 std::list<char>으로 빠릅니다. 연결된 목록이 각 문자에 동적으로 메모리를 할당해야하기 때문에 절충안이있을 수 있습니다. 당신의 문자열의

준비 공간
는 대상 문자열을 만들 때 미리 할당 또는 생성자를 사용해야하는 가장 큰 크기의 문자열을 보유 룸. 이렇게하면 std::string이 재 할당되지 않습니다. 재 할당은 비용이 많이 듭니다.

지우지 마십시오
문자열의 문자를 정말로 지워야합니까? 시작 및 끝 인덱스를 사용하면 전체 문자열을 지우지 않고 기존 문자를 덮어 씁니다. 부분 지우기는 비용이 많이 듭니다. 완전한 지우개는 아닙니다.

자세한 내용은 StackExchange의 코드 검토에 게시하십시오.

+0

나는 std :: list가 성능 향상을 가져올 것이라고 생각하지 않는다. http://channel9.msdn.com/Events/GoingNative/GoingNative-2012/Keynote-Bjarne-Stroustrup-Cpp11-Style @ 44min을 참조하십시오. –

0

이것은 고전적인 재귀 문제입니다. 그러나 재귀를 순진하게 구현하면 이전에 계산 된 함수 값을 기하 급수적으로 재평가하게됩니다. 설명을 위해보다 간단한 예제를 사용하면 합리적으로 큰 N에 대해 다음 두 함수의 런타임을 비교할 수 있습니다. 넘치는 int에 대해 걱정하지 않아도됩니다.

int RecursiveFib(int N) 
{ 
if(N<=1) 
return 1; 
return RecursiveFib(N-1) + RecursiveFib(N-2); 
} 

int IterativeFib(int N) 
{ 
if(N<=1) 
return 1; 
int a_0 = 1, a_1 = 1; 
for(int i=2;i<=N;i++) 
{ 
int temp = a_1; 
a_1 += a_0; 
a_0 = temp; 
} 
return a_1; 
} 

여기에서 유사한 접근 방법을 따라야합니다. 동적 프로그래밍과 메모 작성이라는 두 가지 일반적인 문제 해결 방법이 있습니다. Memoization은 접근 방식을 수정하는 가장 쉬운 방법입니다. 아래는 귀하의 구현 속도를 향상시킬 수있는 방법을 설명하기 위해 memoized fibonacci 구현입니다.

0

오류 메시지는 "10 초 후 중단되었습니다"- 프로그램이 정상적으로 작동했지만 너무 오래 걸렸음을 의미합니다. 재귀 적 프로그램이 긴 입력 문자열에 대해 기하 급수적으로 더 많은 시간을 소요한다는 점을 감안하면 이해할 만합니다. 짧은 문자열 (2 ~ 8 자리)에서는 잘 작동하지만 100 개 이상의 숫자 문자열에 대해서는 막대한 양의 시간이 걸릴 것입니다 (테스트 허용). 실행 시간이 어떻게되는지 확인하려면 더 긴 테스트 입력을 구성하고 실행하는 데 걸리는 시간을 확인해야합니다.

0000000011111111 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBAAAAAAAA 
00000000111111110000000011111111 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBAAAAAAAA 

이상을 사용해보세요. 최대 150 자 및 1000자를 처리 할 수 ​​있어야합니다.

0

CodeEval에서 입력이 무엇인지 출력하는 "솔루션"을 제출하고 테스트 세트를 수집 할 수 있습니다. 그들에는 변이가 있을지도 모르다 그래서 견본을 더 모으기 위하여 그것을 몇 시간 복종시키고 싶을 수도있다. 그 중 일부는 수동으로 해결하기가 너무 어렵습니다. 수동으로 해결할 수있는 솔루션은 CodeEval에서도 매우 비효율적 인 솔루션으로도 빠르게 실행됩니다. 따라서 고려해야 할 사항이 있습니다.

어쨌든, 나는이 모든 문제를 CodeEval (모든 것의 VB 사용)에서했는데 내 솔루션은 "현재"색인이 무엇인지에 따라 A와 B의 "다음 색인"을 재귀 적으로 찾았습니다 번역에서 (재귀 적 방법에서 멈춤 조건을 확인한 후). 나는 memoization을 사용하지 않았지만 그것은 속도를 높이는데 도움이되었을 것입니다.

추신 : 나는 코드를 실행하지 않았지만 재귀 메서드가 호출되는 while 루프가 재귀 메서드에 포함되어 있는지 궁금해합니다. 이미 재귀 적이므로 모든 시나리오를 포함해야합니다.() 루프가 필요합니까?