Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Detecting element types in a mixed array

Im working with some code that has a subroutine which includes an array reference as one of the parameters. The elements in this incoming array can be either small arrays or strings.

I want to determine what type each element is in order to do something specific (i.e. if the element is an array, drill into it further via indexing, if the element is a string, use the string)

I have tried using the ref function to interrogate each array element. It seems to work for elements that are ARRAYs, but if the element is a string, I was expecting the ref to return SCALAR. However ref() seems to return nothing. What am I doing wrong? I would think ref() would return something.

Here is some sample code:

my @array = ("string1", 
             ["ele1_arraystr1", "ele1_arraystr2"], 
             "string2", 
             ["ele4_arraystr1", "ele4_arraystr2"], 
             "etc");
my $tmp;
&foobar( 30, 20, \@array);

sub foobar {
    my($var1, $var2, $array_ref) = @_;
    foreach $element (@$array_ref) {
        my $tmp = ref($element);
        print "Array element type: $tmp\n";
        if ($tmp eq 'ARRAY') {
            print "  ARRAY: $element->[1]\n";

        } elsif ($tmp eq 'SCALAR') {
            print "  SCALAR: $element\n";
        } else {
            print "  Unexpected type: $tmp\n";
       }
    }
 }

The output looks something like this:

ARRAY element test:
Array element type: 
  Unexpected type: 
Array element type: ARRAY
  ARRAY: ele1_arraystr2
Array element type: 
  Unexpected type: 
Array element type: ARRAY
  ARRAY: ele4_arraystr2
Array element type: 
  Unexpected type: 
like image 776
BentChainRing Avatar asked Sep 12 '16 03:09

BentChainRing


2 Answers

The ref returns an empty string if its argument isn't a reference. Docs say

Returns a non-empty string if EXPR is a reference, the empty string otherwise. The value returned depends on the type of thing the reference is a reference to.

The list that follows, which includes SCALAR, are the types that the reference can be to.

So when it has a string it returns an empty string, which evaluates to false. If you were to know for fact that it's ARRAY or string, you could do

if (ref($element) eq 'ARRAY') {
    # process the arrayref
}
else { 
    # process the string
}

Better check for the string (false) specifically, as you do, so to be able to detect any other types

my $ref_type = ref($element);
if ($ref_type eq 'ARRAY') {
    # process arrayref
}
elsif (not $ref_type) {  
    # process string
}
else { print "Got type $ref_type\n" }
like image 140
zdim Avatar answered Oct 27 '22 00:10

zdim


All of this is documented in perldoc perlfunc under ref

ref returns a false value if its parameter isn't a reference. It will return SCALAR only if the parameter is a reference to a scalar

You may also need to know that for a reference to blessed data—a Perl object—ref returns the class that the data has been blessed into, and not the underlying data type. If you need to distinguish between the two, then the core Scalar::Util module provides blessed, which returns the class into which the data has been blessed, and reftype, which returns the type of the underlying data in the same way as ref

You could make your foobar recursive to process an indefinitely-nested data structure, like this

use strict;
use warnings 'all';
use feature 'say';

my @array = (
    "string1", [ "ele1_arraystr1", "ele1_arraystr2" ],
    "string2", [ "ele4_arraystr1", "ele4_arraystr2" ],
    "etc",     [ "etc1", "etc2" ]
);

foobar(\@array);

sub foobar {
    my ($val, $indent) = (@_);
    $indent //= 0;
    my $ref = ref $val;

    if ( $ref eq 'ARRAY' ) {
        foobar($_, $indent+1) for @$val;
    }
    elsif ( not $ref ) {
        say '  ' x $indent, $val;
    }
}

output

  string1
    ele1_arraystr1
    ele1_arraystr2
  string2
    ele4_arraystr1
    ele4_arraystr2
  etc
    etc1
    etc2

Alternatively, if your array always consists of alternating strings and array references, you may find it easier to just assign it to a hash. That would use the strings as hash keys with their corresponding array references as hash values

This code shows the idea. I have used Data::Dump to reveal the resulting data structure

my %data = @array;

use Data::Dump;
dd \%data;

output

{
  etc => ["etc1", "etc2"],
  string1 => ["ele1_arraystr1", "ele1_arraystr2"],
  string2 => ["ele4_arraystr1", "ele4_arraystr2"],
}
like image 30
Borodin Avatar answered Oct 27 '22 00:10

Borodin