Multiple return values with arrays and hashes - arrays

I believe arrays are mostly used for returning multiple values from methods:
def some_method
return [1, 2]
end
[a, b] = some_method # should yield a = 1 and b = 2
I presume this is a kind of syntactic sugar that Ruby provides. Can we get a similar result with hashes, for instance
def some_method
return { "a" => 1, "b" => 2 }
end
{"c", "d"} = some_method() # "c" => 1, "d" => 2
I'm looking for the result { "c" => 1, "d" => 2 }, which obviously does not happen. Is there any other way this can be done? I know that we can return a hash from the method and store it and use it like so
def some_method
return {"a" => 1, "b" => 2}
end
hash = some_method()
Just curious if there is another way similar to the one with arrays but using hashes....
I think a simpler way to put the question would be...
# If we have a hash
hash = {"a" => 1, "b" => 2}
# Is the following possible
hash = {2, 3} # directly assigning values to the hash.
OR
# another example
{"c", "d"} = {2, 3} # c and d would be treated as keys and {2, 3} as respective values.

First of all, you have a syntax error. Instead of this:
[a, b] = [1, 2]
you should use:
a, b = [1, 2]
And if you want to use similar syntax with hashes, you can do:
a, b = { "c" => 1, "d" => 2 }.values # a => 1, b => 2
This is actually the same thing as the array version, beacause Hash#values returns an array of the hash values in the order they were inserted to the hash (because ruby hashes have a nice feature of preserving their order)

What you are asking is syntactically not possible.
What you want to accomplish is possible, but you will have to code it.
One possible way to do that is shown below
hash = {"a" => 1, "b" => 2}
# Assign new keys
new_keys = ["c", "d"]
p [new_keys, hash.values].transpose.to_h
#=> {"c"=>1, "d"=>2}
# Assign new values
new_values = [3, 4]
p [hash.keys, new_values].transpose.to_h
#=> {"a"=>3, "b"=>4}
If you really want some more easier looking way of doing, you could monkey-patch Hash class and define new methods to manipulate the values of keys and values array. Please be cautioned that it may not be really worthwhile to mess with core classes. Anyways, a possible implementation is shown below. Use at your own RISK.
class Hash
def values= new_values
new_values.each_with_index do |v, i|
self[keys[i]] = v if i < self.size
end
end
def keys= new_keys
orig_keys = keys.dup
new_keys.each_with_index do |k, i|
if i < orig_keys.size
v = delete(orig_keys[i])
self[k] = v
rehash
end
end
end
end
hash = {"a" => 1, "b" => 2}
hash.values = [2,3]
p hash
#=> {"a"=>2, "b"=>3}
hash.keys = ["c", "d"]
p hash
#=> {"c"=>2, "d"=>3}
hash.keys, hash.values = ["x","y"], [9, 10]
p hash
#=> {"x"=>9, "y"=>10}
hash.keys, hash.values = ["x","y"], [9, 10]
p hash
#=> {"x"=>9, "y"=>10}
# *** results can be unpredictable at times ***
hash.keys, hash.values = ["a"], [20, 10]
p hash
#=> {"y"=>20, "a"=>10}

Related

Turning an array into a hash

I have a ruby array of hashes:
my_array = [{"apples" => 5}, {"oranges" => 12}]
I would like to turn it into a hash, where the hash keys are equal to the array index values +1, so like this:
my_hash = {"1"=>{"apples"=> 5}, "2"=>{"oranges" => 12}}
Any ideas?
You can also Enumerable#zip with a range, then convert Array#to_h:
(1..my_array.size).zip(my_array).to_h
#=> {1=>{"apples"=>5}, 2=>{"oranges"=>12}}
How it works
my_array.size #=> 2 returns the size of the Array as an Integer.
(1..my_array.size) it's an inclusive Range which enumerates integers form 1 to array size, 2 in this case.
A Range responds to Enumerable#zip, so, for example you can do this obtaining an Array of pairs:
(1..3).zip(:a..:c) #=> [[1, :a], [2, :b], [3, :c]]
Finally, an Array of pairs can be converted into an Hash, see Array#to_h:
[[1, :a], [2, :b], [3, :c]].to_h #=> {1=>:a, 2=>:b, 3=>:c}
Since the Range is made of integer, keys of the Hash are integer. But you can tweak the line of code to obtain strings as keys.
my_array = [{"apples" => 5}, {"oranges" => 12}]
my_hash = my_array.each_with_index.map{|h, i| [(i+1).to_s, h]}.to_h
You can try to use Enumerator#with_index to have index and Enumerator#each_with_object to create new hash
my_array = [{"apples"=> 5}, {"oranges" => 12}]
my_hash = my_array.each.with_index.with_object({}){|(hsh, i), e| e[(i+1).to_s] = hsh}
# => {"1"=>{"apples"=> 5}, "2"=>{"oranges" => 12}}

Ruby array += vs push

I have an array of arrays and want to append elements to the sub-arrays. += does what I want, but I'd like to understand why push does not.
Behavior I expect (and works with +=):
b = Array.new(3,[])
b[0] += ["apple"]
b[1] += ["orange"]
b[2] += ["frog"]
b => [["apple"], ["orange"], ["frog"]]
With push I get the pushed element appended to EACH sub-array (why?):
a = Array.new(3,[])
a[0].push("apple")
a[1].push("orange")
a[2].push("frog")
a => [["apple", "orange", "frog"], ["apple", "orange", "frog"], ["apple", "orange", "frog"]]
Any help on this much appreciated.
The issue here is b = Array.new(3, []) uses the same object as the base value for all the array cells:
b = Array.new(3, [])
b[0].object_id #=> 28424380
b[1].object_id #=> 28424380
b[2].object_id #=> 28424380
So when you use b[0].push, it adds the item to "each" sub-array because they are all, in fact, the same array.
So why does b[0] += ["value"] work? Well, looking at the ruby docs:
ary + other_ary → new_ary
Concatenation — Returns a new array built by concatenating the two arrays together to produce a third array.
[ 1, 2, 3 ] + [ 4, 5 ] #=> [ 1, 2, 3, 4, 5 ]
a = [ "a", "b", "c" ]
c = a + [ "d", "e", "f" ]
c #=> [ "a", "b", "c", "d", "e", "f" ]
a #=> [ "a", "b", "c" ]
Note that
x += y
is the same as
x = x + y
This means that it produces a new array. As a consequence, repeated use of += on arrays can be quite inefficient.
So when you use +=, it replaces the array entirely, meaning the array in b[0] is no longer the same as b[1] or b[2].
As you can see:
b = Array.new(3, [])
b[0].push("test")
b #=> [["test"], ["test"], ["test"]]
b[0].object_id #=> 28424380
b[1].object_id #=> 28424380
b[2].object_id #=> 28424380
b[0] += ["foo"]
b #=> [["test", "foo"], ["test"], ["test"]]
b[0].object_id #=> 38275912
b[1].object_id #=> 28424380
b[2].object_id #=> 28424380
If you're wondering how to ensure each array is unique when initializing an array of arrays, you can do so like this:
b = Array.new(3) { [] }
This different syntax lets you pass a block of code which gets run for each cell to calculate its original value. Since the block is run for each cell, a separate array is created each time.
It's because in the second code section, you're selecting the sub-array and pushing to it, if you want an array of array's you need to push the array to the main array.
a = Array.new(3,[])
a.push(["apple"])
a.push(["orange"])
a.push(["frog"])
to get the same result as the first one.
EDIT: I forgot to mention, because you initialize the array with blank array's as elements, you will have three empty elements in front of the pushed elements,

Convert a hash into an array

I am trying to create a method that will take a hash:
{"H"=> 1, "e"=> 1, "l"=> 3, "o"=> 2, "W"=> 1, "r"=> 1, "d"=> 1}
as a parameter and return an array of its key-value pairs like such:
arr = [["H", 1], ["e", 1], ..., ["d", 1]]
I have the following, but it is flawed:
def toCountsArray(counts)
arr = []
i = 0
counts.each do |key, value|
arr[i].push [key, value]
i += 1
end
return arr
end
I am not supposed to use the to_a method or any kind of helper like that. Any help or guidance is appreciated.
You're basically there. The arbitrary restriction on to_a is odd, since there's many ways to get effectively the same thing. Still, to fix your original example:
array = [ ]
counts.each do |pair|
array << pair
end
That's a messy way of doing to_a, but it should work. Your mistake was trying to append to a specific element of array, not append to the array itself.
A pattern to use when doing this sort of operation is this:
counts = Hash.new(0)
That creates a Hash with a default value of 0 for each element. This avoids the dance you have to do to assign to an undefined key.
There's a few other things you can do to reduce this and make it more Ruby-like:
def count_chars(string)
string.chars.each_with_object(Hash.new(0)) do |char, counts|
case (char)
when ' '
# Ignored
else
counts[char] += 1
end
end
end
The each_with_object method is handy in that it iterates over an array while passing through an object that each iteration can make use of. Combining the trick of having a Hash with a default value makes this pretty tidy.
If you have a longer list of "to ignore" characters, express that as an array. string.chars - exclusions can then delete the unwanted ones. I've used a case statement here to make adding special behaviour easier.
hash = { "H"=> 1, "e"=> 1, "l"=> 3, "o"=> 2, "W"=> 1, "r"=> 1, "d"=> 1 }
p [*hash]
# => [["H", 1], ["e", 1], ["l", 3], ["o", 2], ["W", 1], ["r", 1], ["d", 1]]
instead of
arr[i].push [key, value]
use
arr.push [key, value]
because arr[i] refers to the i-th element
I would do something like this:
hash = { "H"=> 1, "e"=> 1, "l"=> 3, "o"=> 2, "W"=> 1, "r"=> 1, "d"=> 1 }
hash.each_with_object([]) { |kv, a| a << kv }
#=> [["H",1],["e",1],["l",3],["o",2],["W",1],["r",1],["d",1]]
You can do this:
def to_counts_array(counts)
counts.map { |k, v| [k, v] }
end
h = { "H"=> 1, "e"=> 1, "l"=> 3, "o"=> 2, "W"=> 1, "r"=> 1, "d"=> 1 }
to_counts_array(h)
Although I like the #steenslag's answer as well.
Another way, just map to self:
x.map &:itself #=> [["H", 1], ["e", 1], ["l", 3], ["o", 2], ["W", 1], ["r", 1], ["d", 1]]

Destructive reject from an array returning the values rejected

Is there a sensible way to do the following:
I want to take an array and select specific items from the array according to conditions, removing them from the array as they go.
(I basically want to split the contents of an array into categories).
array = [1,2,3,4,5,6,7,8]
less_than_three = array.reject_destructively{|v| v<3}
=> [1,2]
array
=> [3,4,5,6,7,8]
more_than_five = array.reject_destructively{|v| v>5}
=> [6,7,8]
array
=> [3,4,5]
I've tried delete_if, select!, reject! and none of them seem to be able to give you the affected items whilst leaving the array with the rest.
Unless I'm going mad, which is entirely possible.
As I understood the question, you do not want to produce two new objects. Here you go:
class Array
def carve!
dup.tap { delete_if &Proc.new } - self
end
end
array = [1,2,3,4,5,6,7,8]
p array.carve! { |v| v < 3 }
#⇒ [1, 2] # returned by Array#carve method
p array
#⇒ [3, 4, 5, 6, 7, 8] # remained in original array
Using this solution, array.__id__ remains the same. And this is the golfiest answer all around :)
You can build your own method for this...
class Array
def extract(&block)
temp = self.select(&block)
self.reject!(&block)
temp
end
end
then...
a = [1, 2, 3, 4, 5]
a.extract{|x| x < 3}
=> [1,2]
p a
=> [3, 4, 5]
EDIT: If you don't want to monkey patch (but monkey patching isn't evil in itself) you can do it with a vanilla method...
def select_from_array(array, &block)
temp = array.select(&block)
array.reject!(&block)
temp
end
array = [1,2,3,4,5,6,7,8]
less_than_three = select_from_array(array){|v| v<3}
=> [1,2]
array
=> [3,4,5,6,7,8]
more_than_five = select_from_array(array){|v| v>5}
=> [6,7,8]
array
=> [3,4,5]
In rails 6 there is a method extract!:
a = [1, 2, 3] #=> [1, 2, 3]
a.extract! { |num| num.odd? } #=> [1, 3]
a #=> [2]
irb(main):001:0> array = [1,2,3,4,5,6,7,8]
=> [1, 2, 3, 4, 5, 6, 7, 8]
irb(main):002:0> array.partition{|v| v < 3}
=> [[1, 2], [3, 4, 5, 6, 7, 8]]
is there a specific reason, why this has to be destructive ?
Will this help
class Array
def reject_destructively(&block)
arr = self.select(&block)
arr.each{ |i| self.delete(i) }
arr
end
end
array = [1,2,3,4,5,6,7,8]
p less_than_three = array.reject_destructively{|v| v<3}
#=> [1,2]
p array
#=> [3,4,5,6,7,8]
p more_than_five = array.reject_destructively{|v| v>5}
#=> [6,7,8]
p array
#=> [3,4,5]
The above code can be simplified further to look like:
class Array
def reject_destructively(&block)
self.select(&block).each{ |i| self.delete(i) }
end
end
Ok. This works, avoids monkey patching, keeps it to one line...etc, but it's damn ugly....
less_than_three = array.dup - array.reject!{|v| v<3}
=> [1,2]
array
=> [3,4,5,6,7,8]
more_than_five = array.dup - array.reject!{|v| v>5}
=> [6,7,8]
array
=> [3,4,5]
module Enumerable
def reject_destructively
array=[]
self.each do |y|
if yield(y)
array<<y
end
end
array.each do |x|
self.delete(x)
end
return array
end
end
array=[10,9,2,1,3,45,52]
print less_than_three = array.reject_destructively{|v| v < 3}
print array
You can use group_by to get all of the elements that satisfy the condition in one group, and all of the rest in the other.
For example
[1,2,3,4,5].group_by{|i| i > 3}
gives
{false=>[1, 2, 3], true=>[4, 5]}
More information is available at http://ruby-doc.org/core-2.1.1/Enumerable.html#method-i-group_by

Delete duplicate elements from Array

I have two arrays, array1 and array2, as follows:
array1 = [ obj11, obj21, obj31 ]
array2 = [ obj21, obj22, obj23 ]
Objects in both arrays are from the same class. I want to check if array1 contains objects that already exist in array2 and delete them.
Let's say obj11 and obj22 are equal. By "equal," I mean they have similar attribute values. Then I would like to delete obj11 from array1, then insert obj21 and obj31 in array2.
I already define equality for attributes in the objects' class from here:
def ==(other)
return self.a == other.a && self.b == other.b
end
The resulting array would be:
array2 = [ obj21, obj22, obj23, obj21, obj31 ]
You can use Array#| ( it does union operation) to remove duplicates too.
array1 = ["dog", "cat", "had"]
array2 = ["big", "fight", "had"]
array1 | array2
# => ["dog", "cat", "had", "big", "fight"]
A quick way to remove duplicate values from multiple arrays is by using uniq
array1 = ["dog", "cat", "had"]
array2 = ["big", "fight", "had"]
new_array = (array1 + array2).uniq # <- ["dog", "cat", "had", "big", "fight"]
uniq removes duplicate values from an array. By combining array1 and array2 together, you can then filter out duplicates between the two of them.
If I want to solve your problem literally, then, I will write something like this:
array1 = [ :obj11, :obj21, :obj31 ]
array2 = [ :obj21, :obj22, :obj23 ]
new_array = (array1 - array2) + array2
p new_array
(array1 - array2) takes those elements from array1 that are also present in array2 and adding array2 to that will get you the final result
Output
[:obj11, :obj31, :obj21, :obj22, :obj23]
(Note: I used symbols as elements of arrays for illustration purposes)
I got the answer. In the following I delete from array1 what is already exist in array2. Equality here works as I define it in the question. Thus, checking if attributes (that have been defined in the method ==) are equal.
array1.delete_if{|elem| array2.any?{|e| e == elem}}
Then add the rest of array1 into array2.
array2 << array1
Then I flatten array2.
array2.flatten!
You could do that as follows:
a2.concat(a1.delete_if { |e| a2.include?(e) })
Here's an example:
class MyClass
attr_reader :a, :b, :c
def initialize(a, b, c)
#a, #b, #c = a, b, c
end
def ==(other)
self.a == other.a && self.b == other.b
end
end
a1 = [MyClass.new('cat', 'bat', 'rat'),
MyClass.new('dog', 'hog', 'pig'),
MyClass.new('jay', 'bee', 'fly'),]
#=> [#<MyClass:0x007fca8407b678 #a="cat", #b="bat", #c="rat">,
# #<MyClass:0x007fca8407bee8 #a="dog", #b="hog", #c="pig">,
# #<MyClass:0x007fca84073ef0 #a="jay", #b="bee", #c="fly">]
a2 = [MyClass.new('fly', 'bee', 'bat'),
MyClass.new('cat', 'bat', 'rat'),
MyClass.new('dog', 'hog', 'cat'),]
#=> [#<MyClass:0x007fca840382d8 #a="fly", #b="bee", #c="bat">,
# #<MyClass:0x007fca840381e8 #a="cat", #b="bat", #c="rat">,
# #<MyClass:0x007fca840380d0 #a="dog", #b="hog", #c="cat">]
a2.concat(a1.delete_if { |e| a2.include?(e) })
#=> [#<MyClass:0x007f96d404ea08 #a="fly", #b="bee", #c="bat">,
# #<MyClass:0x007f96d404e8c8 #a="cat", #b="bat", #c="rat">,
# #<MyClass:0x007f96d404e710 #a="dog", #b="hog", #c="cat">,
# #<MyClass:0x007f96d409d9c8 #a="jay", #b="bee", #c="fly">]
a1
#=> [#<MyClass:0x007f96d409d9c8 #a="jay", #b="bee", #c="fly">]
If we change the first element of a1 from:
MyClass.new('cat', 'bat', 'rat')
to:
MyClass.new('cat', 'rat', 'bat')
we obtain:
a2.concat(a1.delete_if { |e| a2.include?(e) })
#=> [#<MyClass:0x007f89528568c0 #a="fly", #b="bee", #c="bat">,
# #<MyClass:0x007f8952856708 #a="cat", #b="bat", #c="rat">,
# #<MyClass:0x007f89528562d0 #a="dog", #b="hog", #c="cat">,
# #<MyClass:0x007f89519277f0 #a="cat", #b="rat", #c="bat">,
# #<MyClass:0x007f8951927598 #a="jay", #b="bee", #c="fly">]
Another way (using intersection operation):
array1 = [ 1, 2, 3, 4, 5 ]
array2 = [ 2, 3, 4, 5, 6 ]
final_array = array1 + array2
final_array & final_array
This will also delete duplicates. IRB output:
2.2.1 :012 > array1 = [ 1, 2, 3, 4, 5 ]
=> [1, 2, 3, 4, 5]
2.2.1 :013 > array2 = [ 2, 3, 4, 5, 6 ]
=> [2, 3, 4, 5, 6]
2.2.1 :014 > final_array = array1 + array2
=> [1, 2, 3, 4, 5, 2, 3, 4, 5, 6]
2.2.1 :015 > final_array & final_array
=> [1, 2, 3, 4, 5, 6]

Resources