Coercing a scalar or an array to become an array - ruby

Sometimes I want a variable to always be an array, whether its a scalar or already an array.
So I normally do:
[variable].flatten
which is compatible with ruby-1.8.5, 1.8.7, 1.9.x.
With this method when variable is a string (variable = "asdf"), it gives me ["asdf"]. If it's already an array (variable = ["asdf","bvcx"]), it gives me: ["asdf","bvcx"].
Does anyone have a better way? "Better" meaning more readable, more performant, succinct or more effective in other ways.

Array(variable)
should do the trick. It uses the little known Kernel#Array method.

The way I do, and think is the standard way, is using [*...]:
variable1 = "string"
variable2 = ["element1", "element2"]
[*variable1] #=> ["string"]
[*variable2] #=> ["element1", "element2"]

You might need something like Array.eat. Most other methods either call #to_a or #to_ary on the object. If you where using [ obj ].flatten that might give surprising results. #flatten will also mangle nested arrays unless called with a level parameter and will make an extra copy of the array.
Active support provides Array.wrap, but that also calls #to_ary, which might or might not be to your liking, depending on your needs.
require 'active_support/core_ext/array/wrap'
class Array
# Coerce an object to be an array. Any object that is not an array will become
# a single element array with object at index 0.
#
# coercing nil returns an empty array.
#
def self.eat( object )
object.nil? and return []
object.kind_of?( Array ) and return object
[object]
end
end # class Array
a = { a: 3 }
p [a].flatten # => [{:a=>3}]
p [*a] # => [[:a, 3]] -> OOPS
p Array a # => [[:a, 3]] -> OOPS
p Array.wrap a # => [{:a=>3}]
p Array.eat a # => [{:a=>3}]

Related

Why can't I reassign a variable in a Ruby code block?

Why doesn't calling these two .map methods bring about equivalent results? The first one works as expected, whereas the second has no effect.
array = ["batman","boobytrap"]
puts array.map { |x| x.reverse! }
=> namtab
=> partyboob
puts array.map { |x| x = x.reverse }
=> batman
=> boobytrap
Print array after puts array.map { |x| x.reverse! }. You will see - array has changed.
Read documentation for reverse! method.
The problem is that in your first map, the ! has modified the values in the original array, so it now contains reversed strings.
irb:001:0> array = ["batman","boobytrap"]
=> ["batman", "boobytrap"]
irb:002:0> puts array.map { |x| x.reverse! }
namtab
partyboob
=> nil
irb:003:0> array
=> ["namtab", "partyboob"]
So the second time is doing what you expect it to, but the entry data is not what you think it is.
If you try the second case standalone without doing the first one you will see that it works as you expect it to.
You have to change your view of what a variable is here. The variable is not the actual value, but only a reference to that value.
array = ["batman"]
# We are freezing this string to prevent ruby from creating a
# new object for a string with the same value.
# This isnt really necessary, just to "make sure" the memory
# address stays the same.
string = "boobytrap".freeze
array.each do |val|
puts "before: ",
"val.object_id: #{val.object_id}",
"string.object_id: #{string.object_id}",
"array[0].object_id: #{array[0].object_id}",
""
val = string
puts "after: ",
"val.object_id: #{val.object_id}",
"string.object_id: #{string.object_id}",
"array[0].object_id: #{array[0].object_id}"
end
# before:
# val.object_id: 70244773377800,
# string.object_id: 70244761504360,
# array[0].object_id: 70244773377800
#
# after:
# val.object_id: 70244761504360,
# string.object_id: 70244761504360,
# array[0].object_id: 70244773377800
Obviously the values will differ if you run this code on your machine, but the point is, the memory address for val changes, while array[0] (which is where val comes from) stays the same after we assigned string to val. So basically what we do with the reassignment is, we tell ruby the value for val is no longer found in 70244773377800, but in 70244761504360. The array still refers to its first value with 70244773377800 though!
The #reverse! method call you use in your example on x on the other hand changes the value of whatever is found at 70244773377800 in memory, which is why it works as you expected.
TLDR;
Your first example changes the value in memory, while the second example assigns a new memory address to a local variable.
When you do array.map { |x| x.reverse! }, it changed the array values.
array = ["batman","boobytrap"]
puts array.map { |x| x.reverse! }
=> namtab
=> partyboob
array
=> ["namtab", "partyboob"]
If you perform second operation on the same array, it will produce the same results as you have stated in the question. However, it will not change the value of original array.
array = ["batman","boobytrap"]
puts array.map { |x| x.reverse! }
=> namtab
=> partyboob
array
=> ["namtab", "partyboob"]
puts array.map { |x| x = x.reverse }
=> batman
=> boobytrap
array
=> ["namtab", "partyboob"]
To change the value of original array, use map! in second operation.
array = ["batman","boobytrap"]
puts array.map { |x| x.reverse! }
=> namtab
=> partyboob
array
=> ["namtab", "partyboob"]
puts array.map! { |x| x.reverse }
=> batman
=> boobytrap
array
=> ["batman", "boobytrap"]
There are several problems with your approach.
The first problem is that you don't properly isolate your test cases: in your first test case you reverse the strings in the array. In your second test case you reverse them again. What happens if you reverse something twice? That's right: nothing! So, the reason why you think it isn't working is actually precisely the fact that it is working! And if it weren't working (i.e. not reversing the strings), then it would print the previously-reversed strings, and you would think it does work.
So, lesson #1: Always isolate your test cases!
Problem #2 is that the second piece of code doesn't do what you (probably) think it does. x is a local variable (because it starts with a lowercase letter). Local variables are local to the scope they are defined in, in this case the block. So, x only exists inside the block. You assign to it, but you never do anything with it after assigning to it. So, the assignment is actually irrelevant.
What is relevant, is the return value of the block. Now, in Ruby, assignments evaluate to the value that is being assigned, so considering that the assignment to x is superfluous, we can just get rid of it, and your code is actually exactly equivalent to this:
array.map { |x| x.reverse }
Your third problem is that the first piece also doesn't do what you (probably) think it does. Array#map returns a new array and leaves the original array untouched, but String#reverse! mutates the strings! In other words: its primary mode of operation is a side-effect. In addition to the side-effect, it also returns the reversed string, which is another thing that confused you. It could just as well return nil instead to indicate that it performs a side-effect, in which case you would instead see the following:
array = %w[batman boobytrap]
array.map(&:reverse!)
#=> [nil, nil]
array
#=> ['namtab', 'partyboob']
As you can see, if String#reverse! did return nil, what you would observe is the following:
array.map returns a new array whose elements are the return values of the block, which is just nil
array now still contains the same String objects as before, but they have been mutated
Now, since String#reverse! actually does return the reversed String what you actually observe is this:
array = %w[batman boobytrap]
array.map(&:reverse!)
#=> ['namtab', 'partyboob']
array
#=> ['namtab', 'partyboob']
array.map(&:reverse)
#=> ['batman', 'boobytrap']
array
#=> ['namtab', 'partyboob']
Which brings me to lesson #2: side-effects and shared mutable state are evil!
You should avoid both side-effects and shared mutable state (ideally, mutable state in general, but mutable state that is shared between different pieces is especially evil) as far as possible.
Why are they evil? Well, just look at how much they confused even in this extremely simple tiny example? Can you imagine the same thing happening in a much, much larger application?
The problem with side-effects is that they "happen on the side". They are not arguments, they are not return values. You cannot print them out, inspect them, store them in a variable, place assertions on them in a unit test, and so on.
The problem with shared mutable state (mutation is just a form of side-effect, by the way) is that enables "spooky action at a distance": you have a piece of data in one place of your code, but this piece of data is shared with a different place of your code. Now, if one place mutates the data, the other place will seemingly magically have their data change out under them. In your example here, the shared state were the strings inside the array, and mutating them in one line of code made them also change in another line of code.

Join doesn't convert the splat argument into a string in Ruby

Let's say we have this code:
def something(*someargs)
return *someargs.join(",")
end
Now, I found you can reference *someargs just like any other variable anywhere in the method definition. But I tried this...returning *someargs as a string, separated with a comma. Yet, when I call this method:
a = something(4, 5)
p a.class # => Array
p a #> ["4,5"]
why does something(4,5) still returns an array? If I do something like this:
[4, 5].join(",")
the result will be a string not in an array. So my question would be, how do I make the "something" method return an actual string which contains all the arguments as a string. And it's weird because if I do *someargs.class, the result is "Array", yet it doesn't behave like a typical array...
Try below :
def something(*someargs)
return someargs.join(",")
end
a = something(4, 5)
p a.class # => String
p a # => "4,5"
One example to explain your case:
a = *"12,11"
p a # => ["12,11"]
So when you did return *someargs.join(","), someargs.join(",") created the string as "4,5".But now you are using splat operator(*) again on the evaluated string "4,5" with the assignment operation like a = *"4,5". So finally you are getting ["4,5"].
Read more scenarios with splat operators here - Splat Operator in Ruby
Hope that helps.
An object prepended with a splat *... is not an object. You cannot reference such thing, nor can you pass it as an argument to a method because there is no such thing. However, if you have a method that can take multiple arguments, for example puts, then you can do something like:
puts *["foo", "bar"]
In this case, there is no such thing as *["foo", "bar"]. The splat operator is expanding it into multiple arguments. It is equivalent to:
puts "foo", "bar"
Regarding why someargs remains to be an array after someargs.join(","). That is because join is not a destructive method. It does not do anything to the receiver. Furthermore, an object cannot change its class by a destructive method. The only way to change the reference of someargs from an array to a string is to reassign it.

How to pass JSON values into Struct Ruby object?

Given a JSON object
{"a": 1, "b":2}
and a value object that is derived from a struct:
class A < Stuct.new(:a, :b)
end
How would I make an instance of A that has the values from the JSON?
I am trying:
a = A.new(JSON.parse({a:1,b:2}.to_json).values)
=> #<struct A a=[1, 2], b=nil>
But I would expect a->1, and b->2
Try using:
a = A.new(*JSON[json].values)
a.class # => A < #<Class:0x00000102955828>
The problem is that values returns an array, but you need the individual elements of the array. Using * "splats" the array back into its components, which makes Struct happy when you pass the values to new.
EDIT:
This will fail if the ordering of the JSON and the Struct do not match!
This forces the order of the values.
a = A.new(*JSON[json].values_at('a', 'b'))
{
:a => 1,
:b => 2
}
a.class # => A < #<Class:0x00000102955828>
JSON preserves the hash insertion order, as does Ruby, so, JSON rendered and parsed by Ruby will be correct. JSON rendered by something that doesn't preserve the order could be a problem, but values_at fixes the problem.
Note that JSON converts symbols to strings, so the keys passed to values_at have to be strings, not symbols.
If it does not have to be a predefined struct, this will work
a = Struct.new(*json.keys).new(*json.values)
You can use the splat operator to pass the array values as arguments to the new function.
a = A.new(*{a:1,b:2}.values)

what is the mean of * and flatten in ruby

I am new to ruby language so when I was trying to sort a hash by value
I used this method to sort:
movie_popularity.sort_by{|m,p| p}.reverse
but the the sort method returns an array while I need a hash to be returned so I used this command:
movie_popularity=Hash[*movie_popularity.sort_by{|m,p| p}.reverse.flatten]
my Question is what is the meaning of * and flatten in the above line?
Thanks =)
The * is called the "splat operator"; I'm not sure I could give you the technical definition (though I'm sure you'd find it soon enough with Google's help), but the way I'd describe it is that it basically takes the place of hand-writing multiple comma-separated values in code.
To make this more concrete, consider the case of Hash[] which you've used in your example. The Hash class has a [] class method which takes a variable number of arguments and can normally be called like this:
# Returns { "foo" => 1, "bar" => 2 }
h = Hash["foo", 1, "bar", 2]
Notice how that isn't an array or a hash or anything that I passed in; it's a (hand-written) sequence of values. The * operator allows you to achieve basically the same thing using an array--in your case, the one returned by movie_popularity.sort_by{|m,p| p}.reverse.flatten.
As for that flatten call: when you call sort_by on a hash, you're really leveraging the Enumerable module which is included in a variety of classes (most notably Array and Hash) that provide enumeration. In the case of a hash, you've probably noticed that instead of iterating over one like this:
hash.each { |value| ... }
Instead you do this:
hash.each { |key, value| ... }
That is, iterating over a hash yields two values on each iteration. So your sort_by call on its own would return a sequence of pairs. Calling flatten on this result collapses the pairs into a one-dimensional sequence of values, like this:
# Returns [1, 2, 3, 4]
[[1, 2], [3, 4]].flatten
'flatten' flattens an array: http://www.ruby-doc.org/core-1.9.3/Array.html#method-i-flatten
'*' is the splat operator: http://theplana.wordpress.com/2007/03/03/ruby-idioms-the-splat-operator/
The pertinent bit in the last url is this:
a = [[:planes, 21], [:cars, 36]]
h = Hash[*a] # => { :planes=>21, :cars=>36}

Ruby: How to loop through an object that may or may not be an array?

I have an each method that is run on some user-submitted data.
Sometimes it will be an array, other times it won't be.
Example submission:
<numbers>
<number>12345</number>
</numbers>
Another example:
<numbers>
<number>12345</number>
<number>09876</number>
</numbers>
I have been trying to do an each do on that, but when there is only one number I get a TypeError (Symbol as array index) error.
I recently asked a question that was tangentally similar. You can easily force any Ruby object into an array using Array.
p Array([1,2,3]) #-> [1,2,3]
p Array(123) #-> [123]
Of course, arrays respond to each. So if you force everying into an array, your problem should be solved.
A simple workaround is to just check if your object responds to :each; and if not, wrap it in an array.
irb(main):002:0> def foo x
irb(main):003:1> if x.respond_to? :each then x else [x] end
irb(main):005:1> end
=> nil
irb(main):007:0> (foo [1,2,3]).each { |x| puts x }
1
2
3
=> [1, 2, 3]
irb(main):008:0> (foo 5).each { |x| puts x }
5
=> [5]
It looks like the problem you want to solve is not the problem you are having.
TypeError (Symbol as array index)
That error tells me that you have an array, but are treating it like a hash and passing in a symbol key when it expects an integer index.
Also, most XML parsers provide child nodes as array, even if there is only one. So this shouldn't be necesary.
In the case of arguments to a method, you can test the object type. This allows you to pass in a single object or an array, and converts to an array only if its not one so you can treat it identically form that point on.
def foo(obj)
obj = [obj] unless obj.is_a?(Array)
do_something_with(obj)
end
Or something a bit cleaner but more cryptic
def foo(obj)
obj = [*obj]
do_something_with(obj)
end
This takes advantage of the splat operator to splat out an array if it is one. So it splats it out (or doesn't change it) and you can then wrap it an array and your good to go.
I was in the same position recently except the object I was working with was either a hash or an array of hashes. If you are using Rails, you can use Array.wrap because Array(hash) converts hashes to an array.
Array({foo: "bar"}) #=> [[:foo, "bar"]]
Array.wrap({foo: "bar"}) #=> [{:foo=>"bar"}]
Array.wrap(123) #=> [123]
Array.wrap([123]) #=> [123]
I sometimes use this cheap little trick:
[might_be_an_array].flatten.each { |x| .... }
Use the splat operator:
[*1] # => [1]
[*[1,2]] # => [1,2]
Like Mark said, you're looking for "respond_to?" Another option would be to use the conditional operator like this:
foo.respond_to? :each ? foo.each{|x| dostuff(x)} : dostuff(foo);
What are you trying to do with each number?
You should try to avoid using respond_to? message as it is not a very object oriented aproach.
Check if is it possible to find in the xml generator code where it is assigning an integer value when there is just one <"number"> tag and modify it to return an array.
Maybe it is a complex task, but I would try to do this in order to get a better OO design.
I don't know much anything about ruby, but I'd assume you could cast (explicitly) the input to an array - especially given that if the input is simply one element longer it's automatically converted to an array.
Have you tried casting it?
If your input is x, use x.to_a to convert your input into an array.
[1,2,3].to_a
=> [1, 2, 3]
1.to_a
=> [1]
"sample string".to_a
=> ["sample string"]
Edit: Newer versions of Ruby seem to not define a default .to_a for some standard objects anymore. You can always use the "explicit cast" syntax Array(x) to achieve the same effect.

Resources