/[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

Annotation of /trunk/WOU_Util.pm

Parent Directory Parent Directory | Revision Log Revision Log


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

1 dpavlin 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