Thread Möglichkeiten von Perl (Excel-Tabellen auswerten) (26 answers)
Opened by black_perl at 2014-11-17 15:38

payx
 2014-11-20 20:27
#178487 #178487
User since
2006-05-04
564 Artikel
BenutzerIn

user image
Jetzt hat mir das keine Ruhe gelassen, und ich musste es ausprobieren. Das Ergebnis ist durchaus überraschend, finde ich.

Das folgende Script erzeugt die beiden Excel-Beispieldateien, liest sie dann wieder aus, mappt die Daten und schreibt sie in eine neue Excel-Datei. Dabei wird für jeden Einzelvorgang die Zeit gestoppt.

Wenn man dem Script als Parameter csv übergibt, werden stattdessen csv-Dateien erzeugt und ausgelesen.

more (75.2kb):
Code (perl): (dl )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
#!/usr/bin/perl

use strict;
use warnings;

$|++;

use Spreadsheet::XLSX;
use Excel::Writer::XLSX;

use Encode qw(decode encode);

use Time::HiRes qw(gettimeofday tv_interval);

my $filetype = $ARGV[0];
if (! $filetype) {
    $filetype = "xlsx";
}
elsif ($filetype ne "xlsx" and $filetype ne "csv") {
    die "unknown filetype '$filetype'";
}

# config
my $adrcount = 1_500_000; # divisible by 3
my $transactcount = 35_000;

my $adrfile      = "black_perl_adr." . $filetype;
my $transactfile = "black_perl_btr." . $filetype;
my $outputfile   = "black_perl_out." . $filetype;
# /config

createAdrFile($filetype, $adrfile, $adrcount);
createTransactFile($filetype, $transactfile, $adrcount, $transactcount);
my $alldataHR = readAllData($filetype, $adrfile, $transactfile);
writeResultFile($alldataHR, $filetype, $outputfile);

sub createAdrFile {
    my $t0 = [ gettimeofday ];

    my $filetype = shift;
    my $adrfile = shift;
    my $adrcount = shift;

    print " ... Creating address file $adrfile ...\n";

    my @header = (qw(Kundennummer Name Vorname Straße Hausnummer PLZ Ort Telefon));

    my @adrdata = (
        [qw(Müller Maier Schröder Lehmann)]
        , [qw(Hans Grete Ulrich Susanne)]
        , [qw(Goethestraße Ulmenweg Bahnhofstraße Hauptstraße)]
        , [qw(14 23a 16/1 9)]
        , [qw(12345 23456 34567 45678)]
        , [qw(Foobach Musterstadt Irgendwingen Nirgendhausen)]
        , ['+49 123 45678912', '+49 234 56789123', '+49 345 67891234', '+49 456 78912345']
    );

    if ($filetype eq 'xlsx') {
        my $workbook = Excel::Writer::XLSX->new($adrfile) or die $!;

        my $adrcountperws = $adrcount / 3;

        for my $ws (0 .. 2) {
            my $worksheet = $workbook->add_worksheet();

            for my $row (0 .. $#header) {
                $worksheet->write(0, $row, $header[$row]);
            }

            for my $row (1 .. $adrcountperws) {
                my $kdnr = $ws * $adrcountperws + $row;

                $worksheet->write($row, 0, $kdnr);
                for my $col (1 .. @adrdata) {
                    my $i = int(rand()*4);
                    my $val = $adrdata[$col-1]->[$i];
                    $worksheet->write($row, $col, $val);
                }
            }
        }
    }
    elsif ($filetype eq 'csv') {
        open my $csvFH, ">", $adrfile;

        print $csvFH (join ";", @header);

        for my $kdnr (1 .. $adrcount) {
            print $csvFH "\n";
            print $csvFH $kdnr;
            for my $col (1 .. @adrdata) {
                my $i = int(rand()*4);
                my $val = $adrdata[$col-1]->[$i];
                print $csvFH ";" . $val;
            }
        }
    }
    print "Created address file $adrfile in " . (tv_interval( $t0 )) . " seconds\n";
}

sub createTransactFile {
    my $t0 = [ gettimeofday ];

    my $filetype = shift;
    my $transactfile = shift;
    my $adrcount = shift;
    my $transactcount = shift;

    print " ... Creating transation file $transactfile ...\n";

    my @header = (qw(Kundennummer Geldbetrag));

    my ($workbook, $worksheet, $csvFH);

    if ($filetype eq 'xlsx') {
        $workbook = Excel::Writer::XLSX->new($transactfile) or die $!;

        $worksheet = $workbook->add_worksheet();

        for my $col (0 .. $#header) {
            $worksheet->write(0, $col, $header[$col]);
        }

        for my $row (1 .. $transactcount) {
            my $kdnr = int(rand()*$adrcount+1);
            my $betr = int(rand()*100_000+1)/100;

            $worksheet->write($row, 0, $kdnr);
            $worksheet->write($row, 1, $betr);
        }
    }
    elsif ($filetype eq 'csv') {
        open $csvFH, ">", $transactfile;

        print $csvFH (join ";", @header);
    }

    for my $row (1 .. $transactcount) {
        my $kdnr = int(rand()*$adrcount+1);
        my $betr = int(rand()*100_000+1)/100;

        if ($filetype eq 'xlsx') {
            $worksheet->write($row, 0, $kdnr);
            $worksheet->write($row, 1, $betr);
        }
        else {
            print $csvFH "\n$kdnr;$betr";
        }
    }
    print "Created transaction file $transactfile in " . (tv_interval( $t0 )) . " seconds\n";
}

sub readAllData {
    my $t0 = [ gettimeofday ];

    my $filetype = shift;
    my $adrfile = shift;
    my $transactfile = shift;

    my %alldata;

    print " ... Reading data from transaction file $transactfile ...\n";

    if ($filetype eq 'xlsx') {
        my $xlsx = Spreadsheet::XLSX->new($transactfile) or die $!;

        my $worksheet = $xlsx->{Worksheet}->[0];

        for my $row (1 .. $worksheet->{MaxRow}) {

            my $kdnr = $worksheet->{Cells}->[$row]->[0]->{Val};
            my $betr = $worksheet->{Cells}->[$row]->[1]->{Val};
            $alldata{$kdnr}->{$row} = [$betr];
        }
    }
    elsif ($filetype eq 'csv') {
        open my $csvFH, "<", $transactfile;

        my $header = <$csvFH>;

        while (my $row = <$csvFH>) {
            chomp $row;
            my ($kdnr, $betr) = split /;/, $row;
            next unless $kdnr == $kdnr*1;
            $alldata{$kdnr}->{$.} = [$betr];
        }
    }

    print " ... Reading data from address file $adrfile ...\n";

    if ($filetype eq 'xlsx') {
        my $xlsx = Spreadsheet::XLSX->new($adrfile) or die $!;

        for my $worksheet (@{$xlsx->{Worksheet}}) {
            my $colcount = $worksheet->{MaxCol};

            for my $row (1 .. $worksheet->{MaxRow}) {
                my $kdnr = $worksheet->{Cells}->[$row]->[0]->{Val};

                if ($alldata{$kdnr}) {
                    for my $entry (keys %{$alldata{$kdnr}}) {
                        for my $col (1 .. $colcount) {
                                push @{$alldata{$kdnr}->{$entry}}, $worksheet->{Cells}->[$row]->[$col]->{Val};
                        }
                    }
                }
            }
        }
    }
    else {
        open my $csvFH, "<", $adrfile;

        my $header = <$csvFH>;

        while (my $row = <$csvFH>) {
            chomp $row;
            my ($kdnr, @adr) = split /;/, $row;

            if ($alldata{$kdnr}) {
                for my $entry (keys %{$alldata{$kdnr}}) {
                    push @{$alldata{$kdnr}->{$entry}}, @adr;
                }
            }
        }
    }
    print "All data read from $adrfile, $transactfile in " . (tv_interval( $t0 )) . " seconds\n";
    return \%alldata;
}

sub writeResultFile {
    my $t0 = [ gettimeofday ];

    my $alldataHR = shift;
    my $filetype = shift;
    my $outputfile = shift;

    print " ... Creating output file $outputfile ...\n";

    my @header = (qw(Kundennummer Geldbetrag Name Vorname Straße Hausnummer PLZ Ort Telefon));

    if ($filetype eq 'xlsx') {
        my $workbook = Excel::Writer::XLSX->new($outputfile) or die $!;

        my $worksheet = $workbook->add_worksheet();

        for my $row (0 .. $#header) {
            $worksheet->write(0, $row, $header[$row]);
        }

        my $row = 0;

        for my $kdnr (sort {$a <=> $b} keys %{$alldataHR}) {
            for my $entry (keys %{$alldataHR->{$kdnr}}) {
                $row++;
                $worksheet->write($row, 0, $kdnr);
                for my $col (1 .. @{$alldataHR->{$kdnr}->{$entry}}) {
                    my $val = $alldataHR->{$kdnr}->{$entry}->[$col-1];

                    $val = decode('utf-8',$val);

                    $worksheet->write($row, $col, $val);
                }
            }
        }
    }
    elsif ($filetype eq 'csv') {
        open my $csvFH, ">", $outputfile;

        print $csvFH (join ";", @header);

        for my $kdnr (sort {$a <=> $b} keys %{$alldataHR}) {
            for my $entry (keys %{$alldataHR->{$kdnr}}) {
                print $csvFH "\n" . (join ";", ($kdnr, @{$alldataHR->{$kdnr}->{$entry}}));
            }
        }
    }
    print "All data written to output file $outputfile in " . (tv_interval( $t0 )) . " seconds\n";
}


Output auf meinem Laptop (Win7, 64Bit, 2,9GHz Intel i7, 16 GB RAM):

Als xlsx:
Code: (dl )
1
2
3
4
5
6
7
8
9
 ... Creating address file black_perl_adr.xlsx ...
Created address file black_perl_adr.xlsx in 211.786114 seconds
... Creating transation file black_perl_btr.xlsx ...
Created transaction file black_perl_btr.xlsx in 1.305074 seconds
... Reading data from transaction file black_perl_btr.xlsx ...
... Reading data from address file black_perl_adr.xlsx ...
All data read from black_perl_adr.xlsx, black_perl_btr.xlsx in 1642.366938 seconds
... Creating output file black_perl_out.xlsx ...
All data written to output file black_perl_out.xlsx in 7.947455 seconds

Der Arbeitsspeicher-Verbrauch geht dabei zwischenzeitlich bis über 10 GB.

Also schon eine anspruchsvolle Aufgabe für den Rechner, zwar deutlich schneller als die von OP genannten 6 Stunden, aber doch viel, viel langsamer als die reine csv-Verarbeitung:
Code: (dl )
1
2
3
4
5
6
7
8
9
 ... Creating address file black_perl_adr.csv ...
Created address file black_perl_adr.csv in 6.162353 seconds
... Creating transation file black_perl_btr.csv ...
Created transaction file black_perl_btr.csv in 0.058533 seconds
... Reading data from transaction file black_perl_btr.csv ...
... Reading data from address file black_perl_adr.csv ...
All data read from black_perl_adr.csv, black_perl_btr.csv in 3.806632 seconds
... Creating output file black_perl_out.csv ...
All data written to output file black_perl_out.csv in 0.106645 seconds

Arbeitsspeicherverbrauch in der Spitze um 68 MB.

Vermutlich geht es also tatsächlich schneller, wenn man die csv-Dateien aus Excel von Hand exportiert (nehmen wir mal an, es gibt keine Konvertierungsprobleme), diese dann wie oben verarbeitet und am Ende wieder das resultierende csv von Hand in Excel einliest.

Gut, dass ich nicht gewettet habe.

Oder ist das Script (im xlsx-Teil) schlecht programmiert? (Oder vielleicht sollte man sich auch mal andere XLSX-Module ansehen.)

Grüße
payx

View full thread Möglichkeiten von Perl (Excel-Tabellen auswerten)