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

Brethren, I have an application where Perl is acting as a client to a server. The server returns data from a serial port in the form of one of about 20 different hashes. Because of speed considerations, I have to sit in a tight loop and continually check for the presence of new data (until a timeout), and quickly store the data away for post processing. Here is my problem: I need to preserve the key/value information from the hashes, and I cannot predict which hash will be read next. Ideally, I would like to be able to "malloc" a chunk of memory, cast it as an anonymous hash, load the data into it, and push the reference onto an array. Once I have finished reading, I would shift the reference off (to preserve chronological order), derefenence the hash, decide which type of hash it is based on the keys, and do my post-processing. So far I have not been successful in preserving the "hashness" of the data. Any wisdom would be greatly appreciated.

Replies are listed 'Best First'.
Re: Creating an array of hash references
by Tanktalus (Canon) on Mar 17, 2005 at 04:26 UTC

    It's not entirely clear to me where the problem is - what you said you want to do sounds reasonable to me, so I'm guessing that I'm missing where the problem is.

    Perhaps some code would help?

    What is the data coming across the wire - how do you know beginning and end of the hash?

    my $context; my @list; while (1) { my @data = get_next_key_and_value(); if (@data) { # got an extra key/value for current hash. $context->{$data[0]} = $data[1]; } else { # end of current hash. push @list, $context; $context = undef; } }

    Something like that may help? Hard to tell.

Re: Creating an array of hash references
by graff (Chancellor) on Mar 17, 2005 at 04:43 UTC
    Maybe I'm not understanding what you mean... If that the server and client are separate processes, then you aren't really passing a hash from one to the other; you're passing a stream of bytes, which need to be split or parsed somehow into a hash (a series of key/value pairs) in the client.

    Does the client receive a sequence of distinct hashes in a single stream? If so, how do you know the boundaries between hashes?

    But the main question is: how do you know when you are finished reading? I would tend to use a "read()" call in this situation, or maybe sysread, and maybe play with ioctl's or fcntl's in order to do non-blocking reads from the server. Put all the input data into a scalar in the client, and after the reading is done (and there's time to process it), use unpack or split or whatever to break the stream up into the hash(es).

Re: Creating an array of hash references
by perlfan (Parson) on Mar 17, 2005 at 06:11 UTC
    Sounds like you have to store the data directly, then munge through it to extract the key/value pair at a later point.
    my @data = (); my @final = (); while (!$client->timeout()) { my $stream = $client->get_data(); push(@data,$stream); } foreach (@data) { push(@final,munge($_)); #<-- push hash ref onto @final } sub munge { my %hash = (); #...create hash return \%hash; #<-- hash ref }
      Thanks to all for the replies. I see now that I did not make my problem clear enough. My script is accessing a company-proprietary package that interfaces with the server. The "Read()" routine from the package returns a hash. Depending on the event being read, the hash will have a different set of key/value pairs, but each hash contains an ID key/value pair so that the calling code can process the data correctly:
      my %event = SECRET_PACKAGE_NAME::WaitForEvent($timeout);
      My script is initiating an action which causes the server to spit out data at a much higher rate than normal. Originally I was reading a single hash and processing it real-time. Running some external diagnostics showed that I was missing some key data, so I decided to run a tight loop to collect ALL the data. I tried the following which didn't work:
      my @Events; while(1) { my %event = (); eval { %event = SECRET_PACKAGE_NAME::WaitForEvent($timeout); }; if($@) { # timeout occurred, serial burst complete last; } else { push (@Events, \%event); } }# end while ... while(@Events) { # Get the first event, NOT the last one my $EventRef = shift(@Events); } # $EventRef points to garbage
      Does this clarify my issue? I need to preserve all the key/value pairs of the received data somehow so that I can post-process the information. Thanks again for the responses

        Nothing looks wrong here. I mean, I would clean it up differently, but functionally, it seems ok.

        use Data::Dumper; # ... my @Events; while (1) { eval { my %event = SECRET_PACKAGE_NAME::WaitForEvent($timeout); push @Events, \%event; print LOG Dumper(\%event); }; last if $@; } print LOG "=======\n", Dumper(\@Events);
        Take the dumper lines out once you're convinced everything is working fine. But I would be extremely interested in whether there is a difference here or not. I expect no difference, and thus the problem is that the WaitForEvent sub is returning garbage (or returning something that isn't really a hash list).

        That's just my gut feel on it...

        update: As pointed out below by Tanktalus, the information in this reply is basically wrong. Please ignore it.

        I think what's wrong there is that you're pushing the same reference to the same hash over and over onto your array, and also clearing and refilling that hash on each iteration.

        What you probably want to do instead is push a reference to an anonymous copy of "%event":

        while(1) { my %event = (); eval { %event = SECRET_PACKAGE_NAME::WaitForEvent($timeout); }; if($@) { # timeout occurred, serial burst complete last; } else { push (@Events, { %event }); } }# end while ...
        (I could be wrong, but I think this is worth a try.)
Re: Creating an array of hash references
by lidden (Curate) on Mar 17, 2005 at 05:12 UTC
    Creating an array of hash referenses is easy, if I understand you correctly you want something like this:
    my @array; while( my $nr = int rand 15){ if ( $nr > 5){ my %tmp = ( 'large' => 1, 'value' => $nr, ); push @array, \%tmp; } else{ my %tmp = ( 'small' => 1, 'value' => $nr, ); push @array, \%tmp; } } while (@array){ my $ref = shift @array; print "large: $ref->{'value'}\n" if $ref->{'large'}; }