Array destructuring in Ruby - arrays

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.

Related

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

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.

Storing values obtained from for each loop Scala

Scala beginner who is trying to store values obtains in a Scala foreach loop but failing miserably.
The basic foreach loop looks like this currently:
order.orderList.foreach((x: OrderRef) => {
val references = x.ref}))
When run this foreach loop will execute twice and return a reference each time. I'm trying to capture the reference value it returns on each run (so two references in either a list or array form so I can access these values later)
I'm really confused about how to go about doing this...
I attempted to retrieve and store the values as an array but when ran, the array list doesn't seem to hold any values.
This was my attempt:
val newArray = Array(order.orderList.foreach((x: OrderRef) => {
val references = x.ref
}))
println(newArray)
Any advice would be much appreciated. If there is a better way to achieve this, please share. Thanks
Use map instead of foreach
order.orderList.map((x: OrderRef) => {x.ref}))
Also val references = x.ref doesn't return anything. It create new local variable and assign value to it.
Agree with answer 1, and I believe the reason is below:
Return of 'foreach' or 'for' should be 'Unit', and 'map' is an with an changed type result like example below:
def map[B](f: (A) ⇒ B): Array[B]
Compare To for and foreach, the prototype should be like this
def foreach(f: (A) ⇒ Unit): Unit
So If you wanna to get an changed data which is maped from your source data, considering more about functions like map, flatMap, and these functions will traverse all datas like for and foreach(except with yield), but with return values.

using ruby to extract the values in a hash in a DRY way

My app passes to different methods a json_element for which the keys are different, and sometimes empty.
To handle it, I have been hard-coding the extraction with the following sample code:
def act_on_ruby_tag(json_element)
begin
# logger.progname = __method__
logger.debug json_element
code = json_element['CODE']['$'] unless json_element['CODE'].nil?
predicate = json_element['PREDICATE']['$'] unless json_element['PREDICATE'].nil?
replace = json_element['REPLACE-KEY']['$'] unless json_element['REPLACE-KEY'].nil?
hash = json_element['HASH']['$'] unless json_element['HASH'].nil?
I would like to eliminate hardcoding the values, and not quite sure how.
I started to think through it as follows:
keys = json_element.keys
keys.each do |k|
set_key = k.downcase
instance_variable_set("#" + set_key, json_element[k]['$']) unless json_element[k].nil?
end
And then use #code for example in the rest of the method.
I was going to try to turn into a method and then replace all this hardcoded code.
But I wasn't entirely sure if this is a good path.
It's almost always better to return a hash structure from a method where you have things like { code: ... } rather than setting arbitrary instance variables. If you return them in a consistent container, it's easier for callers to deal with delivering that to the right location, storing it for later, or picking out what they want and discarding the rest.
It's also a good idea to try and break up one big, clunky step with a series of smaller, lighter operations. This makes the code a lot easier to follow:
def extract(json)
json.reject do |k, v|
v.nil?
end.map do |k, v|
[ k.downcase, v['$'] ]
end.to_h
end
Then you get this:
extract(
'TEST' => { '$' => 'value' },
'CODE' => { '$' => 'code' },
'NULL' => nil
)
# => {"test"=>"value", "code"=>"code"}
If you want to persist this whole thing as an instance variable, that's a fairly typical pattern, but it will have a predictable name that's not at the mercy of whatever arbitrary JSON document you're consuming.
An alternative is to hard-code the keys in a constant like:
KEYS = %w[ CODE PREDICATE ... ]
Then use that instead, or one step further, define that in a YAML or JSON file you can read-in for configuration purposes. It really depends on how often these will change, and what sort of expectations you have about the irregularity of the input.
This is a slightly more terse way to do what your original code does.
code, predicate, replace, hash = json_element.values_at *%w{
CODE PREDICATE REPLACE-KEY HASH
}.map { |x| x.fetch("$", nil) if x }

can't convert Enumerator into Array

While working on one application I am getting this error:
can't convert Enumerator into Array
Here is my code, mr_collection is MongoID query.
mr_collection = self.where(query).map_reduce(map, reduce).finalize(finalize).out({:replace => 'mr_results'})
paginator = WillPaginate::Collection.new(page, limit, collection_count)
collection = mr_collection.find(
:sort => sort,
:limit => limit,
:skip => skip
)
paginator.replace(collection)
While getting mr_collection, if I inspect the result mr_collection gives me:
[
{"_id"=>1.0, "value"=>{"s"=>4.2, "p"=>14.95, "pml"=>0.01993}},
{"_id"=>2.0, "value"=>{"s"=>3.7, "p"=>12.9, "pml"=>0.0172}},
{"_id"=>3.0, "value"=>{"s"=>4.2, "p"=>12.9, "pml"=>0.0172}},
{"_id"=>4.0, "value"=>{"s"=>4.0, "p"=>11.95, "pml"=>0.01593}},
{"_id"=>300.0, "value"=>{"s"=>0.0, "p"=>8.95, "pml"=>0.01193}},
]
While getting collection, if I inspect the result collection gives me:
#<Enumerator: []:find({:sort=>[["value.s", :desc], ["value.pml", :asc]], :limit=>10, :skip=>0})>
I am getting error on the line paginator.replace(collection). I'm using Ruby 1.9.3 & Rails 3.2.6.
collection is an Enumerator which obviously can't convert into an Array, which is what replace expects.
Here are the comments from the rubydocs:
Enumerable#find(ifnone = nil) { |e| ... }
Passes each entry in enum to block. Returns the first for which block
is not false. If no object matches, calls ifnone and returns its
result when it is specified, or returns nil otherwise.
If no block is given, an enumerator is returned instead.
Therefore you have two options:
If you want all elements, yield from the Enumerator to an Array.
If you only want the first match, supply a block that determines what the match is.
Hope this helps.
(Moral of the story: always read the docs!)
I have no idea about mongoid having never used it.
But a search has brought to fore an awfully similar question -
Mongoid 3 - access map_reduce results
Unfortunately my environent is not set to test the magic of
collection = mr_collection.send(:documents).sort(sort).limit(limit).skip(skip).to_a
Have you had a look at this link? Hopefully it'll help solve your issue!

Perl -- DBI selectall_arrayref when querying getting Not Hash Reference

I am very new to perl (but from a c# background) and I am trying to move some scripts to a windows box.
Due to some modules not working easily with windows I have changed the way it connects to the DB.
I have an sqlserver DB and I had a loop reading each row in a table, and then within this loop another query was sent to select different info.
I was the error where two statements can't be executed at once within the same connection.
As my connection object is global I couldn't see an easy way round this, so decided to store the first set of data in an array using:
my $query = shift;
my $aryref = $dbh->selectall_arrayref($query) || die "Could not select to array\n";
return($aryref);
(this is in a module file that is called)
I then do a foreach loop (where #$s_study is the $aryref returned above)
foreach my $r_study ( #$s_study ) {
~~~
my $surveyId=$r_study->{surveyid}; <-------error this line
~~~~
};
When I run this I get an error "Not a hash reference". I don't understand?!
Can anyone help!
Bex
You need to provide the { Slice => {} } parameter to selectall_arrayref if you want each row to be stored as a hash:
my $aryref = $dbh->selectall_arrayref($query, { Slice => {} });
By default, it returns a reference to an array containing a reference to an array for each row of data fetched.
$r_study->{surveyid} is a hashref
$r_study->[0] is an arrayref
this is your error.
You should use the second one
If you have a problem with a method, then a good first step is to read the documentation for that method. Here's a link to the documentation for selectall_arrayref. It says:
This utility method combines
"prepare", "execute" and
"fetchall_arrayref" into a single
call. It returns a reference to an
array containing a reference to an
array (or hash, see below) for each
row of data fetched.
So the default behaviour is to return a reference to an array which contains an array reference for each row. That explains your error. You're getting an array reference and you're trying to treat it as a hash reference. I'm not sure that the error could be much clearer.
There is, however, that interesting bit where it says "or hash, see below". Reading on, we find:
You may often want to fetch an array
of rows where each row is stored as a
hash. That can be done simple using:
my $emps = $dbh->selectall_arrayref(
"SELECT ename FROM emp ORDER BY ename",
{ Slice => {} }
);
foreach my $emp ( #$emps ) {
print "Employee: $emp->{ename}\n";
}
So you have two options. Either switch your code to use an array ref rather than a hash ref. Or add the "{ Slice => {} }" option to the call, which will return a hash ref.
The documentation is clear. It's well worth reading it.
When you encounter something like "Not a hash reference" or "Not an array reference" or similar you can always take Data::Dumper to just dump out your variable and you will quickly see what data you are dealing with: arrays of arrayrefs, hashes of something etc.
And concerning reading the data, this { Slice => {} } is most valuable addition.

Resources