/[vdw]/trunk/WOU_Util.pm
This is repository of my old source code which isn't updated any more. Go to git.rot13.org for current projects!
ViewVC logotype

Contents of /trunk/WOU_Util.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1 - (show annotations)
Sun Feb 6 05:28:38 2005 UTC (19 years, 2 months ago) by dpavlin
File size: 18968 byte(s)
initial import into svn

1 package WOU_Util;
2
3 # Utilities
4 #
5 # Jeremy Hickerson, 3/5/2002
6
7 use strict;
8 use Safe;
9
10 BEGIN {
11 use Exporter ();
12 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
13
14 # set the version for version checking
15 $VERSION = 1.00;
16 @ISA = qw(Exporter);
17 @EXPORT = qw(&compound_sort &hash_compare &output_delimited
18 &date_stamp &arr2hash &date_compare &add2hash &fix_nulls
19 &ssn_format &join_table_subs &is_true &draw_from_hat
20 &num_sort &add_commas2dollars add_commas
21 &date_stamp_mrep &date_sort &delta_time
22 &outer_join_table_subs);
23 %EXPORT_TAGS = ( ); # eg: TAG => [ qw!name1 name2! ],
24 @EXPORT_OK = qw( );
25 }
26 use vars @EXPORT_OK;
27 use subs qw(compound_sort sort_nested_hash hash_compare output_delimited
28 date_stamp arr2hash date_compare add2hash fix_nulls ssn_format
29 join_table_subs is_true draw_from_hat num_sort add_commas2dollars
30 add_commas date_stamp_mrep date_sort delta_time
31 outer_join_table_subs);
32
33 my $compartment = new Safe;
34
35 $compartment->permit(qw()); # nothing! just need comparison operators
36
37 # ============================================================================
38 # Routines
39 # ============================================================================
40 sub compound_sort {
41
42 # "special sort" param is optional
43 my ($rrecords_hash, $rkeys_array, $rh_special_sort) = @_;
44
45 my ($arr_size, $rresults_array, $rrecord_key, $sort_key, $sortkey_val,
46 %sort_category_hash, $rsort_category_array, $depth);
47
48 # Returns reference to array holding all values in compound sort order.
49 #
50 # Explanation
51 # -----------
52 # %{$rrecords_hash} is a hash of hashes, where the hashes have the keys in
53 # @$rkeys_array (and maybe some additional fields). Each hash is uniquely
54 # identified by $rrecords_hash->{$rrecord_key}.
55 #
56 # Below is an example of %records_hash
57 # $term_code{$pidm} = { };
58 # $rrecord_key = $term_code{$pidm};
59 #
60 # $records{$rrecord_key} =
61 # { "term" => $term_code,
62 # "ethnicity" => $ethnicity,
63 # "recr_status" => $recr_status,
64 # "first_name" => $rname,
65 # "middle_name" => $rname,
66 # "last_name" => $rname,
67 # "street" => $raddr,
68 # "city" => $raddr,
69 # "state" => $raddr,
70 # "zip" => $raddr,
71 # "nation" => $raddr,
72 # "phone" => $phone,
73 # "major" => $major,
74 # "gpa" => $gpa,
75 # "high_school" => $high_school };
76 #
77 # So %records is of the form (r2a => r3a, r2b => r3b, ... r2n => r3n),
78 # where the r2's are the $$record_key's and the r3's are the anonymous
79 # hashes of ("term" => $term_code, "ethnicity" => $ethnicity, ...).
80 #
81 # Compound sorting will be accomplished by building a hash of nested
82 # hash references, and pushing each $rrecord_key onto an array reference
83 # held by the hash at the end of the hash reference chain.
84 # If @$rkeys_array = ("term_code", "ethnicity", "recr_status") then this
85 # array reference (call it $rsort_category_array) is of the form
86 #
87 # $sort_category_hash{$term_code}->{$ethnicity}->{$recr_status}.
88 #
89 # The values of $term_code, $ethnicity, etc. for any record are given by
90 #
91 # $sortkey_val = $rrecords_hash->{$rrecord_key}->{$sort_key};
92 #
93 # Each @{$rsort_category_array} holds all the records that belong to the
94 # given compound sort category.
95
96
97 if (defined($rh_special_sort) ) {
98 # make sure $rh_special_sort{$sort_key} is defined for all values so
99 # we don't have to check on every inner loop iteration below.
100 foreach $sort_key (@{$rkeys_array}) {
101 if (!exists($rh_special_sort->{$sort_key}) ) {
102 $rh_special_sort->{$sort_key} = "";
103 }
104 }
105 }
106
107 # ===================
108 # Build Nested Hash
109 # ===================
110 # Builds a nested hash where the nested keys are values specific to this
111 # record. We will then sort the keys from the outermost to the innermost.
112 # Need to do numeric sort where needed based on the existence of the
113 # hash key 'sort' at the given hash level. The numeric sort routine
114 # (or any other type of special sort on a given key will be passed in
115 # $rh_special_sort, where the hash key is the sort column value and the
116 # hash value is the perl code reference for the special sort routine.
117 # This code ref will be put on the hash key 'sort' at the appropriate
118 # level when the nested keys are created below.
119 foreach $rrecord_key (keys %{$rrecords_hash}) {
120
121 foreach $sort_key (@{$rkeys_array}) {
122
123 # Put special sort routine in at every level needed. Will just
124 # overwrite it w/ the same value every iteration; this is probably
125 # faster than checking for its existence every time.
126
127 $sortkey_val = $rrecords_hash->{$rrecord_key}->{$sort_key};
128 $arr_size = @{$rkeys_array};
129
130 if ($sort_key eq $rkeys_array->[0]) { # on first sort_key
131
132 $sort_category_hash{"sort"} = $rh_special_sort->{$sort_key};
133
134 $sort_category_hash{$sortkey_val} =
135 $sort_key ne $rkeys_array->[$arr_size - 1] ? { } : [ ]
136 unless exists $sort_category_hash{$sortkey_val};
137
138 $rsort_category_array = $sort_category_hash{$sortkey_val};
139 }
140 else {
141
142 $rsort_category_array->{"sort"} = $rh_special_sort->{$sort_key};
143
144 # nest this reference in previous reference
145 $rsort_category_array->{$sortkey_val} =
146 $sort_key ne $rkeys_array->[$arr_size - 1] ? { } : [ ]
147 unless exists $rsort_category_array->{$sortkey_val};
148
149 $rsort_category_array = $rsort_category_array->{$sortkey_val};
150 }
151 }
152
153 # put the record key on full nested reference
154 push @{$rsort_category_array}, $rrecords_hash->{$rrecord_key};
155 }
156
157 # ==================
158 # Sort Nested Hash
159 # ==================
160 $depth = @{$rkeys_array};
161 $rresults_array =
162 sort_nested_hash(\%sort_category_hash, $depth);
163
164 return $rresults_array;
165 }
166
167
168 sub sort_nested_hash {
169 my ($rsort_category_hash, $depth) = @_;
170
171 # Recurses to $depth, sorting hash %{$rsort_category_hash} keys at every
172 # level. Pushes values at final levels onto @sort_results, which ends
173 # up holding all values in correct compound sort order.
174
175 my ($sorted_key, @sorted_keys_array, @sort_results, $rresults_array,
176 $hash_ref, $sort_sub);
177
178 #===============================
179 # check if we're done recursing
180 #===============================
181 if ($depth == 0) {
182 # return array of record_keys for this compound sort category
183 # (remember, rsort_category_hash now points to an anonymous array
184 # of anonymous hashes at the final nested hash key)
185 return $rsort_category_hash;
186 }
187
188 #==========================================
189 # otherwise, sort the keys at this depth
190 # and call self to sort keys at next depth
191 #==========================================
192 if ( $rsort_category_hash->{sort} ) {
193 $sort_sub = $rsort_category_hash->{sort};
194 delete $rsort_category_hash->{sort}; # otherwise it will be in the output
195
196 @sorted_keys_array = sort $sort_sub keys %{$rsort_category_hash};
197 }
198 else {
199 delete $rsort_category_hash->{sort}; # delete dummy sort key
200 @sorted_keys_array = sort keys %{$rsort_category_hash};
201 }
202
203
204 foreach $sorted_key (@sorted_keys_array) {
205
206 # "append" $sorted_key to hash ref passed in and call self
207 $rresults_array = sort_nested_hash($rsort_category_hash->{$sorted_key},
208 $depth - 1);
209
210 push @sort_results, @{$rresults_array};
211 }
212
213 return \@sort_results;
214 }
215
216 # hash_compare(): compares fields in record 1 with fields in record 2. Allows
217 # Oracle '%' wildcard at end of expression, ignores case.
218 sub hash_compare {
219
220 my ($rh_hash1, $rh_hash2) = @_;
221 my ($field_name, $hash2_field_re);
222
223 # if we look at each hash as a record in a table, where the keys
224 # are the field names and the values the field values, then we
225 # compare if the same field names have the same values. We take
226 # hash2 to be a subset of hash 1. If not (i.e. if hash2 has a
227 # field that isn't in hash1) then the hashes don't match. If all
228 # values for fields in hash2 match the values for the same fields
229 # in hash1 then the hashes match. We allow a "%" wildcard at the
230 # end of a string. "xyz%" matches /^(xyz)(.*)$/ (like
231 # the Oracle "like 'xyz%'", but only at the end of a string).
232
233 foreach $field_name (keys %{$rh_hash2} ) {
234
235 $hash2_field_re = $rh_hash2->{$field_name};
236
237 $hash2_field_re =~ s/^(.*)%$/$1\(\.\*\)/;
238
239 if ( ! exists $rh_hash1->{$field_name} ||
240 $rh_hash1->{$field_name} !~ /^$hash2_field_re$/i ) {
241
242 return 0; # "record" doesn't match - return false
243 }
244 }
245
246 # fall-through means all fields in hash1 matched, so "record" matches
247 # return true
248 return 1;
249 }
250
251
252 sub output_delimited {
253 my ($fh_out, $ra_records, $ra_field_order, $rh_field_titles, $delim,
254 $no_titles) = @_;
255
256 my ($rh_record, $field_name, $save_ors);
257
258 if (!$delim ) { $delim = "\|" } # "|" is default
259
260 fix_nulls($ra_records); # clean up data
261
262 # check Output Record Separator
263 if ( defined($\) ) {
264 $save_ors = $\;
265 $\ = ""; # disable for field-by-field print below
266 }
267
268 # output delimited field titles
269 if ( !$no_titles or uc($no_titles) eq "N" ) {
270 foreach $field_name (@{$ra_field_order} ) {
271 # print $field_name for field title if no $rh_field_titles
272 print $fh_out $rh_field_titles ? $rh_field_titles->{$field_name}
273 : $field_name;
274
275 # don't print delimiter if on last field
276 print $fh_out $delim unless $field_name eq
277 $ra_field_order->[ @{$ra_field_order} - 1 ];
278 }
279
280 if ( defined($save_ors) ) {
281 print $fh_out $save_ors;
282 }
283 else {
284 print $fh_out "\n";
285 }
286 }
287
288 OUTPUT_LOOP:
289 foreach $rh_record (@{$ra_records} ) {
290
291 next OUTPUT_LOOP unless defined( %{$rh_record} );
292
293 # fall-through
294
295 # output delimited field values
296 foreach $field_name (@{$ra_field_order} ) {
297 # allow the possibility that records may not have all the fields
298 if ( exists $rh_record->{$field_name} ) {
299 print $fh_out $rh_record->{$field_name};
300 }
301
302 # don't print delimiter if on last field
303 print $fh_out $delim unless $field_name eq
304 $ra_field_order->[ @{$ra_field_order} - 1 ];
305 }
306
307 if ( defined($save_ors) ) {
308 $\ = $save_ors; # restore saved value to $\
309 print $fh_out ""; # this will print the Output Record Separator
310 }
311 else {
312 print $fh_out "\n";
313 }
314 }
315 }
316
317
318 sub date_stamp {
319 my @ts = localtime;
320 my $timestamp = sprintf "%d/%d/%d %.2d:%.2d",
321 $ts[4] + 1,
322 $ts[3],
323 1900 + $ts[5],
324 $ts[2],
325 $ts[1];
326 return $timestamp;
327 }
328
329
330 sub date_stamp_mrep {
331
332 my %months = ( 1 => "JAN",
333 2 => "FEB",
334 3 => "MAR",
335 4 => "APR",
336 5 => "MAY",
337 6 => "JUN",
338 7 => "JUL",
339 8 => "AUG",
340 9 => "SEP",
341 10 => "OCT",
342 11 => "NOV",
343 12 => "DEC" );
344
345 my @ts = localtime;
346 my $timestamp = sprintf "%.2d-%s-%.2d %.2d:%.2d:%.2d",
347 $ts[3],
348 $months{ $ts[4] + 1 },
349 substr(1900 + $ts[5], 2, 2),
350 $ts[2],
351 $ts[1],
352 $ts[0];
353 return $timestamp;
354 }
355
356
357 sub arr2hash {
358 my $array_ref = shift;
359
360 my ($value, %hash, $i);
361
362 $i = 0;
363 foreach $value ( @{$array_ref} ) { $hash{$i++} = $value }
364 return \%hash;
365 }
366
367
368 sub date_compare {
369 my ($date, $op, $refdate) = @_;
370
371 my ($mm_date, $dd_date, $yyyy_date) = split(/\//, $date);
372 my ($mm_ref, $dd_ref, $yyyy_ref) = split(/\//, $refdate);
373
374 my $date_str = $yyyy_date . $mm_date . $dd_date;
375 my $ref_str = $yyyy_ref . $mm_ref . $dd_ref;
376
377 $compartment->reval( qq{
378 ($date_str $op $ref_str) || return 0;
379
380 # fall-through
381 return 1;
382 } );
383 }
384
385
386 # Note: You don't have to pass $rh_field_map, and if passed, it doesn't need
387 # values for every field. This sub will translate the fieldnames passed in
388 # $rh_field_map (if any) and leave the rest unchanged.
389 sub add2hash {
390
391 my ($rh_hash1, $rh_hash2, $rh_field_map) = @_;
392
393 my ($new_key, $mapped_key);
394
395 foreach $new_key ( keys %{$rh_hash2} ) {
396
397 if ( defined($rh_field_map) and
398 exists($rh_field_map->{$new_key} ) ) {
399 $mapped_key = $rh_field_map->{$new_key};
400 }
401 else { $mapped_key = $new_key }
402
403 if (!exists($rh_hash1->{$mapped_key} ) ) { # if name conflict, hash1 wins
404 $rh_hash1->{$mapped_key} = $rh_hash2->{$new_key};
405 }
406 }
407 }
408
409
410 sub fix_nulls {
411
412 my $ra_hashes = shift;
413
414 my ($rh_hash, $field);
415
416 FIX_LOOP:
417 for $rh_hash ( @{ $ra_hashes } ) {
418
419 next FIX_LOOP unless defined( %{$rh_hash} );
420
421 # fall-through
422
423 for $field ( keys %{ $rh_hash } ) {
424
425 if ( !defined($rh_hash->{$field} ) ) {
426 $rh_hash->{$field} = "";
427 }
428
429 }
430 }
431 }
432
433
434 sub ssn_format {
435 my $str = shift;
436
437 length($str) < 6 && return $str;
438
439 # fall-through
440 return substr($str, 0, 3) . "-" . substr($str, 3, 2) . "-" . substr($str, 5);
441 }
442
443
444 # Has optional final parm "outer_join". If outer_join is 'Y' then will return
445 # rows from sub1 even if no corresponding rows from sub2.
446 sub join_table_subs {
447 my ($rs_sub1, $ra_parms1, $rs_sub2, $ra_parms2, $outer_join) = @_;
448
449 my (@new_table, $ra_sub1, $rh_sub1, $ra_sub2, $rh_sub2, @subst_parms, $parm,
450 $subst_parm, $rh_new, $got_join_rows);
451
452 $ra_sub1 = &{ $rs_sub1 } ( @{ $ra_parms1 } );
453
454
455 foreach $rh_sub1 ( @{ $ra_sub1 } ) {
456
457 while (shift @subst_parms) { } # reset
458
459 # substitute $rh_sub1 values for relational parms (identified by "$" prefix)
460 foreach $parm ( @{$ra_parms2} ) {
461 $subst_parm = $parm;
462
463 if ( $subst_parm =~ /^\$/ ) {
464 $subst_parm =~ s/^\$//;
465
466 $subst_parm = $rh_sub1->{$subst_parm};
467 }
468
469 push @subst_parms, $subst_parm;
470 }
471
472 $ra_sub2 = &{ $rs_sub2 } ( @subst_parms );
473
474 $got_join_rows = 0;
475 foreach $rh_sub2 ( @{ $ra_sub2 } ) {
476
477 $got_join_rows = 1;
478 $rh_new = { }; # get new memory
479 add2hash($rh_new, $rh_sub1); # "dup" $rh_sub1
480
481 add2hash($rh_new, $rh_sub2);
482 push @new_table, $rh_new;
483 }
484
485 if ( defined($outer_join) and
486 $outer_join eq 'Y' and
487 !$got_join_rows ) {
488
489 $rh_new = { }; # get new memory
490 add2hash($rh_new, $rh_sub1); # "dup" $rh_sub1
491
492 push @new_table, $rh_new;
493 }
494 }
495
496 return \@new_table;
497 }
498
499
500 sub is_true {
501
502 my $comparison_str = shift;
503
504 $compartment->reval( qq{
505 if ($comparison_str) { return 1 }
506
507 # fall-through
508 return 0;
509 } );
510 }
511
512
513 # draw_from_hat() - single arg is ref to array of scalar identifiers
514 sub draw_from_hat {
515
516 my $ra_population = shift;
517
518 my ($identifier, %rand_population, $cnt, $rand_no, @sorted_keys,
519 @save_sorted_keys, $rand_key, $pop_size, $size);
520
521 $pop_size = @{$ra_population};
522
523
524 # put each identifier into %rand_population under a random key
525 foreach $identifier (@$ra_population) {
526
527 $rand_no = rand;
528 while ( exists $rand_population{$rand_no} ) {
529 $rand_no = rand;
530 }
531
532 $rand_population{$rand_no} = $identifier;
533 }
534
535
536 # pick the random number of times to iterate
537 $rand_no = 0;
538
539 while ( $rand_no <= 0 ) {
540 $rand_no = rand;
541 }
542
543 $rand_no *= ($pop_size * 5); # go through population up to 5 times just
544 # so we don't have any bias for or against
545 # those at the beginning or end of the
546 # population (note: rand returns decimals
547 # between 0 and 1)
548
549 $rand_no = sprintf("%d", $rand_no + 1); # make sure the integer part is > 0
550 # so that we get at least 1 iteration
551
552
553 # sort by random keys and iterate a random number of times to pick the winner
554
555 @sorted_keys = (sort keys %rand_population);
556 @save_sorted_keys = @sorted_keys;
557
558 $cnt = 0;
559
560 while ( $cnt != $rand_no ) {
561
562 $cnt++;
563
564 $size = @sorted_keys;
565
566 ( $size ) || ( @sorted_keys = @save_sorted_keys );
567
568 $rand_key = shift @sorted_keys;
569
570 }
571
572
573 return $rand_population{$rand_key};
574
575 }
576
577
578 sub num_sort {
579 $a <=> $b;
580 }
581
582 sub date_sort {
583 date_compare($a, "<", $b) && return -1;
584 date_compare($a, "==", $b) && return 0;
585 date_compare($a, ">", $b) && return 1;
586 }
587
588
589 sub add_commas2dollars {
590
591 my $amount = shift;
592
593 my ($dollars, $cents);
594
595 ($dollars, $cents) = split(/\./, $amount);
596
597 if ( !defined($cents) ) { $cents = 0 };
598
599 return add_commas($dollars) . '.' . substr(sprintf("%.2d", $cents), 0, 2);
600 }
601
602
603 sub add_commas {
604
605 my $integer = shift;
606
607 my ($digit, $pos, @digits, $char, $cnt, $integer_w_commas);
608
609 $cnt = 0;
610 # grab each digit starting w/ the last
611 for ( $pos = length($integer) - 1; $pos >= 0; $pos-- ) {
612
613 $cnt++;
614
615 $digit = substr($integer, $pos, 1);
616 push @digits, $digit;
617
618 if ( $cnt % 3 == 0 ) {
619 push @digits, ',';
620 }
621 }
622
623 while ( defined($char = pop @digits) ) {
624 $integer_w_commas .= $char;
625 }
626
627 $integer_w_commas =~ s/^,//; # in case $cnt ended on a multiple of 3
628
629 return $integer_w_commas;
630 }
631
632
633 sub delta_time {
634
635 my $delta_hrs = shift;
636
637 defined($delta_hrs) && $delta_hrs =~ /^[-+]*\d+/
638 or die "ERROR: bad param $delta_hrs";
639
640 my (@ts);
641
642 @ts = localtime( time() + ($delta_hrs * 3600) );
643
644 # fall-through
645 my $timestamp = sprintf "%.2d/%.2d/%d %.2d:%.2d",
646 $ts[4] + 1,
647 $ts[3],
648 1900 + $ts[5],
649 $ts[2],
650 $ts[1];
651
652 return $timestamp;
653 }
654
655
656 return 1; # for module
657
658

  ViewVC Help
Powered by ViewVC 1.1.26