perlMunger has asked for the wisdom of the Perl Monks concerning the following question:

Hi perl gurus,

I have a delima that I'm trying to solve, but can't seem find the answer. Maybe someone has already solved this before.

My delima is: I'm trying to push a string onto an array only if it doesn't already exist in the array. I know that I could probably use a hash instead, but don't want to use a hash (key = value), since the string is just a directory path that I'm storing for future use.

some code that I've tried:

push(@mounts, "$directory/$entry") unless (exists($mounts["$directory/ +$entry"]));
returns a warning/error: Argument "$directory/$entry" isn't numeric in exists at scriptname line #.
push(@mounts, "$directory/$entry") if (!(exists($mounts["$directory/$ +entry"])));
returns the same warning/error.

What, if any, is the syntax for testing if a string already exists in the @array, before pushing it onto the @array?

TIA

Mitch

Replies are listed 'Best First'.
Re: Testing for a string value within an array
by revdiablo (Prior) on May 03, 2005 at 22:41 UTC
    I know that I could probably use a hash instead, but don't want to use a hash

    The only time a hash is not acceptible for something like this, is when you want to maintain the original insertion order. If you don't, then follow tlm's advice and use that.

    If, however, you need to maintain the original order, the easiest solution is to use both a hash and an array. You may be thinking, "this sounds like a real pain." But it's not. Here's a demonstration:

    my %mountpoints; my @mountpoints; for (qw(foo bar bar foo baz)) { push @mountpoints, $_ unless $mountpoints{$_}++; }
      If you need to keep the original order, is there any overhead to using Tie:LLHash over using the array? I'm guessing that Tie::LLHash must be using an array, but just provides an interface to work with only the hash?
        is there any overhead to using Tie:LLHash over using the array?

        It depends on what you mean by "overhead". Certainly, there's overhead involved in loading a module. There's overhead involved in installing a module. Depending on how the module is implemented, there's likely overhead involved in using the module. Whether that overhead is worth it or not depends on a lot of factors. But personally, I would stick with a plain dual hash/array approach unless there was a specific reason not to.

Re: Testing for a string value within an array
by tlm (Prior) on May 03, 2005 at 22:37 UTC

    Hashes are your friends! Perl programs often use them for precisely what you want to do. Just do this

    $mounts{ "$directory/$entry" }++;
    or this
    $mounts{ "$directory/$entry" } = 1;
    And when you're ready to retrieve the entries:
    my @mounts = keys %mounts;

    the lowliest monk

Re: Testing for a string value within an array
by Transient (Hermit) on May 03, 2005 at 22:35 UTC
    Try grep.

    You're receiving the errors because you're trying to access an array index using a string. You are also using exists, which is a hash function
Re: Testing for a string value within an array
by Limbic~Region (Chancellor) on May 03, 2005 at 23:32 UTC
    perlMunger,
    If you have what you believe to be a common question, chances are it has been asked. Try to use Super Search first to see if someone has already solved your problem. In this case, I offered this solution when it was asked at Adding Unique Elements to Array

    Incidently, if you really don't want to use a hash you will be able to do the following in Perl6 which is hiding the search under the covers.

    push @array, $value if $value eq none( @array );

    Cheers - L~R

Re: Testing for a string value within an array
by Roy Johnson (Monsignor) on May 03, 2005 at 23:16 UTC
    Here's a quick hash-as-unique-array technique:
    my %hash; for (qw(first second first third second fourth)) { exists $hash{$_} or $hash{$_} = keys %hash; # Put stuff in like t +his } print "$_\n" for sort {$hash{$a} <=> $hash{$b}} keys %hash; # Get stuf +f out like this

    Caution: Contents may have been coded under pressure.
Re: Testing for a string value within an array
by scmason (Monk) on May 03, 2005 at 23:38 UTC
    What's wrong with making your very own exists function? It is quick, readable and configurable:

    I would write it like this:

    $filename = "$directory/$entry"; if( myExists( $filename, @mounts ) == 0 ){ push(@mounts, $filename); } sub myExists(){ my $compare = shift; foreach my $element (@_){ if( $element eq $compare ){ return 1; } } return 0; }

    I know that this is more code than some of the previous solutions, but it has advantages. It is more readable and can allow you to more precisely define what it means to exist. Say for instance that you wanted to only add it if it were unique in the first 256 charachters, or alter it otherwise, this would be easy to do. In addition, it is also more resource friendly and less confusing than maintaining both arrays and hashes.

    Good luck

      The problem is that you have to search the whole array every time you insert, making insertion an O(N) operation. With a hash, the lookup is essentially constant-time. Building an array becomes O(N^2), like bubblesort (which is also easy to read an understand, and which nobody uses).

      You can use a hash in all the same ways of defining existence. If you only want unique in the first 256 chars, only hash those.


      Caution: Contents may have been coded under pressure.

      It also does a full linear search of the entire list of items (which has to be copied onto the stack for each myExists call) if the element isn't already present. Replacing an O(N)-ish exists $seen{$key} with something exponential doesn't sound like it's worth any supposed simplification.

      Not wanting to use a hash for keeping track of set membership in Perl is in the same vein as people who want to do iterative-task-foo without using foreach or map. Learning the idioms will make your life much easier.

Re: Testing for a string value within an array
by perlMunger (Novice) on May 04, 2005 at 15:01 UTC
    Thanks to all for the replies. All of the examples and suggestions will help me tremendously. I really appericate the help. I know that hashes are my friend (usually use them all the time) and will most likely use $mounts{$directory/$entry}++ to test for uniqueness. I just thought there might be a way to test for uniqueness within an array without doing a for loop or some other magic. Anyway, thanks again for the help.. Mitch (AKA: PerlMunger)