Search code examples
arraysperlreferencevariable-assignmentsubroutine

perl array not populating, despite hours of tinkering


Um...I have the following code snippet, and was wondering why the second subroutine, search($$) fails to yield results...while the first routine, search_item($$$), performs admirably (imo).

########################
# generate and return a list of users which match only
# one criteria (eg: eyes=2)
#
# $users is a reference to an array of 6-digit hexidecimal user IDs (eg: 000001, 000002, etc)
# $name is the name of the key (or field) to find (eg: 'eyes')
# $value (eg: 2) is compared with the value stored in the key
# if $value matches what's in the $name'd key, then add the uid to a list

sub search_item($$$) {
  my ($users, $name, $value) = @_;
  my @searched;
  foreach my $uid (@$users) {
    my %ustats = user::getUserStats($uid);
    if ($ustats{$name} eq $value) { push @searched, $uid; }
  }

  return @searched;
}

########################
# generate and return a list of users which match
# many criteria (eg: eyes=2, hair=1, etc)
#
# $users is a reference to an array of user IDs (eg: 000001, 000002, etc)
# $terms is a reference to an array of search terms (eg: $terms[0] = "eyes=2";)
sub search($$) {
  my $users = $_[0]; # an array reference
  my $terms = $_[1]; # an array reference
  my @searched;
  my $first = 1;

  foreach my $term (@$terms) {
    # since @$terms is an array of scalars, in the format of 'name=value' pairs
    my $name = $term; $name =~ s/=(.)*//;
    my $value = $term; $value =~ s/$name=//;

    if ($first) {
      # search the given list reference ($users)
      @searched = search_item($users, $name, $value);
      $first = 0; # set to 0 cause now we gotta use @searched
    } else {
      # otherwise use a reference to @searched
      @searched = search_item(\@searched, $name, $value);
    }
  }

  return @searched;
}

i have setup the data so the code should return 1 hit. the data is correct and underlying functions (eg: getUserStats($)) also perform flawlessly.

both user 000001 and 969696 have eyes=2 all others eyes=1 and user ID 000001 is gender=1, all others gender=0

so...if i write:

my @users = getUsers();
foreach my $uid (search_item(\@users, 'eyes', 2)) {
  print "$uid<br>\n";
}

i get 2 hits of a total of 6 users in my database (this IS a correct result, of course). satisfied with those results, I run the search routine.

my @terms = ('eyes=2', 'gender=1'); # gender=0 is a boy.  1 is a girl
my @sResults = search(\@users, \@terms);
if (@sResults) {
  foreach my $uid (@sResults) {
    print "$uid<br>\n";
  }
} else {
  print "nothing found!<br>\n";
}

i always see "nothing found!" when i pray and hope to see "000001" instead... :(

this seems like legit code....so....whud am i doin wrong guys??? am i not derefencing something correctly? or...is the dereferencing / referencing the source of my dilema? i loath pointers...however incredibly useful :p


Solution

  • Your code does actually work if you pass correct parameters. My best guess is that the strings like eyes=2 that you're using contain spurious whitespace such as a trailing newline

    Here's the test program that I used to work on your subrouitines

    use strict;
    use warnings;
    use 5.010;
    
    my %users = (
        '000001' => { eyes => 2, gender => 1 },
        '000002' => { eyes => 1, gender => 0 },
        '000003' => { eyes => 1, gender => 0 },
        '000004' => { eyes => 1, gender => 0 },
        '969696' => { eyes => 2, gender => 0 },
    );
    
    sub user::getUserStats {
    
        my ( $uid ) = @_;
    
        %{ $users{$uid} };
    }
    
    ########################
    # generate and return a list of users which match only
    # one criteria (eg: eyes=2)
    #
    # $users is a reference to an array of 6-digit hexidecimal user IDs (eg: 000001, 000002, etc)
    # $name is the name of the key (or field) to find (eg: 'eyes')
    # $value (eg: 2) is compared with the value stored in the key
    # if $value matches what's in the $name'd key, then add the uid to a list
    
    sub search_item($$$) {
        my ( $users, $name, $value ) = @_;
        my @searched;
        foreach my $uid ( @$users ) {
            my %ustats = user::getUserStats( $uid );
            if ( $ustats{$name} eq $value ) { push @searched, $uid; }
        }
    
        return @searched;
    }
    
    ########################
    # generate and return a list of users which match
    # many criteria (eg: eyes=2, hair=1, etc)
    #
    # $users is a reference to an array of user IDs (eg: 000001, 000002, etc)
    # $terms is a reference to an array of search terms (eg: $terms[0] = "eyes=2";)
    sub search($$) {
        my $users = $_[0];    # an array reference
        my $terms = $_[1];    # an array reference
        my @searched;
        my $first = 1;
    
        foreach my $term ( @$terms ) {
            # since @$terms is an array of scalars, in the format of 'name=value' pairs
            my $name = $term;
            $name =~ s/=(.)*//;
            my $value = $term;
            $value =~ s/$name=//;
    
            if ( $first ) {
                # search the given list reference ($users)
                @searched = search_item( $users, $name, $value );
                $first = 0;    # set to 0 cause now we gotta use @searched
            }
            else {
                # otherwise use a reference to @searched
                @searched = search_item( \@searched, $name, $value );
            }
        }
    
        return @searched;
    }
    
    my $users = [ keys %users ];
    
    say for search( $users, [ 'eyes=2', 'gender=1' ] );
    

    output

    000001
    

    Here's how I would write similar subroutines that behave identically and take the same parameters, but there is a lot in the design of this application that is less that optimal

    sub search_item {
        my ( $users, $name, $value ) = @_;
    
        grep {
            my %ustats = user::getUserStats( $_ );
            $ustats{$name} eq $value;
        } @$users;
    }
    
    sub search {
        my ($users, $terms) = @_;
        my @searched;
    
        for my $term ( @$terms ) {
            my ($name, $value) = split /=/, $term;
            @searched = search_item( $users, $name, $value );
            $users = \@searched;
        }
    
        @searched;
    }
    

    but I think user::getUserStats should be called User::get_user_stats (because Perl reserves capital letters for global identifiers such as package names) and it should return a reference to a hash instead of just a list