Friday, February 23, 2007

A Cube Example

Again taken from msdn...

A cube is defined by its measures and dimensions. The measures and dimensions in a cube are derived from the tables and views in the data source view on which the cube is based, or which is generated from the measure and dimension definitions.

The Imports cube contains two measures, Packages and Last, and three related dimensions, Route, Source, and Time.

Cube Example 1

The smaller alphanumeric values around the cube are the members of the dimensions. Example members are ground (member of the Route dimension), Africa (member of the Source dimension), and 1st quarter (member of the Time dimension).


The values within the cube cells represent the two measures, Packages and Last. The Packages measure represents the number of imported packages, and the Sum function is used to aggregate the facts. The Last measure represents the date of receipt, and the Max function is used to aggregate the facts.


The Route dimension represents the means by which the imports reach their destination. Members of this dimension include ground, nonground, air, sea, road, or rail. The Source dimension represents the locations where the imports are produced, such as Africa or Asia. The Time dimension represents the quarters and halves of a single year.


Business users of a cube can determine the value of any measure for each member of every dimension, regardless of the level of the member within the dimension, because Analysis Services aggregates values at upper levels as needed. For example, the measure values in the preceding illustration can be aggregated according to a standard calendar hierarchy by using the Calendar Time hierachy in the Time dimension as illustrated in the following diagram.

Diagram of measures organized along time dimension

In addition to aggregating measures by using a single dimension, you can aggregate measures by using combinations of members from different dimensions. This allows business users to evaluate measures in multiple dimensions simultaneously. For example, if a business user wants to analyze quarterly imports that arrived by air from the Eastern Hemisphere and Western Hemisphere, the business user can issue a query on the cube to retrieve the following dataset.

Packages Last
All Sources Eastern Hemisphere Western Hemisphere All Sources Eastern Hemisphere Western Hemisphere
All Time 25110 6547 18563 Dec-29-99 Dec-22-99 Dec-29-99
1st half 11173 2977 8196 Jun-28-99 Jun-20-99 Jun-28-99
1st quarter 5108 1452 3656 Mar-30-99 Mar-19-99 Mar-30-99
2nd quarter 6065 1525 4540 Jun-28-99 Jun-20-99 Jun-28-99
2nd half 13937 3570 10367 Dec-29-99 Dec-22-99 Dec-29-99
3rd quarter 6119 1444 4675 Sep-30-99 Sep-18-99 Sep-30-99
4th quarter 7818 2126 5692 Dec-29-99 Dec-22-99 Dec-29-99

After a cube is defined, you can create new aggregations, or you can change existing aggregations to set options such as whether aggregations are precalculated during processing or calculated at query time.

Thursday, February 22, 2007

Why use UDM?

Continuing with my research about Data Warehousing, the following are some of my notes in Data Modeling. Taken from this tutorial in SQL Server from msdn.

UDM - Unified Dimensional Model

A user who wants to retrieve information directly from a data source, such as an Enterprise Resource Planning (ERP) database, faces several significant challenges:

  • The contents of such data sources are frequently very hard to understand, being designed with systems and developers instead of users in mind.
  • Information of interest to the user is typically distributed among multiple heterogeneous data sources. Even if dealing only with different relational databases, the user must understand the details of each, such as the dialect of SQL that is used. Worse, those data sources might be of very different types, including not only relational databases but files and Web services.
  • Whereas many data sources are oriented toward holding large quantities of transaction level detail, frequently the queries that support business decision-making involve summary, aggregated information. With increased data volumes, the time that is required to retrieve such summary values for interactive end-user analysis can be prohibitive.
  • Business rules are generally not encapsulated in the data sources. Users are left to make their own interpretation of the data.

The role of a Unified Dimensional Model (UDM) is to provide a bridge between the user and the data sources. A UDM is constructed over one or more physical data sources. The user issues queries against the UDM using a variety of client tools, such as Microsoft Excel.

Clients access all data sources through single UDM

There are advantages to the end user even when the UDM is constructed only as a thin layer over the data source: simpler, more easily understood model of the data, isolation from heterogeneous backend data sources, and improved performance for summary type queries. In some scenarios, a simple UDM can be constructed automatically. Greater investment in the construction of the UDM can generate additional benefits that accrue from the richness of metadata that the model can provide.

The UDM provides the following benefits:

  • Greatly enriches the user model.
  • Provides high performance queries supporting interactive analysis, even over large data volumes.
  • Captures business rules in the model to support richer analysis.
  • Supports ‘closing the loop’: letting users can act upon the data they see.

Monday, February 19, 2007

Cool Snow in Bear Valley
This is a Flickr badge showing photos in a set called Bear Valley Feb 2007. Make your own badge here.
We just got back from a three hour drive from Angel's Camp where we stayed for 2 nights. Bear Valley was just an hour away from where we stayed. Driving to highway 4 going to north, one would not think that there's lots of snow once you get on the top of the mountains. Bear Valley is so nice, we particularly enjoyed the not so angled slopes and vast stretch of snow, well suited for our 3 and 4 year old kids. My son who's 9 years old, especially enjoyed the 2 sledding hills. He got to the hills countless times tirelessly. I asked my daughter if she really enjoyed it, she said "Yes" and what she enjoyed the most, well "making snowballs".

Thursday, February 15, 2007

Five Reasons Why I Sleep Past Midnight Everyday

This is in response to a contest called "Five Things" hosted by one of my favorite blogs //engtech. So without further ado, here's my "five things":

Five Reasons Why I Sleep Past Midnight Everyday:
  1. So I can do things after putting my children to bed.
  2. So I can catch up on my reading and study materials.
  3. So I can surf the net.
  4. So I can have quiet time for myself.
  5. I have Insomnia, which is probably the result of the first 4 reasons mentioned.

Now that I'm thinking about it, the result could also be the reason why I do the first 4. It's a cycle you know, it's a never ending thing.

Wednesday, February 14, 2007

Kill Processes in Windows using Perl

When doing install automation, sometimes, it is necessary to disable some processes before proceeding. This can be done easily using perl's kill() function. But sometimes some processes are hard to kill, so what I have been using is this script that I borrowed from Dave Roth in his book Win32 Perl Scripting.

Here's how I do it:

use SamTools::Process;

#Kill Anti-Spywares

sub kill_process {
    my $process = shift;
    my $result = 0;

    my $pid = SamTools::Process::getpid($process);

    if ($pid){
        if (SamTools::Process::kill_pid($pid)){
            $log->message("Process=Successfully killed $process\n");
            $log->exception("Process=Cannot kill $process\n");
        $log->message("Process=$process not found\n");

I modularized it below:

package SamTools::Process;

use Win32::PerfLib;
use Win32::API;
use Win32::Process::Info;

sub getpid {
    my $process = shift;

    my $server = $ENV{COMPUTERNAME};
    my $pid;

    Win32::PerfLib::GetCounterNames($server, \%counter);
    %r_counter = map { $counter{$_} => $_ } keys %counter;
    $process_obj = $r_counter{Process};
    $process_id = $r_counter{'ID Process'};
    $perflib = new Win32::PerfLib($server) || return 0;
    $proc_ref = {};
    $perflib->GetObjectList($process_obj, $proc_ref);
    $instance_ref = $proc_ref->{Objects}->{$process_obj}->{Instances};
    foreach $p (sort keys %{$instance_ref}){
        $counter_ref = $instance_ref->{$p}->{Counters};
        foreach $i (keys %{$counter_ref}){
            if($counter_ref->{$i}->{CounterNameTitleIndex} ==
               $process_id && $instance_ref->{$p}->{Name} eq $process){
                $pid = $counter_ref->{$i}->{Counter};

    #try again using a different approach WMI
    unless ($pid){
        if (my $pi = Win32::Process::Info->new($server)){
            my $processes = $pi->GetProcInfo();
            my $number = @$processes;
            foreach (@$processes){
                if ($_->{Name} =~ /$process/i){
                    $pid = $_->{ProcessId};

    $pid?return $pid:return 0;

sub pidalive {
    my $pid = shift;
    my $server = $ENV{COMPUTERNAME};

    Win32::PerfLib::GetCounterNames($server, \%counter);
    %r_counter = map { $counter{$_} => $_ } keys %counter;
    $process_obj = $r_counter{Process};
    $process_id = $r_counter{'ID Process'};
    $perflib = new Win32::PerfLib($server) || return 0;
    $proc_ref = {};
    $perflib->GetObjectList($process_obj, $proc_ref);
    $instance_ref = $proc_ref->{Objects}->{$process_obj}->{Instances};
    foreach $p (sort keys %{$instance_ref}){
        $counter_ref = $instance_ref->{$p}->{Counters};
        foreach $i (keys %{$counter_ref}){
            if ($counter_ref->{$i}->{Counter} == $pid){
                return $pid;
    return 0;

sub kill {
    my $process = shift;
    my $pid = getpid($process);
    if ($pid){
        $iResult = ForceKill( $pid );
        return 1 if( $iResult );
    return 0;

sub kill_pid {
    my $pid = shift;
    $iResult = ForceKill( $pid );
    return 1 if( $iResult );
    return 0;

sub ForceKill {
    my( $Pid ) = @_;
    my $iResult = 0;
    my $phToken = pack( "L", 0 );
    # Fetch the process's token
                               $TOKEN_ADJUST_PRIVILEGES | $TOKEN_QUERY,
                               $phToken )){
        my $hToken = unpack( "L", $phToken );
        # Set the debug privilege on the token
        if( SetPrivilege( $hToken, $SE_DEBUG_NAME, 1 ) ){
            # Now that we have debug privileges on the process
            # open the process so we can mess with it.
            my $hProcess = $OpenProcess->Call( $PROCESS_TERMINATE, 0, $Pid );
            if( $hProcess ){
                # We no longer need the debug privilege since we have opened
                # the process so remove the privilege.
                SetPrivilege( $hToken, $SE_DEBUG_NAME, 0 );
                # Let's termiante the process
                $iResult = $TerminateProcess->Call( $hProcess, 0 );
                $CloseHandle->Call( $hProcess );
        $CloseHandle->Call( $hToken );
    return $iResult;

sub SetPrivilege {
    my( $hToken, $pszPriv, $bSetFlag ) = @_;
    my $pLuid = pack( "Ll", 0, 0 );
    # Lookup the LIUD of the privilege
    if( $LookupPrivilegeValue->Call( "\x00\x00", $pszPriv, $pLuid ) ){
        # Unpack the LUID
        my $pPrivStruct = pack( "LLlL",
                               unpack( "Ll", $pLuid ),
                               ( ( $bSetFlag )? $SE_PRIVILEGE_ENABLED : 0 )
        # Now modify the process's token to set the required privilege
        $iResult = ( 0 != $AdjustTokenPrivileges->Call( $hToken,
                                                       length( $pPrivStruct ),
                                                       0 )

    return $iResult;

sub Configure {
    $TOKEN_QUERY             = 0x0008;
    $SE_PRIVILEGE_ENABLED    = 0x02;
    $PROCESS_TERMINATE       = 0x0001;
    $SE_DEBUG_NAME           = "SeDebugPrivilege";

    # Prepare to use some specialized Win32 API calls
    $GetCurrentProcess     = new Win32::API( 'Kernel32.dll',
                                             N ) || die;
    $OpenProcessToken      = new Win32::API( 'AdvApi32.dll',
                                             [N,N,P], I ) || die;
    $LookupPrivilegeValue  = new Win32::API( 'AdvApi32.dll',
                                             [P,P,P], I ) || die;
    $AdjustTokenPrivileges = new Win32::API( 'AdvApi32.dll',
                                             I ) || die;
    $OpenProcess           = new Win32::API( 'Kernel32.dll',
                                             N ) || die;
    $TerminateProcess      = new Win32::API( 'Kernel32.dll',
                                             I ) || die;
    $CloseHandle           = new Win32::API( 'Kernel32.dll',
                                             I ) || die;



=head1 NAME

SamTools::Process - Sam's Process controllers.


    use SamTools::Process;


This module controls processes on Win32 Systems.  Currently, only has killing capability.

use SamTools::Process;

=head1 kill


Tuesday, February 13, 2007

Data Warehousing Concepts

I'm reading a book on Data Warehousing. This is in preparation for a project that I'm involved in. The project is to build a global reporting solution for IT using SQL Server 2005 Reporting Services. Here are some key words I want to remember about data warehousing:

This termMeans this
AttributeInformation about a specific dimension member
Data warehouseA relational database designed to store management information
DimensionA list of labels that can be used to cross-tabulate values from other dimensions
Fact tableThe relational database table that contains values for one or more measures at the lowest level of detail for one or more dimensions
Foreign key columnA column in a database table that contains many values for each value in the primary key column of another database table
JoinThe processes of linking the primary key of one table to the foreign key of another table
MeasureA summarizable numerical value used to monitor business activity
MemberA single item within a dimension
Member propertyAn attribute of a member that is not meaningful when grouping values for a report, but contains valuable information about a different attribute
Primary key columnA column in a database dimension table that contains values that uniquely identify each row
Snowflake designA database arrangement in which attributes of a dimension are stored in a separate (normalized) table
Star designA database arrangement in which multiple attributes of a dimension are redundantly stored in a single (denormalized) dimension table

Design considerations:

  • Use integers for dimension members to reduce size of the fact table and to gaurantee uniqueness of each member key in a dimension table.
  • Use surrogate keys so that the fact table consumes less space. The ability to create multiple instances of the same product–or the same customer–is an extremely important benefit of surrogate keys, and it is particularly important in a data warehouse where you are maintaining historical information for comparison. Surrogate keys are a critical part of most data warehouse design. The foreign key in the fact table and the primary key in the dimension table are then completely under the control of the data warehouse.
  • Values in a data warehouse are not changing as dynamically as they would in a production database, so storing the values redundantly is less important than is retrieving the values as quickly as possible for a report.

Understanding OLAP and Analysis Services:

This termMeans this
AggregationSummarized values of a measure
CacheServer-based storage locations both in memory (automatic) or on disk (designed) that enhance query performance
Calculated memberA mechanism for aggregating measures using formulas more complex than those stored in a cube
CubeA collection of one or more related measure groups and their associated dimensions
Cube metadataInstructions for creating and querying OLAP structures such as cubes and dimensions
HierarchyLevels of aggregation within a single dimension
Measure groupThe conceptual container of detail values from a single fact table, along with all possible aggregations for one or more dimension hierarchies
Online analytical processing (OLAP)A database system optimized to support decision-making processes
Online transaction processing (OLTP)A database system used to manage transactions such as order processing
Unified Dimensional Model (UDM)The measure groups and dimensions that define your organization's BI data; essentially synonymous with a cube

Benefit of an OLAP cube over a relational database:

  1. Consistently fast response - prestoring calculated values.
  2. Metadata-based queries
  3. Spreadsheet-style formulas

Design considerations:

  • Use Database model OLAP instead of Spreadsheet model OLAP. Advantages and disadvantages, the biggest benefit of OLAP stored using the database model is the ability to avoid data explosion. Because you need relatively few aggregate tables to provide fast results, you can have much larger cubes with many more dimensions and attributes than by using a spreadsheet model. Perhaps the biggest disadvantage of OLAP stored by using a database model is that there is no inherent way to physically store values that are calculated using nonassociative operators.

Thursday, February 08, 2007

Progress GUI in perl using Win32::GUI

Did you ever need a progress GUI injected in your program? I did, and I still do. A progress GUI is something that is nice to have to show how a long running program is progressing. You don't want a user to think that your program is stuck. Last thing you want to happen is the user doing Ctrl+Alt+Delete then End Task. And so my solution is to display something like this:

Here's how my module is used:

use SamTools::ProgressGUI;

my $GUI = SamTools::ProgressGUI->new({
           Title       => 'Organization Name - Install Status',
           ProgressBar => 1,

$GUI->show("Installing Software...");

foreach (1..10) {
    sleep 1;


Here's the module. I will probably contribute this to CPAN, just need to find time to prepare it, but here you go, enjoy...

#!/usr/bin/perl -w
#   This is module for displaying progress GUI
#   Written By: Sam Dela Cruz
#   Date      : 05/18/2006
#   Version   : 1.00

package SamTools::ProgressGUI;

use strict;
use Win32::GUI;
use Win32::GUI::BitmapInline;

#   Constructor
sub new {
    my $proto  = shift;
    my $class  = ref($proto) || $proto;

    my $self = {};
    bless($self, $class);

    #initialize object
    return $self->_init(@_);

#   Initialize object
sub _init {
    my $self = shift;

    # default values
    $self->{'title'}        = 'Install Progress';
    $self->{'progressbar'}  = 0;

    # Have we been passed anything
    if (@_ != 0){
        # We are expecting our configuration to come as an anonymous hash
        if (ref $_[0] eq 'HASH'){
            my $hash=$_[0];
            foreach my $key (keys %$hash){
        else {
            ( $self->{'title'}, $self->{'progressbar'} ) = @_;
    #create the GUI
    return $self->_createGUI;

#   Create the GUI
sub _createGUI {
    my $self = shift;

    # Program Icon
    $self->{'icon'} = newIcon Win32::GUI::BitmapInline( q(
    ) );

    # Start Pop-Up Window GUI
    $self->{'screen_width'}  = Win32::GUI::GetSystemMetrics(0);
    $self->{'screen_height'} = Win32::GUI::GetSystemMetrics(1);
    $self->{'font1'}         = new GUI::Font(-name=>"Tahoma",
    $self->{'font2'}         = new GUI::Font(-name=>"Tahoma",
    $self->{'font3'}         = new GUI::Font(-name=>"Tahoma",
    $self->{'font4'}         = new GUI::Font(-name=>"Tahoma",

    $self->{'Popup'} = new GUI::DialogBox(
                       -name   => "Popup",
                       -text   => $self->{'title'},
                       -size   => [400, 150],
                       -pos    => [(($self->{'screen_width'}/2)-200),
                       -font   => $self->{'font1'},
                       -helpbutton => 0,

    my $wc = $self->{'Popup'}->Width();


    $self->{'Popup'}->AddLabel( -name    => "PopupLabel",
                                -text    => "",
                                -top     => 20,
                                -left    => 20,
                                -height  => 80,
                                -width   => $wc-40,
                                -font    => $self->{'font1'},

    #display progress bar if specified in object creation
    if ($self->{'progressbar'}) {
        $self->{'Popup'}->AddProgressBar( -name   => "ProgressBar",
                                          -width  => $wc-40,
                                          -height => 10,
                                          -top    => 80,
                                          -left   => 20,
                                          -smooth => 1,

        $self->{'Popup'}->ProgressBar->SetPos (0);
        $self->{'Popup'}->ProgressBar->SetStep (1);


    return $self;

#   Methods
sub show {
    my $self = shift;
    my $message = shift;

sub step {
    my $self = shift;
    $self->{'Popup'}->ProgressBar->StepIt() if ($self->{'progressbar'});

sub hide {
    my $self = shift;
    if ($self->{'progressbar'}){
        sleep 1; # give chance to show completion of progress bar



=head1 NAME

 SamTools::ProgressGUI - Sam's Simple Progress GUI


 use SamTools::ProgressGUI;

 # Pretty format, all the parameters
 my $GUI = SamTools::ProgressGUI->new({
            Title       => 'Organization Name - Install Status',   # define the window title
            ProgressBar => 1,                                      # display progress bar

 # Typical usage
 my $GUI = SamTools::ProgressGUI->new({
            Title       => 'Organization Name - Install Status',
            ProgressBar => 1,

 # Display status window with the message
 $GUI->show("Installing Software...");

 # Move the progress bar

 # Hide the status GUI


I is a class providing methods to display a progress GUI.  There are a number
of parameters that can be passed to allow configuration of the GUI.



=head1 METHODS

There are no class methods, the object methods are described below.
Private class method start with the underscore character '_' and
should be treated as I.

=head2 new

Called to create a I object.  The following optional named parameters can be
passed to the constructor via an anonymous hash:

=over 4

=item Title

Defines the window title.  If not defined in the
constructor then it will use the default "Philips NA IT - Install Progress".

=item ProgressBar

Used to determine if a progress bar is needed.
Default is none.


=head2 _init & Private methods

I method to initialise the object on construction.  Called by C.
All I methods start with B<_> and should be treated as PRIVATE.  No other
private methods are documented (since they are private).

=head2 show

The C method is used to show the Progress GUI with message specified.

eg.  $GUI->show("Installing Software...");

=head2 step

C is used to step the progress bar if progress bar is turned on.

=head2 hide

C is used to hide the GUI.



=head1 AUTHOR

Sam Dela Cruz, sammydc at gmail dot com

=head1 LICENSE

Copyright (c) 2006 - Sam Dela Cruz. All rights reserved. This
program is free software; you can redistribute it and/or modify it under
the same terms as Perl itself.


Tuesday, February 06, 2007

Perl Best Practices

I always wanted to tidy up my perl code. But most of the time, because of the nature of work and tasks, I don't need to worry about this because my programs are mostly throw-away scripts, write and use once. But through the years that I'm programming in perl, I have accumulated a bunch of snippets here and there that are really worthy of reuse. Problem is when I look at them again, I need to re-read and re-understand my code again. Something I left out before is naming and layout convention. So with the purpose of rewriting and improving readability, consistency and maintainability of my codes, I set to follow some standards. I found this wonderful book called Perl Best Practices. It outlines coding layouts, naming conventions and well, best practices, perl style. Below are my notes for Naming Conventions taken from thisbook by Damian Conway.


# Packages and Classes
# Noun::Adjective::Adjective
package Disk;
package Disk::DVD;
use base qw( Disk );
package Disk::DVD::Rewritable;
use base qw( Disk::DVD );

# Variables
# noun, preceded by zero or more adjectives
my $next_client;
my $prev_appointment;
my $estimated_net_worth;
my $final_total;
my $cumulative_total;

# Hashes and Arrays for look-up tables
# noun, preceded by zero or more adjectives, then preposition
my %title_of;
my %ISBN_for;
my @sales_from;
# and later...
while (my $month = prompt -menu => $MONTH_NAMES) {
    for my $book ( @catalog ) {
        print "$ISBN_for{$book} $title_of{$book}: $sales_from[$month]\n";

# Subroutines and Methods

# imperative_verb noun
sub get_record;                      

# imperative_verb noun preposition
sub get_record_for;                  

# imperative_verb noun
sub eat_cookie;                      

# imperative_verb adjective noun
sub eat_previous_cookie;              

# imperative_verb noun
sub build_profile;

# imperative_verb adjective noun
sub build_execution_profile;

# imperative_verb adjective noun participle
sub build_execution_profile_using;

# Notice how clear this is:
@config_options = get_record_for($next_client);
for my $option (@config_options) {
    build_execution_profile_using($next_client, $option);

# Booleans
# begin with is_ or has_, but not always

sub is_valid;
sub metadata_available_for;
sub has_end_tag;

my $loading_finished;
my $has_found_bad_record;

# and later...

if (is_valid($next_record) && !$loading_finished) {
    while (metadata_available_for($next_record)) {
        push @metadata, get_metadata_for($next_record);
        last METADATA if has_end_tag($next_record);
else {
    $has_found_bad_record = 1;

# Reference Variables
# suffix _ref to any variable that is supposed to store a reference
sub pad_str {
    my ($text, $opts_ref) = @_;

    my $gap   = $opts_ref{cols} - length $text;
    my $left  = $opts_ref{centred} ? int($gap/2) : 0;
    my $right = $gap - $left;

    return $SPACE x $left . $text . $SPACE x $right;

# Arrays and Hashes
# Name arrays in the plural and hashes in the singular

my %option;
my %title_of;
my %count_for;
my %is_available;

# and later...
if ($option{'count_all'} && $title_of{$next_book} =~ m/$target/xms) {
    $is_available{$next_book} = 1;

my @events;
my @handlers;
my @unknowns;

# and later...

for my $event (@events) {
    push @unknowns, grep { ! $_->handle($event) } @handlers;

print map { $_->err_msg } @unknowns;

# but arrays as random-access look-up table, should be singular

my @factorial = (1);
for my $n (1..$MAX_FACT) {
    $factorial[$n] = $n * $factorial[$n-1];

sub factorial {
    my ($n) = @_;

    croak "Can't compute factorial($n)"
        if $n < 0 || $n > $MAX_FACT;

    return $factorial[$n];

# Underscores
# Use underscores to separate words in multiword identifiers

for my $tax_form (@tax_form_sequence) {
    my $notional_tax_paid
        = $tax_form->{reported_income} * $tax_form->{effective_tax_rate};

    next FORM if $notional_tax_paid  < $MIN_ASSESSABLE;

        += $notional_tax_paid - $tax_form->{allowed_deductions};

# Capitalization
# Distinguish different program components by case
# Use lowercase only for the names of subroutines,
# methods, variables, and labeled arguments
# $controller, new( ), src=>$fh

# Use mixed-case for package and class names
# IO::Controller

# Use uppercase for constants

my $controller
        = IO::Controller->new(src=>$fh,  mode=>$SRC|$NODE);

# Abbreviations
# retain start of each word
# This example is easily comprehended:

use List::Util qw( max );

for my $desc (@orig_strs) {
    my $len = length $desc;
    next DESC if $len > $UPPER_LIM;
    $max_len = max($max_len, $len);

# Utility Subroutines
# Prefix "for internal use only" subroutines with an underscore
sub _find_fib {
    my ($n) = @_;

# Walk up cache from last known value, applying Fn = Fn-1 + Fn-2...

    for my $i (@fib_for..$n) {
        $fib_for[$i] = $fib_for[$i-1] + $fib_for[$i-2];