This question already has answers here:
Value inside array having same object id [duplicate]
(2 answers)
Closed 11 months ago.
ruby 1.8.7 (2008-08-11 patchlevel 72) [i586-linux]
From script/console:
>> pairs = Array.new(2).map!{Array.new(2).map!{Array.new(2, Array.new)}}
=> [[[[], []], [[], []]], [[[], []], [[], []]]]
>> pair = Pair.first
=> #<Pair id: 39, charge_card_id: 1, classroom_id: 1, timeslot_id: 1, created_at: "2010-04-01 00:45:37", updated_at: "2010-04-01 00:45:47">
>> pairs[0][0][0] << pair
=> [#<Pair id: 39, charge_card_id: 1, classroom_id: 1, timeslot_id: 1, created_at: "2010-04-01 00:45:37", updated_at: "2010-04-01 00:45:47">]
>> pairs[0][0]
=> [[#<Pair id: 39, charge_card_id: 1, classroom_id: 1, timeslot_id: 1, created_at: "2010-04-01 00:45:37", updated_at: "2010-04-01 00:45:47">], [#<Pair id: 39, charge_card_id: 1, classroom_id: 1, timeslot_id: 1, created_at: "2010-04-01 00:45:37", updated_at: "2010-04-01 00:45:47">]]
>>
So the question is why the pair object appears in pairs[0][0][0] AND in pairs[0][0][1] inspite of I did'n ask it to appear there. Notice I don't nedd to pairs[0][0][0] = pair - I want it as a first array member, so I need to use << or .push.
First of all, you want
pairs = Array.new(2) { Array.new(2) { Array.new(2) { [] }}}
instead of what you got. Two major differences:
you save yourself the #map! calls
in your example, "Array.new(2, Array.new)" is creating one Array which is used for both indices, so you are refering to the same array twice. By using the block syntax, you are ensuring that for every index you are having one separate instance of Array
Now works with:
pairs = Array.new(2).map!{Array.new(2).map!{Array.new(2).map!{Array.new}}}
I think it's because of the deepest arrays was just links to memory pointer.
Array.new(2, Array.new) gives you an array of size 2, with a copy of the same empty array object at both index 0 and index 1. If you want them to be different objects, use map! like you've done at the higher levels.
Related
Say I have this collection of objects:
[
{value: 1, contents: "one"},
{value: 2, contents: "two"},
{value: 3, contents: "three"},
{value: 4, contents: "four"},
{value: 5, contents: "five"}
]
And want to invert the relation of values to contents, like so:
[
{value: 5, contents: "one"},
{value: 4, contents: "two"},
{value: 3, contents: "three"},
{value: 2, contents: "four"},
{value: 1, contents: "five"}
]
I was unable to think of an algorithm to accomplish this. I'm using Ruby, but I'm not so concerned about the code as I am about the method of accomplishing this.
a = [
{value: 1, contents: "one"},
{value: 2, contents: "two"},
{value: 3, contents: "three"},
{value: 4, contents: "four"},
{value: 5, contents: "five"}
]
a.map{|h| h[:value]}.reverse.zip(a.map{|h| h[:contents]})
.map{|k, v| {value: k, contents: v}}
# =>
# [
# {:value=>5, :contents=>"one"},
# {:value=>4, :contents=>"two"},
# {:value=>3, :contents=>"three"},
# {:value=>2, :contents=>"four"},
# {:value=>1, :contents=>"five"}
#]
Or,
a.each_index.map{|i| {value: a[-i - 1][:value], contents: a[i][:contents]}}
Letting arr equal your array of hashes, here are a couple of ways you could do it.
Two passes, no indices
value_vals = arr.map {|h| h[:value]}.reverse
#=> [5, 4, 3, 2, 1]
arr.map { |h| {value: value_vals.shift, contents: h[:contents]}}
#=> [{:value=>5, :contents=>"one"},
# {:value=>4, :contents=>"two"},
# {:value=>3, :contents=>"three"},
# {:value=>2, :contents=>"four"},
# {:value=>1, :contents=>"five"}]
One pass, but not pretty
arr.each_index.map {|i,a| {value: arr[-1-i][:value], contents: arr[i][:contents]}}
#=> [{:value=>5, :contents=>"one"},
# {:value=>4, :contents=>"two"},
# {:value=>3, :contents=>"three"},
# {:value=>2, :contents=>"four"},
# {:value=>1, :contents=>"five"}]
TL;DR
arr.zip(arr.reverse).map {|a, b| a.merge(value: b[:value]) }
Since reverse makes a copy of the array, this will take twice as much memory as other methods—which for most data sets probably isn't an issue at all. But if it is, there's an easy way to avoid it. See the "Bonus" section at the end of my answer.
Building an algorithm
The simplest (and probably best) solution is to walk the array, and for each item get the :value from its counterpart at the other end of the array. You can get an item's "counterpart" by subtracting the item's index from the index of the last item (i.e. the size of the array minus 1). So, if you have five items in an array called arr, the steps of the algorithm looks like this:
end_idx = arr.size - 1 # => 4
new_arr = []
new_arr[0] = { value: arr[end_idx - 0][:value], contents: arr[0][:contents] }
new_arr[1] = { value: arr[end_idx - 1][:value], contents: arr[1][:contents] }
new_arr[2] = { value: arr[end_idx - 2][:value], contents: arr[2][:contents] }
new_arr[3] = { value: arr[end_idx - 3][:value], contents: arr[3][:contents] }
new_arr[4] = { value: arr[end_idx - 4][:value], contents: arr[4][:contents] }
As you can see, every step is the same but with one number incremented, so I bet you already know how to turn this into a loop:
end_idx = arr.size - 1 # => 4
new_arr = []
0.upto(end_idx) do |idx|
new_arr[idx] = { value: arr[end_idx - idx][:value],
contents: arr[idx][:contents] }
end
Easy, and to be honest a perfectly good solution. However, it's not very "Rubyish." How do we make it more Rubyish? I'm glad you asked!
Make it more Rubyish
It's a pretty common situation to want, as an output, an array with one item corresponding to each item in an input array. Because it's so common, we have the Enumerable#map method, which does exactly that: It "maps" every item in an input array (or other Enumerable) to an item in an output array.
map walks over the items of the array, which is just what we need, but it's missing one thing we need: The index of the current item. To get that, we can "chain" the with_index method onto the map method, and now, in addition to the array item itself, the block will be passed a second argument, which is its index. Now we have everything we need:
end_idx = vals.size - 1
arr.map.with_index do |hsh, idx|
{ value: arr[end_idx - idx][:value],
contents: hsh[:contents] }
end
Alternatively, if we don't want to explicitly specify the structure of the hash (as we might if the hash comes from, say, user input or a database query and might have keys other than :value and :contents that we want to preserve without having to keep track of changes to the input form or database schema), we could do this:
end_idx = vals.size - 1
arr.map.with_index do |hsh, idx|
hsh.merge(value: arr[end_idx - idx][:value])
end
But I've saved the best for last.
At last...
arr.zip(arr.reverse_each).map do |a, b|
a.merge(value: b[:value])
end
What's going on here? The Array#zip method takes two arrays and "zips" them up, so e.g. [1, 2, 3].zip([:a, :b, :c]) yields [[1, :a], [2, :b], [3, :c]], so we do that with our array and its reverse (or, rather, an Enumerable that yields successive items from the end of the array, which is what reverse_each returns), and then we use map to set the value at :value from the latter on a copy of the former (using merge).
Bonus: Why reverse_each and not just reverse? Because we can make it lazy! Suppose arr has a billion items. If you call arr.zip(arr.reverse), now you have a (two-dimensional) array with two billion items. Maybe that's not a big deal (or maybe you don't have anywhere near a billion items), but if it is, laziness can help us out:
new_enum = arr.lazy.zip(arr.reverse_each).map do |a, b|
a.merge(value: b[:value])
end
# => #<Enumerator::Lazy: ...>
All we've done is added lazy, and now we get an Enumerator back instead of an array. This won't even do any work until we call, say, each or some other Enumerable method on it, and when we do that it will only operate on as many items as we ask it to. For example, say we just want the first three items:
new_enum.take(3).to_a
# => [ { value: 1000000000, contents: "one" },
# { value: 999999999, contents: "two" },
# { value: 999999998, contents: "three" } ]
Thanks to laziness, we never had to make a copy of the whole array and reverse it (and take up the corresponding amount of memory); we only had to deal with three items.
And if you do want all of the items, but still want to avoid making a copy of the whole array, just call new_enum.to_a.
Is there a starightforward way to achieve array.join(", ") where the comma is only included between elements that exist? I.e., if some elements don't exist in the array, I don't end up getting orphan commas?
Example:
["", nil, "test word", 5, 7, nil, "", nil, "", 7, 6, ""] => "test word, 5, 7, 7, 6"
Edit: Please note that the first method here requires Ruby on Rails. Use the second method for a Ruby-only solution
You can try this to remove both nil and empty strings "" and then join with commas (It removes all nil values with compact, then it does split on "" to create a two-dimensional array where any "" elements in the first array are just empty arrays in the new 2D array, then it does flatten which turns the 2D array back into a normal array but with all the empty arrays removed, and finally it does the join(", ") on this array):
> array.compact.split("").flatten.join(", ")
array = ["", nil, "test word", 5, 7, nil, "", nil, "", 7, 6, ""]
array.compact => ["", "test word", 5, 7, "", "", 7, 6, ""].split("") => [[], ["test word", 5, 7], [], [7, 6], []].flatten => ["test word", 5, 7, 7, 6].join(", ") => "test word, 5, 7, 7, 6"
Edit: Another way would be:
> array.reject(&:blank?).join(", ")
array = ["", nil, "test word", 5, 7, nil, "", nil, "", 7, 6, ""]
array.reject(&:blank?) => ["test word", 5, 7, 7, 6].join(", ") => "test word, 5, 7, 7, 6"
I think you have nil elements in your array. You can do this:
arr.compact.join(", ")
It seems you need to compact the array before join. It returns a copy of array without nil elements.
http://ruby-doc.org/core-2.2.0/Array.html#method-i-compact
[1,nil,2,3].compact.join(', ')
You can also use compact! to remove the nil elements from the source array itself (without making a copy).
This answer will join all array elements except nil from a nested array as well as a flat array:
[1, nil, 3, [:a, nil, :c], 5, nil, 7].flatten.compact.join(',')
=> "1,3,a,c,5,7"
nils are suppressed from the output and do not appear in the joined string, and there are no commas around missing (nil) elements.
I am using ActiveRecord. It has a handy method called group_by. When I use it with my activerecord objects, i get the below hash:
{["junior"]=>[#<Lead id: 1, created_at: "2015-02-13 02:34:39", updated_at: "2015-02-13 02:35:27", case_enabled: true>, #<Lead id: 2, created_at: "2015-02-13 20:48:19", updated_at: "2015-02-13 20:48:19", case_enabled: nil>, ["senior"]=>[#<Lead id: 3, created_at: "2015-02-13 20:48:19", updated_at: "2015-02-13 20:48:19", case_enabled: nil>, #<Lead id: 4, created_at: "2015-02-13 20:49:16", updated_at: "2015-02-13 20:49:16", case_enabled: nil>]}
However, I want a hash with subhashes that contain the collection as an ActiveRecord::Relation and column data. So this is what I come up with:
i = 0
r = group.reduce({}) do |acc, (k,v)|
h = {}
active_record_relation = where(id: v.map(&:id))
h["#{k.first}_collection"] = active_record_relation
h["#{k.first}_columns"] = Classification.where(code: k.first).first.default_fields
acc[i] = h
i += 1
acc
end
And it gives me the results I want:
{0=>{"junior_collection"=>#<ActiveRecord::Relation [# ... ]>, "junior_columns"=>[ ... ]}, 1=>{"senior_collection"=>#<ActiveRecord::Relation [# ... ]>, "senior_columns"=>[ ... ]}}
The fact that I had to add the i variable makes me feel like this is not the ruby way to do this. But I looked at the docs and I didn't find a way to add an index to reduce, since I am already passing a hash into reduce. Is there another way?
Your way is probably good enough but you can avoid separately tracking the index by doing .each.with_index.reduce(...) { |acc, ((k,v),i)| ... }, like so:
h = {'a' => 'b', 'c' => 'd', 'e' => 'f'}
h.each.with_index.reduce('OK') do |acc, ((k, v), i)|
puts "acc=#{acc}, k=#{k}, v=#{v}, i=#{i}"
acc
end
# acc=OK, k=a, v=b, i=0
# acc=OK, k=c, v=d, i=1
# acc=OK, k=e, v=f, i=2
# => "OK"
Not sure if it's more Rubyish than your way =\
I have an array of ids order say
order = [5,2,8,6]
and another array of hash
[{id: 2,name: name2},{id: 5,name: name5}, {id: 6,name: name6}, {id: 8,name: name8}]
I want it sorted as
[{id: 5,name: name5},{id: 2,name: name2}, {id: 8,name: name8}, {id: 6,name: name6}]
What could be best way to implement this? I can implement this with iterating both and pushing it to new array but looking for better solution.
Try this
arr = [
{:id=>2, :name=>"name2"}, {:id=>5, :name=>"name5"},
{:id=>6, :name=>"name6"}, {:id=>8, :name=>"name8"}
]
order = [5,2,8,6]
arr.sort_by { |a| order.index(a[:id]) }
# => [{:id=>5, :name=>"name5"}, {:id=>2, :name=>"name2"},
#{:id=>8, :name=>"name8"}, {:id=>6, :name=>"name6"}]
Enumerable#in_order_of (Rails 7+)
Starting from Rails 7, there is a new method Enumerable#in_order_of.
A quote right from the official Rails docs:
in_order_of(key, series)
Returns a new Array where the order has been set to that provided in the series, based on the key of the objects in the original enumerable.
[ Person.find(5), Person.find(3), Person.find(1) ].in_order_of(:id, [ 1, 5, 3 ])
=> [ Person.find(1), Person.find(5), Person.find(3) ]
If the series include keys that have no corresponding element in the Enumerable, these are ignored. If the Enumerable has additional elements that aren't named in the series, these are not included in the result.
It is not perfect in a case of hashes, but you can consider something like:
require 'ostruct'
items = [{ id: 2, name: 'name2' }, { id: 5, name: 'name5' }, { id: 6, name: 'name6' }, { id: 8, name: 'name8' }]
items.map(&OpenStruct.method(:new)).in_order_of(:id, [5,2,8,6]).map(&:to_h)
# => [{:id=>5, :name=>"name5"}, {:id=>2, :name=>"name2"}, {:id=>8, :name=>"name8"}, {:id=>6, :name=>"name6"}]
Sources:
Official docs - Enumerable#in_order_of.
PR - Enumerable#in_order_of #41333.
Rails 7 adds Enumerable#in_order_of.
I have an array in my Rails 3.1 apps that has made by several objects:
[#<Hardware id: 10, brand_id: 5, model: "B4200", description: "Stampante OKI B4200", typology_id: 3, sub_typology_id: 10, created_at: nil, updated_at: nil>, #<Hardware id: 19, brand_id: 9, model: "JetLab", description: "JetLab - 600 ", typology_id: 5, sub_typology_id: nil, created_at: nil, updated_at: nil>]
and I want remove one object from this array. Using Rails console, I've tried to do something such as (try to remove first object):
array.pop=#<Hardware id: 10, brand_id: 5, model: "B4200", description: "Stampante OKI B4200", typology_id: 3, sub_typology_id: 10, created_at: nil, updated_at: nil>
but it doesn't work. How can I do this?
UPDATED: My goal isn't to pop last element on array, but a generic object (everywhere inside array) that I should find using mysql search query.
my_array = [ 1, 2, 3 ]
item = my_array.pop
puts item
# => 3
puts my_array
# => [ 1, 2 ]
You probably want to use the Array#delete function
an_array = [1,3,4]
an_array.delete(3)
# => 3
puts an_array
# => [1,4]
Check it out in the Ruby documentation:
http://www.ruby-doc.org/core-1.9.3/Array.html#method-i-delete