In Perl 6, can I use an Array as a Hash key? - arrays

In the Hash documentation, the section on Object keys seems to imply that you can use any type as a Hash key as long as you indicate but I am having trouble when trying to use an array as the key:
> my %h{Array};
{}
> %h{[1,2]} = [3,4];
Type check failed in binding to parameter 'key'; expected Array but got Int (1)
in block <unit> at <unknown file> line 1
Is it possible to do this?

The [1,2] inside the %h{[1,2]} = [3,4] is interpreted as a slice. So it tries to assign %h{1} and %{2}. And since the key must be an Array, that does not typecheck well. Which is what the error message is telling you.
If you itemize the array, it "does" work:
my %h{Array};
%h{ $[1,2] } = [3,4];
say %h.perl; # (my Any %{Array} = ([1, 2]) => $[3, 4])
However, that probably does not get what you want, because:
say %h{ $[1,2] }; # (Any)
That's because object hashes use the value of the .WHICH method as the key in the underlying array.
say [1,2].WHICH; say [1,2].WHICH;
# Array|140324137953800
# Array|140324137962312
Note that the .WHICH values for those seemingly identical arrays are different.
That's because Arrays are mutable. As Lists can be, so that's not really going to work.
So what are you trying to achieve? If the order of the values in the array is not important, you can probably use Sets as keys:
say [1,2].Set.WHICH; say [1,2].Set.WHICH
# Set|AEA2F4CA275C3FE01D5709F416F895F283302FA2
# Set|AEA2F4CA275C3FE01D5709F416F895F283302FA2
Note that these two .WHICHes are the same. So you could maybe write this as:
my %h{Set};
dd %h{ (1,2).Set } = (3,4); # $(3, 4)
dd %h; # (my Any %{Set} = ((2,1).Set) => $(3, 4))
Hope this clarifies things. More info at: https://docs.raku.org/routine/WHICH

If you are really only interested in use of an Object Hash for some reason, refer to Liz's answer here and especially the answers to, and comments on, a similar earlier question.
The (final1) focus of this answer is a simple way to use an Array like [1,'abc',[3/4,Mu,["more",5e6],9.9],"It's {<sunny rainy>.pick} today"] as a regular string hash key.
The basic principle is use of .perl to approximate an immutable "value type" array until such time as there is a canonical immutable Positional type with a more robust value type .WHICH.
A simple way to use an array as a hash key
my %hash;
%hash{ [1,2,3].perl } = 'foo';
say %hash{ [1,2,3].perl }; # displays 'foo'
.perl converts its argument to a string of Perl 6 code that's a literal version of that argument.
say [1,2,3].perl; # displays '[1, 2, 3]'
Note how spaces have been added but that doesn't matter.
This isn't a perfect solution. You'll obviously get broken results if you mutate the array between key accesses. Less obviously you'll get broken results corresponding to any limitations or bugs in .perl:
say [my %foo{Array},42].perl; # displays '[(my Any %{Array}), 42]'
1 This is, hopefully, the end of my final final answer to your question. See my earlier 10th (!!) version of this answer for discussion of the alternative of using prefix ~ to achieve a more limited but similar effect and/or to try make some sense of my exchange with Liz in the comments below.

Related

Why does numpy's `np.char.encode` turn an empty unicode array into an empty `float64` array?

I have an empty unicode array:
a = np.array([], dtype=np.str_)
I want to encode it:
b = np.char.encode(a, encoding='utf8')
Why is the result an empty array with dtype=float64?
# array([], dtype=float64)
If the array is not empty the resulting array is a properly encoded array with dtype=|S[n]:
a = np.array(['ss', 'ff☆'], dtype=np.str_)
b = np.char.encode(a, encoding='utf8')
# array([b'ss', b'ff\xe2\x98\x86'], dtype='|S5')
EDIT: The accepted answer below does, in fact, answer the question as posed but if you come here looking for a workaround, here is what I did:
if array.size == 0:
encoded_array = np.chararray((0,))
else:
encoded_array = np.char.encode(a, encoding='utf8')
This will produce an empty encoded array with dtype='|S1' if your decoded array is empty.
The source of numpy.char.encode is available here. It basically calls _vec_string which returns an empty array of type np.object_ in this case. This result is provided to _to_string_or_unicode_array which builds the final array and determine its type. Its code is available here. It basically converts the Numpy array to a list so to then provide it to np.asarray. The goal of this operation is to determine the type of the array but the thing is that empty arrays have a default type of np.float64 by convention (I think it is because Numpy was initially design for physicist who usually work with np.float64 arrays). This result is quite unexpected in this context, but the "S0" does not exists and I am not sure everyone would agree that the "S1" type is better here (still, it is certainly better than np.float64). Feel free to fill an issue on the GitHub Numpy repository so to start a discussion about this behaviour.

Array destructuring in Ruby

I've got a variable data which comes in one of the following two formats:
[1,2,3]
[[1,2,3],['a','b','c']]
At some point I need to parse this data and so I do:
main, alternative = data
While case (2) works as expected, (1) doesn't.
Instead it sets:
main=1
alternative=2
# 3 is dropped.
My end goal however is this:
main=[1,2,3]
alternative=nil
What's the most elegant way to do this? Ideally I'd like to avoid conditionals and long methods...
My honest answer here is don't pass data around in a fuzzy, poorly-defined structure. If at all possible, improve the underlying caller to send consistently-defined objects.
However if you're looking for a quick patch, then how about:
# data comes in one of the following two formats:
# 1. [1,2,3]
# 2. [[1,2,3],['a','b','c']]
# So, this patch enforces some consistency in the structure:
data = [data, nil] unless data.first.is_a?(Array)
main, alternative = data
If you are lucky enough to be running on ruby 2.7 or 3, you can use pattern matching:
case data
in [Array => main, Array => alternative]
# here `main` and `alternative` are bound to the expected items
# because the match succeeds by type.
in main
# now main is bound but alternative might still be bound to the previous
# clause, so don't use it.
alternative = nil
end
A more fluent, but still correct, way would be
data in [Array => main, Array => alternative] or data in Array => main
# now main and alternative are as expected
If the structure (length) of the array is known beforehand to be 3, you might also be comfortable with
data in [[_,_,_] => main, [_,_,_] => alternative] or data in [_,_,_] => main
so you have less false negatives.

Perl array referencing reversing

I am newbie to perl. And have been working with CSV files, JSON strings, arrays and hashes.
I have written this code, but it is giving me error. I want to use $header_copy in foreach loop.
1. my #headers=qw/January February March April May June/;
2. my $header_copy=\#headers;
3. print("$header_copy->[2]"); # Prints "March" Correctly
4. print("$header_copy[2]"); #Gives Error.
Error:
Global symbol "#header_copy" requires explicit package name at line 4
And I want to use $header_copy in for loop: like:
foreach $i ($header_copy){ **code..** }
You are taking the reference of #headers array using \#headers into $header_copy. So, before accessing it, you need to dereference it.
There are two ways(actually more than that) for it:
Using Arrow operator(->) - Most suitable for accesing a single item from arrayref
Using # { } - Suitable for iterating over arrayrefs .
$header_copy[2] will give error because you are accessing an element from arrayref without dereferencing it. The interpreter assumes it as an array #header_copy not an arrayref because the syntax says it.
Below program summarizes both approach:
#!/usr/bin/perl
use strict;
use warnings;
# define # headers
my #headers = qw/January February March April May June/;
# taken refrence of #headers array into $header_copy
my $header_copy = \#headers;
# dereferencing using arrow(->) operator
print $header_copy -> [2],"\n";
# derefrencing for iteration using #{...}
foreach(#{ $header_copy }) {
print $_,"\n";
}
The error 'Global symbol "..." requires explicit package name' is Perl-speak for "you're trying to use an undeclared variable". It even tells you the name of the undeclared variable - in this case it's #header_copy.
And if you look in your code, you'll see there's no declaration for an array called #header_copy. Oh, you have a scalar variable called $header_copy. And that contains a reference to an array (the array #headers). But that has no connection to an array called #header_copy.
So why does Perl think you want to use the array #header_copy? Well in the last line of your code, you use $header_copy[2] - which means "the third element in array #header_copy". And that generates an error because (as I've already pointed out) you don't have that array.
On the previous line you use $header_copy->[2]. And that works fine because ->[...] is the correct way to look up an element in an array that you have a reference to.
The important thing to realise is that $header_copy->[2] and $header_copy[2] mean two completely different things and refer to two completely different variables.
You also ask how you can get back to an array that you have a reference to (this is called "dereferencing"). That is simple. In general you use:
#{ expression that returns an array reference }
So, in your case, that would be:
#{ $header_copy }
But in cases where your expression is a scalar variable, you can simplify it by omitting the braces, so it becomes:
#$header_copy
So you want:
foreach my $i (#$header_copy) {
...
}
# reads data
my #headers=qw/January February March April May June/;
# use a foreach loop and store local copy of item nr in $headeritem_copy
foreach my $headeritem_copy (#headers) {
print("$headeritem_copy\n");
}
my #headers=qw/January February March April May June/;
my $header_copy=\#headers;
print("$header_copy->[2]"); # Prints "March" Correctly
De-reference the particular index
print("#{$header_copy}->[2]"); #Gives Error.

Perl data structures - looping an array of hashes inside a hash

I need a data structure to keep metadata about a field in a database, which I'm going to access to write dynamic SQL.
I'm using a hash to store things like the name, maybe data type, etc. And most importantly, an array of hashes containing information about the values I want to query out of the field, and the name I want to alias them with.
When I try to access elements of that array, I get:
Global symbol "%elem" requires explicit package name at test.pl line 18.
It sounds like maybe it's having trouble registering the fact that the loop variable representing the array elements is a hash, not a scalar. If I try:
foreach my %elem
then I get:
Missing $ on loop variable at test.pl line 17 (#1)
So far I can't find the relevant Perl documentation that addresses this.
#!/usr/local/bin/perl
use warnings;
use strict;
use diagnostics;
use POSIX 'strftime';
my %struct = (
#"field" = "foobar",
"values" => [
{value => "Y", name => "FOO"}
, {value => "N", name => "BAR"}
]
);
foreach my $elem (#{$struct->{'values'}}) {
print $elem->{'value'};
}
I expect the program to print "YN" to the console.
UPDATE, as someone pointed out I needed to use %hash->{'ref'} in the loop addressing. I added it. Now, I get a notification saying that using a hash as a reference is deprecated (?) but it is printing to the console now!
When I tried running your code, I got a different error than you reported:
Global symbol "$struct" requires explicit package name
This is because you've defined a hash %struct, not a hashref $struct, so you don't need to dereference it. Thus, I changed the line
foreach my $elem (#{$struct->{'values'}}) {
to
foreach my $elem (#{$struct{'values'}}) {
(note no -> to dereference) and it ran perfectly, no errors or warnings, and emitted the output
YN
as expected.
%struct is a hash, not a hash reference. Therefore, $struct->{'values'} is not the correct way to access the values key.
for my $elem (#{$struct{values}}) {
print "$elem->{value}\n";
}

How do I return a string as a one element array reference in Perl?

I'm new to Perl, and this thing has got me stuck for far too long...
I want to dump a readable representation of the object itself from inside a function (I'm trying to debug something, and I'm doing this by returning an array reference which the caller expects, but containing an object dump rather than human readable text as per normal) so in my package I have:
use Data::Dumper;
sub somefunctionName{
my $self = shift;
my $d = Dumper($self);
my #retval = ();
push(#retval, $d);
return \#retval;
}
This is giving me the error "Can't use string ("the literal object dump goes here") as a HASH ref while "strict refs" in use"
I can't for the life of me figure out a way to make the error go away, no matter how I mess with the backslashes, and what I've done above looks to me like exactly what every online tutorial does... But I'm obviously missing the point somewhere.
What am I doing wrong?
According to the documentation
Dumper(LIST)
Returns the stringified form of the values in the list, subject to the configuration options below. The values will be named $VAR n in the output, where n is a numeric suffix. Will return a list of strings in a list context.
You should be able to do
#retval = Dumper($self);
return \#retval

Resources