Ruby - Pushing Hash to Array - arrays

While iterating, I am saving some data to a hash each time. Within the same loop, I push the hash to an array.
The below code does not work, the last hash object overwrites all the other ones in the array.
playlists = []
aPlaylist = {}
while (count < 3)
#some code... produces the hash "aPlaylist"
playlist << aPlaylist
end
The code below does work. Why, and what is the difference?
playlists = []
while (count < 3)
aPlaylist = {}
#some code... produces the hash "aPlaylist"
playlist << aPlaylist
end
Here are the correct and wrong outputs (converted to csv):
http://imgur.com/a/rjmBA.

Because, in the first case, the object is same that is on 0, 1, and 2nd index.
playlist = []
aPlaylist = {}
count = 0
while (count < 3)
#some code... produces the hash "aPlaylist"
playlist << aPlaylist
puts aPlaylist.object_id
count += 1
end
#=> 2048
#=> 2048
#=> 2048
While in second case it changes:
playlist = []
count = 0
while (count < 3)
aPlaylist = {}
#some code... produces the hash "aPlaylist"
playlist << aPlaylist
puts aPlaylist.object_id
count += 1
end
#=> 2048
#=> 2038
#=> 2028
Which is why from second case when you make changes to hash, it does not get reflected in all places in array.
Read this stackoverflow answer for more detail.

aPlaylist = {} creates a hash and aPlaylist variable holds a pointer to the hash object.
In your first example you edit only this one hash object.
aPlaylist = {}
count = 0
while (count < 3)
puts aPlaylist.object_id
count += 1
end
#=> 70179174789100
#=> 70179174789100
#=> 70179174789100
In your second example you create a new hash object within each iteration. That's way this code works.
count = 0
while (count < 3)
aPlaylist = {}
puts aPlaylist.object_id
count += 1
end
#=> 70179182889040
#=> 70179182888980
#=> 70179182888920
Have a look to the printed object-ids.

I think an idiomatic Ruby approach would be something like ...
playlist = 0.upto(2).map{|count| something_that_returns_a_hash }
... or ...
playlist = (0..2).map{|count| something_that_returns_a_hash }
Hence:
0.upto(2).map{|count| {count => count} }
[{0=>0}, {1=>1}, {2=>2}]

Related

Find difference between two arrays considering duplicates [duplicate]

[1,2,3,3] - [1,2,3] produces the empty array []. Is it possible to retain duplicates so it returns [3]?
I am so glad you asked. I would like to see such a method added to the class Array in some future version of Ruby, as I have found many uses for it:
class Array
def difference(other)
h = other.each_with_object(Hash.new(0)) { |e,h| h[e] += 1 }
reject { |e| h[e] > 0 && h[e] -= 1 }
end
end
A description of the method and links to some of its applications are given here.
By way of example:
a = [1,2,3,4,3,2,4,2]
b = [2,3,4,4,4]
a - b #=> [1]
a.difference b #=> [1,2,3,2]
Ruby v2.7 gave us the method Enumerable#tally, allowing us to replace the first line of the method with
h = other.tally
As far as I know, you can't do this with a built-in operation. Can't see anything in the ruby docs either. Simplest way to do this would be to extend the array class like this:
class Array
def difference(array2)
final_array = []
self.each do |item|
if array2.include?(item)
array2.delete_at(array2.find_index(item))
else
final_array << item
end
end
end
end
For all I know there's a more efficient way to do this, also
EDIT:
As suggested by user2864740 in question comments, using Array#slice! is a much more elegant solution
def arr_sub(a,b)
a = a.dup #if you want to preserve the original array
b.each {|del| a.slice!(a.index(del)) if a.include?(del) }
return a
end
Credit:
My original answer
def arr_sub(a,b)
b = b.each_with_object(Hash.new(0)){ |v,h| h[v] += 1 }
a = a.each_with_object([]) do |v, arr|
arr << v if b[v] < 1
b[v] -= 1
end
end
arr_sub([1,2,3,3],[1,2,3]) # a => [3]
arr_sub([1,2,3,3,4,4,4],[1,2,3,4,4]) # => [3, 4]
arr_sub([4,4,4,5,5,5,5],[4,4,5,5,5,5,6,6]) # => [4]

How to split a hash into multiple arrays of keys based the values not exceeding a certain sum in each array?

I have a large hash, where the keys are names, like "Alex", and the values are numeric, like "100".
How can I split this hash into multiple arrays that contain the keys, of which the sum of values doesn't exceed a certain threshold value?
Example
I have the hash
{"Alex"=>50, "Bamby"=>100, "Jordan"=>300, "Ger"=>700, "Aus"=>500, "Can"=>360}
and I want to split it into packs of 1000 from the beginning (doesn't have to be from the beginning but would be nice),
meaning:
array1 = ["Alex", "Bamby", "Jordan"] # not "Ger" bc it would exceed the 1000 in sum
array2 = ["Ger"] # not the Aus because it again would exceed the 1000
array3 = ["Aus", "Can"]
The best solution would actually be to have it optimized in a way that the code makes arrays all close or equal 1000 but that's the next step I guess...
Thank you so much in advance! ~Alex
h = {"Alex"=>50, "Bamby"=>100, "Jordan"=>300, "Ger"=>700, "Aus"=>500, "Can"=>360}
tot = 0
h.keys.slice_before { |k| (tot += h[k]) > 1000 ? tot = h[k] : false }.to_a
#=> [["Alex", "Bamby", "Jordan"], ["Ger"], ["Aus", "Can"]]
Not that if tot > 1000 the block returns a truthy value (h[k]) and the parentheses around tot += h[k] are necessary.
See Enumerable#slice_before.
original = {"Alex"=>50, "Bamby"=>100, "Jordan"=>300, "Ger"=>700, "Aus"=>500, "Can"=>360}
chunked = original.inject([]) do |array, (key, value)|
array << {} unless array.any?
if array.last.values.sum + value <= 1_000
array.last.merge!(key => value)
else
array << { key => value }
end
array
end
# => [{"Alex"=>50, "Bamby"=>100, "Jordan"=>300}, {"Ger"=>700}, {"Aus"=>500, "Can"=>360}]
You can iterate over the elements inside the hash like this, the explain is in the comments:
hash={"Alex"=>50, "Bamby"=>100, "Jordan"=>300, "Ger"=>700, "Aus"=>500, "Can"=>360}
rs = [] # the outside array
rss = [] # the array inside the array
m = 0 # check if the sum of nexts are 1000
hash.each do |key, n|
if m+n <= 1000 # if the counter + the next element < 1000
m += n # then add it to the counter
rss << key # add the key to the actual array
else
rs << rss #else m is equal or bigger than 1000, so, I add all the keys to the main array
m=n # the element that overcomes m to 1000, becomes the first count now
rss=[key] # And that key is the first element of a new array
end
end
rs << rss #Importan! at the end, the final array need to be added outside the loop
print rs
Result _
=> [["Alex", "Bamby", "Jordan"], ["Ger"], ["Aus", "Can"]]

How to find the index of the last array element

In a situation where you have to deal with array indices (and can't just use an enumerator), how do you find the index of the last element?
These two options come to mind, but I'm hoping there's an answer that is more self documenting (like Array#last(), but for the index instead of the element):
a = [0, 1, 2, 3]
# option 1
last_index = a.length - 1
# option 2
last_index = a.rindex { true }
arr = [1,2,3,4,5,6]
def last_index(arr)
counter = -1
arr.each do |i|
i
counter += 1
end
counter
end
last_index(arr)

Ruby Input Array, Output Array without duplicates

# creating empty array (array1)
array1 = []
i = 0
# taking 10 numbers from user and storing them in the array
while array1.size < 10
print "Enter a number: "
array[i] = gets.chomp.to_i
i += 1
end
# pushing only the unique elements into a new array (array2)
k = 0
j = 0
array2 = []
while k < array1.size
if array1[k] != array1[j]
if array2.include? (array1[k])
puts "Element already exists"
else
array2.push(array1[k])
end
j += 1
end
k += 1
end
puts array2
It only works in some situations, but at all times, the first element that is unique is not being added to the new array for some reason, should I be starting j or k at 1, rather than 0?
I know that i can use .uniq and turn the array into a set and then back to an array, or just intersect the array with &, but i wanted to make it without ruby "shortcuts", any advice?
Sure.
k and j are both equal to 0 at startup.
So array1[k] == array1[j] is true, and you get k+=1.
k is now 1, and you missed the first element of array1.
Nothing specially Rubyish about this solution :
array2 = []
array1 = [1, 2, 3, 3, 2, 4, 5, 6]
i=0
while i<array1.size do
element = array1[i]
if !array2.include?(element) then
array2.push(element)
end
i += 1
end
puts array2.inspect

Ruby: Find index of next match in array, or find with offset

I want to find further matches after Array#find_index { |item| block } matches for the first time. How can I search for the index of the second match, third match, and so on?
In other words, I want the equivalent of the pos argument to Regexp#match(str, pos) for Array#find_index. Then I can maintain a current-position index to continue the search.
I cannot use Enumerable#find_all because I might modify the array between calls (in which case, I will also adjust my current-position index to reflect the modifications). I do not want to copy part of the array, as that would increase the computational complexity of my algorithm. I want to do this without copying the array:
new_pos = pos + array[pos..-1].find_index do |elem|
elem.matches_condition?
end
The following are different questions. They only ask the first match in the array, plus one:
https://stackoverflow.com/questions/11300886/ruby-how-to-find-the-next-match-in-an-array
https://stackoverflow.com/questions/4596517/ruby-find-next-in-array
The following question is closer, but still does not help me, because I need to process the first match before continuing to the next (and this way also conflicts with modification):
https://stackoverflow.com/questions/9925654/ruby-find-in-array-with-offset
A simpler way to do it is just:
new_pos = pos
while new_pos < array.size and not array[new_pos].matches_condition?
new_pos += 1
end
new_pos = nil if new_pos == array.size
In fact, I think this is probably better than my other answer, because it's harder to get wrong, and there's no chance of future shadowing problems being introduced from the surrounding code. However, it's still clumsy.
And if the condition is more complex, then you end up needing to do something like this:
new_pos = pos
# this check is only necessary if pos may be == array.size
if new_pos < array.size
prepare_for_condition
end
while new_pos < array.size and not array[new_pos].matches_condition?
new_pos += 1
if new_pos < array.size
prepare_for_condition
end
end
new_pos = nil if new_pos == array.size
Or, God forbid, a begin ... end while loop (although then you run into trouble with the initial value of new_pos):
new_pos = pos - 1
begin
new_pos += 1
if new_pos < array.size
prepare_for_condition
end
end while new_pos < array.size and not array[new_pos].matches_condition?
new_pos = nil if new_pos == array.size
This may seem horrible. However, supposing prepare_for_condition is something that keeps being tweaked in small ways. Those tweaks will eventually get refactored; however, by that time, the output of the refactored code will also end up getting tweaked in small ways that don't belong with the old refactored code, but do not yet seem to justify refactoring of their own - and so on. Occasionally, someone will forget to change both places. This may seem pathological; however, in programming, as we all know, the pathological case has a habit of occurring only too often.
Here is one way this can be done. We can define a new method in Array class that will allow us to find indexes that match a given condition. The condition can be specified as block that returns boolean.
The new method returns an Enumerator so that we get the benefit of many of the Enumerator methods such next, to_a, etc.
ary = [1,2,3,4,5,6]
class Array
def find_index_r(&block)
Enumerator.new do |yielder|
self.each_with_index{|i, j| yielder.yield j if block.call(i)}
end
end
end
e = ary.find_index_r { |r| r % 2 == 0 }
p e.to_a #=> [1, 3, 5]
p e.next
#=> 1
p e.next
#=> 3
ary[2]=10
p ary
#=> [1, 2, 10, 4, 5, 6]
p e.next
#=> 5
e.rewind
p e.next
#=> 1
p e.next
#=> 2
Note: I added a new method in Array class for demonstration purpose. Solution can be adapted easily to work without the monkey-patching
Of course, one way to do it would be:
new_pos = pos + (pos...array.size).find_index do |index|
elem = array[index]
elem.matches_condition?
end
However, this is clumsy and easy to get wrong. For example, you may forget to add pos. Also, you have to make sure elem isn't shadowing something. Both of these can lead to hard-to-trace bugs.
I find it hard to believe that an index argument to Array#find_index and Array#index still hasn't made it into the language. However, I notice Regexp#match(str,pos) wasn't there until version 1.9, which is equally surprising.
Suppose
arr = [9,1,4,1,9,36,25]
findees = [1,6,3,6,3,7]
proc = ->(n) { n**2 }
and for each element n in findees we want the index of the first unmatched element m of arr for which proc[n] == m. For example, if n=3, then proc[3] #==> 9, so the first matching index in arr would be 0. For the next n=3 in findees, the first unmatched match in arr is at index 4.
We can do this like so:
arr = [9,1,4,1,9,36,25]
findees = [1,6,3,6,3,7]
proc = ->(n) { n**2 }
h = arr.each_with_index.with_object(Hash.new { |h,k| h[k] = [] }) { |(n,i),h| h[n] << i }
#=> {9=>[0, 4], 1=>[1, 3], 4=>[2], 36=>[5], 25=>[6]}
findees.each_with_object([]) { |n,a| v=h[proc[n]]; a << v.shift if v }
#=> [1, 5, 0, nil, 4, nil]
We can generalize this into a handy Array method as follow:
class Array
def find_indices(*args)
h = each_with_index.with_object(Hash.new {|h,k| h[k] = []}) { |(n,i),h| h[n] << i }
args.each_with_object([]) { |n,a| v=h[yield n]; a << v.shift if v }
end
end
arr.find_indices(*findees) { |n| n**2 }
#=> [1, 5, 0, nil, 4, nil]
arr = [3,1,2,1,3,6,5]
findees = [1,6,3,6,3,7]
arr.find_indices(*findees, &:itself)
#=> [1, 5, 0, nil, 4, nil]
My approach is not much different from the others but perhaps packaged cleaner to be syntactically similar to Array#find_index . Here's the compact form.
def find_next_index(a,prior=nil)
(((prior||-1)+1)...a.length).find{|i| yield a[i]}
end
Here's a simple test case.
test_arr = %w(aa ab ac ad)
puts find_next_index(test_arr){|v| v.include?('a')}
puts find_next_index(test_arr,1){|v| v.include?('a')}
puts find_next_index(test_arr,3){|v| v.include?('a')}
# evaluates to:
# 0
# 2
# nil
And of course, with a slight rewrite you could monkey-patch it into the Array class

Resources