2011-01-14 5 views
0

perl에서 xml을 파싱하고 구문 분석 한 다음 csv를 빌드하는 스크립트를 작성했습니다. xml을 가져 와서 파싱하고 정렬하는 것은 매우 원활하게 진행되는 것 같습니다. 그러나 일단 큰 데이터 세트 (예 : 10000 개의 행과 260 개의 열이있는 csv를 작성)로 들어가면 스크립트는 엄청난 시간 (~ 1 시간)을 소비하기 시작합니다. csv 문자열을 작성합니다. 필자는 Perl이 아마도 문자열 연결에 가장 좋지 않다는 것을 알고 있습니다. 그러나 나는 이것보다 더 효율적이라고 생각했을 것이다.perl에서 csv를 빌드하는 중에 발생하는 문제

기본적으로 정렬을 위해 두 개의 배열 해시가 있습니다. 하나의 해시에는 정렬에 사용한 배열이 들어 있습니다. 다른 해시에는 다른 모든 열 (CSV에 쓰기를 원하는 열, 그러나 정렬 방법과 관련이없는 열)에 대한 배열이 들어 있습니다.

my $csv = "Header1, Header2, Header3, Header4,...,HeaderN-1,HeaderN\n"; 
    foreach my $index (@orderedIndecies) { 
    my @records = @{$primaryFields{"Important Field 1"}}; 
    $csv .= $records[$index] ? "$records[$index]," : ","; 
    $csv .= $primaryIndex[$index] >= 0 ? "$primaryIndex[$index]," : ","; 
    @records = @{$primaryFields{"Important Field 2"}}; 
    $csv .= $records[$index] ? "$records[$index]," : ","; 
    foreach my $key (@keys) { 
     @records = @{$csvContent{$key}}; 
     if($key eq $last) { 
     $csv .= $records[$index] ? "$records[$index]\n" : "\n"; 
     } else { 
     $csv .= $records[$index] ? "$records[$index]," : ","; 
     } 
    } 
    } 

가 나는 또한 단지가. "="대신 방법을 결합하여 같은 일을 시도 : 그래서 내 문제 코드 코드는 다음과 같다 (나는이 영원히 복용 코드 블록 확인했습니다). 또한 문자열 집계를 모두 모으고 파일에 직접 쓰려고했습니다. 이 두 가지 모두 그다지 도움이되지 않았다. 나는 perl에서의 메모리 관리에 대한 나의 지식이 아마도 가장 크지 않다는 것을 인정하는 첫 번째 사람이 될 것이다. 제발 학교에 나눠주세요. (건설적으로). 또한, 이것이 펄의 외부에서 재 작성하는 것을 고려해야한다고 생각된다면 알려주십시오.

편집 : 일부 샘플 XML (필자는 XML의 구조를 편집 할 위치에 있지 해요 명심하시기 바랍니다) :

<fields> 
    <field> 
    <Name>IndicesToBeSorted</Name> 
    <Records>idx12;idx14;idx18;...idxN-1;idxN</Records> 
    </field> 
    <field> 
    <Name>Important Field1</Name> 
    <Records>val1;val2;;val4;...;valn-1;valn</Records> 
    </field> 
    <field> 
    <Name>Important Field2</Name> 
    <Records>val1;val2;;val4;...;valn-1;valn</Records> 
    </field> 
    <field> 
    <Name>Records...</Name> 
    <Records>val1;val2;;val4;...;valn-1;valn</Records> 
    </field> 
    <field> 
    <Name>More Records...</Name> 
    <Records>val1;val2;;val4;...;valn-1;valn</Records> 
    </field> 
</fields> 

하나 개의 필드의 레코드의 위치는 위치에 해당 다른 분야. 예를 들어; 각 "레코드"요소의 첫 번째 항목이 연결되어 있고 내 CSV에서 열을 구성합니다. 그래서 기본적으로 스크립트는이 모든 것을 파싱하고 정렬 된 인덱스의 배열을 만듭니다 (이 예제는 내 예제에서 @orderedIndecies에있는 것입니다). @orderdIndecies는 orderedIndecies에서 문자열 순서가 있기 때문에 나는이 일이 방법을

print "$orderedInecies[0]\n" #prints index of location of idx0 
print "$orderedInecies[1]\n" #prints index of location of idx1 
print "$orderedInecies[2]\n" #prints index of location of idx2 
print "$orderedInecies[3]\n" #prints index of location of idx3 

... 같은 데이터를 포함하고; 나는 모든 데이터를 옮기고 싶지 않았다.

편집 : 도움말들에 대한 최종 답변

open my $csv_fh, ">", $$fileNameRef or die "$$fileNameRef: $!"; 
    print $csv_fh "Important Field 1,Index Field,Important Field 2"; 

    # Defining $comma, $endl, $empty allows me to do something like: 
    # 
    #     print $csv_fh $val ? $val : $empty; 
    #     print $csv_fh $comma; 
    # 
    # As opposed to.... 
    # 
    #     print $csv_fh $val ? "$val," : ","; 
    # 
    # Note, the first method avoids the string aggregation of "$val," 
    my $comma = ","; 
    my $endl = "\n"; 
    my $empty = ""; 

    my @keys = sort(keys %csvContent); 
    my $last = $keys[-1]; 
    foreach (@keys) { 
    print $csv_fh $_; 
    print $csv_fh $_ eq $last ? $endl : $comma; 
    } 

    # Even though the hash lookup is probably very efficient, I still 
    # saw no need to redo it constantly, so I defined it here as 
    # opposed to inline within the for loops 
    my @ImportantFields1 = @{$primaryFields{"Important Field 1"}}; 
    my @ImportantFields2 = @{$primaryFields{"Important Field 2"}}; 

    print "\n\n--------- BUILD CSV START ---------------\n\n"; 
    foreach my $index (@orderedIndecies) { 
    print $csv_fh exists $ImportantFields1[$index] ? $ImportantFields1[$index] : $empty; 
    print $csv_fh $comma; 
    print $csv_fh $originalIndexField[$index] >= 0 ? $originalIndexField[$index] : $empty; 
    print $csv_fh $comma; 
    print $csv_fh exists $ImportantFields2[$index] ? $ImportantFields2[$index] : $empty; 

    #If needed, this is where you would make sure to escape commas 
    foreach my $key (@keys) { 
     print $csv_fh $comma; 
     $record = exists @{$csvContent{$key}}[$index] 
        ? @{$csvContent{$key}}[$index]; 
        : $empty; 
    } 
    print $csv_fh $endl; 
    } 

    print "\n\n------- CSV Contents wrtten to file -----------\n\n" 
    close($csv_fh); 

감사합니다 : D

+0

샘플 XML 및 예상되는 CSV 출력을 보지 않고도 자신이 무엇을하고 있는지 말할 수는 없습니다. 또한 이것은 아마도 XSLT를 사용하는 것보다 쉽다 (또는 매우 쉽다). –

+0

샘플 xml로 업데이트되었습니다. xslt를 사용하는 것이 훨씬 더 효율적입니까? 내가 가지고있는 XML이 잘 구조화되어 있지 않다는 것과 xslt 내부에서 문자열 분석을위한 논리를 많이 가져야 만 원하는 결과를 얻을 수있는 CSV를 얻을 수 있다는 것을 알고 있습니다. 특히 행의 내용을 정렬해야하기 때문에 특히 그렇습니다. xslt는 여전히 고려해야 할 사항입니까? – Dave

+2

사실, perl은 문자열을 조작하기위한 훌륭한 도구입니다. 이것은 주요 목적 중 하나입니다. 알고리즘이 오래 걸리는 이유는 알고리즘 자체입니다. O (N^2)입니다. 언어에 관계없이, 그것은 연결하기 위해 260 만 값으로 오랜 시간이 걸릴 것입니다. 할당해야하고 매번 다시 할당해야합니다 (거의 perl은 일반적으로 그것에 대해 영리합니다). 그런 종류의 작업을 수행합니다. –

답변

4

내가 함께 10 만 임의의 문자열 배열 (260)의 또 다른를 생성하는 작은 프로그램을 넣어 마지막에 join에게 그것들을 배열로 개별 필드를 조립하고, 수 임의의 문자열을 만든 다음 루프를 연결하여 루프의 해당 부분에 도달하는 데 걸리는 시간을 인쇄합니다. 당신이 볼 수 있듯이

On Key 0, has been 00:00:00 
On value 0, has been 00:00:00 
On value 10000, has been 00:00:05.4373956 
On value 20000, has been 00:00:22.3901951 
On value 30000, has been 00:00:51.1552678 
On value 40000, has been 00:01:31.0138775 
On value 50000, has been 00:02:26.4659378 
On value 60000, has been 00:03:32.6834164 
On value 70000, has been 00:04:48.4788361 

는 연결 자체가 일반적으로 작업의 종류를 처리 할 수있는 좋은 방법이 아니다 : concatonated 처음 70,000 값의 경우, 이것은 내 프로그램에 저를 복용 시간입니다. '고정 된'양의 데이터가 있더라도 필요한 메모리 복사로 인해 소요 시간이 길어집니다.

대안은 Perl에서 권장하는 것을 수행하는 것입니다 - 디스크에 기록하십시오! :) 연결을 전혀하지 마십시오. 각 파트를 디스크에 저장하십시오. 마지막 줄을 23.2183042 초에 끝낸 샘플 프로그램. 그러면 다음과 같이 보일 것입니다 :

# a couple utilities 

use File::Temp qw/tempdir/; 
use File::Spec::Functions; # catdir 

# ... and later 

my $dir = tempdir(); # give me a temporary, empty place to put it 
my $file = catdir($dir, 'temp.csv'); 

open my $fh, '>', $file 
    or die "Can't open '$file' for write: $!"; 

print {$fh} "Header1, Header2, Header3, Header4,...,HeaderN-1,HeaderN\n"; 
foreach my $index (@orderedIndecies) { 
    my @records = @{$primaryFields{"Important Field 1"}}; 
    print $records[$index] ? "$records[$index]," : ","; 
    print $primaryIndex[$index] >= 0 ? "$primaryIndex[$index]," : ","; 
    @records = @{$primaryFields{"Important Field 2"}}; 
    print $records[$index] ? "$records[$index]," : ","; 
    foreach my $key (@keys) { 
     @records = @{$csvContent{$key}}; 
     if($key eq $last) { 
      print {$fh} $records[$index] ? "$records[$index]\n" : "\n"; 
     } else { 
      print {$fh} $records[$index] ? "$records[$index]," : ","; 
     } 
    } 
} 

close $fh 
    or warn "Can't close '$file' for some reason: $!"; 

이제는 연결이없고 메모리가 복사되지 않습니다. 정말로, 정말로 빨리 가야합니다.

+0

답변으로 표시. 나는 원래 이것을 "올바르게"했다고 생각했다. 직접 파일 핸들을 인쇄하기로 전환했지만 여전히 많은 문자열 집계를 수행하고 있다는 것을 깨닫지 못했습니다. 나는 내가 사용을 끝내는 최종적인 algo와 함께 편집을 게시 할 것이다. – Dave

2

IT는 하나의 변수 $csv에 전체 출력을 조립 할 필요가 있습니까? 더 건전한 방법은 각 레코드에 대해 하나의 요소를 포함하는 배열을 사용하는 것입니다. 그런 다음 배열을 출력 스트림에 인쇄하거나, 주장하는 경우 join을 사용하여 배열 요소를 단일 스칼라로 연결합니다. 각 레코드 내에서

, 당신은 또한

my @csv = ("Header1, Header2, Header3, Header4,...,HeaderN-1,HeaderN\n"); 
    foreach my $index (@orderedIndecies) { 
    my @records = @{$primaryFields{"Important Field 1"}}; 
    my @newRow =(); 
    push @newRow, $records[$index] ? $records[$index] : ""; 
    # alternatively: push @newRow, $records[$index] || ""; 
    push @newRow, $primaryIndex[$index]>=0 ? $primaryIndex[$index] : ""; 
    @records = @{$primaryFields{"Important Field 2"}}; 
    push @newRow, $records[$index] ? $records[$index] : ""; 
    foreach my $key (@keys) { 
     @records = @{$csvContent{$key}}; 
     push @newRow, $records[$index] ? $records[$index] : ""; 
    } 
    push @csv, join(",", @newRow) . "\n"; 
    } 

    ... 
    $csv = join '', @csv;  # if necessary 
+0

답변 해 주셔서 감사합니다. 기회가 생겨 결과를 게시 할 때 시도해 보겠습니다. – Dave

4

손으로 구겨진 코드 대신 다음을 사용하면 속도가 약간만 올라가는 것을 볼 수있을 것입니다. CSV 작성을위한 Text::CSV_XS (필요한 경우 읽기) 및 XML::LibXML을 XML 구문 분석에 사용합니다. 그들은 각각 두건의 밑에 C를 사용한다.

문자열 연결은 속도는 물론 견고 함을 나타내는 문제가 될 수 있습니다.

관련 문제