Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Raku: Trouble Accessing Value of a Multidimensional Hash

Tags:

hash

raku

I am having issues accessing the value of a 2-dimensional hash. From what I can tell online, it should be something like: %myHash{"key1"}{"key2"} #Returns value

However, I am getting the error: "Type Array does not support associative indexing."

Here's a Minimal Reproducible Example.

my %hash = key1-dim1 => key1-dim2 => 42, key2-dim1 => [42, 42];
say %hash{'key1-dim1'}{'key1-dim2'}; # 42
say %hash{'key2-dim1'}{'foo bar'};   # Type Array does not support associative indexing.

Here's another reproducible example, but longer:

my @tracks = 'Foo Bar', 'Foo Baz';
my %count;
for @tracks -> $title {
        $_ = $title;
        my @words = split(/\s/, $_);
        if (@words.elems > 1) {
            my $i = 0;
            while (@words.elems - $i > 1) { 
                my %wordHash = ();
                %wordHash.push: (@words[$i + 1] => 1);
                %counts.push: (@words[$i] => %wordHash);
                say %counts{@words[$i]}{@words[$i+1]}; #===============CRASHES HERE================
                say %counts.kv;
                $i = $i + 1;
            }
        }
    }

In my code above, the problem line where the 2-d hash value is accessed will work once in the first iteration of the for-loop. However, it always crashes with that error on the second time through. I've tried replacing the array references in the curly braces with static key values in case something was weird with those, but that did not affect the result. I can't seem to find what exactly is going wrong by searching online.

I'm very new to raku, so I apologize if it's something that should be obvious.

like image 689
Garrett S Avatar asked Oct 24 '21 08:10

Garrett S


Video Answer


2 Answers

After adding the second elements with push to the same part of the Hash, the elment is now an array. Best you can see this by print the Hash before the crash:

 say "counts: " ~  %counts.raku;
 #first time: counts: {:aaa(${:aaa(1)})}
 #second time: counts: {:aaa($[{:aaa(1)}, {:aaa(1)}])}

The square brackets are indicating an array.

Maybe BagHash does already some work for you. See also raku sets without borders

my @tracks = 'aa1 aa2 aa2 aa3', 'bb1 bb2', 'cc1';
for @tracks -> $title {
    my $n = BagHash.new: $title.words;
    $n.raku.say;
}

#("aa2"=>2,"aa1"=>1,"aa3"=>1).BagHash
#("bb1"=>1,"bb2"=>1).BagHash
#("cc1"=>1).BagHash

like image 112
LuVa Avatar answered Nov 03 '22 00:11

LuVa


Let me first explain the minimal example:

my %hash = key1-dim1 => key1-dim2 => 42,
           key2-dim1 => [42, 42];

say %hash{'key1-dim1'}{'key1-dim2'}; # 42
say %hash{'key2-dim1'}{'key2-dim2'}; # Type Array does not support associative indexing.

The problem is that the value associated with key2-dim1 isn't itself a hash but is instead an Array. Arrays (and all other Positionals) only support indexing by position -- by integer. They don't support indexing by association -- by string or object key.

Hopefully that explains that bit. See also a search of SO using the [raku] tag plus 'Type Array does not support associative indexing'.


Your longer example throws an error at this line -- not immediately, but eventually:

say %counts{...}{...}; # Type Array does not support associative indexing.

The hash %counts is constructed by the previous line:

%counts.push: ...

Excerpting the doc for Hash.push:

  • If a key already exists in the hash ... old and new value are both placed into an Array

Example:

my %h  = a => 1;
%h.push: (a => 1);              # a => [1,1]

Now consider that the following code would have the same effect as the example from the doc:

my %h;
say %h.push: (a => 1);          # {a => 1}
say %h.push: (a => 1);          # {a => [1,1]}

Note how the first .push of a => 1 results in a 1 value for the a key of the %h hash, while the second .push of the same pair results in a [1,1] value for the a key.

A similar thing is going on in your code.

In your code, you're pushing the value %wordHash into the @words[$i] key of the %counts hash.

The first time you do this the resulting value associated with the @words[$i] key in %counts is just the value you pushed -- %wordHash. This is just like the first push of 1 above resulting in the value associated with the a key, from the push, being 1.

And because %wordHash is itself a hash, you can associatively index into it. So %counts{...}{...} works.

But the second time you push a value to the same %counts key (i.e. when the key is %counts{@words[$i]}, with @words[$i] set to a word/string/key that is already held by %counts), then the value associated with that key will not end up being associated with %wordHash but instead with [%wordHash, %wordHash].

And you clearly do get such a second time in your code, if the @tracks you are feeding in have titles that begin with the same word. (I think the same is true even if the duplication isn't the first word but instead later ones. But I'm too confused by your code to be sure what the exact broken combinations are. And it's too late at night for me to try understand it, especially given that it doesn't seem important anyway.)

So when your code then evaluates %counts{@words[$i]}{@words[$i+1]}, it is the same as [%wordHash, %wordHash]{...}. Which doesn't make sense, so you get the error you see.


Hopefully the foregoing has been helpful.

But I must say I'm both confused by your code, and intrigued as to what you're actually trying to accomplish.

I get that you're just learning Raku, and that what you've gotten from this SO might already be enough for you, but Raku has a range of nice high level hash like data types and functionality, and if you describe what you're aiming at we might be able to help with more than just clearing up Raku wrinkles that you and we have been dealing with thus far.

Regardless, welcome to SO and Raku. :)

like image 29
raiph Avatar answered Nov 03 '22 02:11

raiph