I want to create an array of hashes like this:
[
{"start"=>1, "end"=>2},
{"start"=>2, "end"=>3},
{"start"=>3, "end"=>4},
{"start"=>4, "end"=>5},
{"start"=>5, "end"=>6}
]
When I try this code:
foo = 1
bar = 2
hash = {}
array = []
5.times do
hash['start'] = foo
hash['end'] = bar
array << hash
foo += 1
bar += 1
end
the hash values change inside array while looping and hashes are added to it. array becomes:
[
{"start"=>5, "end"=>6},
{"start"=>5, "end"=>6},
{"start"=>5, "end"=>6},
{"start"=>5, "end"=>6},
{"start"=>5, "end"=>6}
]
Why does this happen when:
foo = 1
array = []
5.times do
array << foo
foo += 1
end
array # => [1, 2, 3, 4, 5]
does not change the numeral inside array during the loop?
That is because a hash is mutable. if you have foo = {"start" => 1}, and do foo["start"] += 1, then, foo still points to the same hash although it is modified to {"start" => 2}. It does not change the reference. If you have multiple copies of this same object in an array and change modify one of them, then all of them will be modified.
On the other hand, a numeral is not mutable; if you had foo = 1, and do foo += 1, then foo will now point to 2, which is a different object from 1.
You could create a new hash each time.
foo = 1
array = []
5.times do
array << { 'start' => foo, 'end' => foo + 1 }
foo += 1
end
Use:
array << hash.dup
instead of:
array << hash
because of you've added here just references to hash, not the hashs themselves.
You should change your code to create a new hash in every loop iteration:
foo = 1
bar = 2
array = []
5.times do
hash = {}
hash['start'] = foo
hash['end'] = bar
array << hash
foo += 1
bar += 1
end
puts array
Otherwise you are always changing the same object, that's the reason you end with the same hash as array elements.
As a quick literature on subject taken from here:
Ruby variables hold references to objects and the = operator copies
the references. Also, a self assignment such as a += b is actually
translated to a = a + b. Therefore it may be advisable to be aware
whether in a certain operation you are actually creating a new object
or modifying an existing one.
For example, string << "another" is faster than string += "another"
(no extra object creation), so you would be better off using any
class-defined update-method (if that is really your intention), if it
exists. However, notice also the "side effects" on all other variables
that refer to the same object:
a = 'aString'
c = a
a += ' modified using +='
puts c # -> "aString"
a = 'aString'
c = a
a << ' modified using <<'
puts c # -> "aString modified using <<"
Related
I have built a version of mastermind that checks a user's input and provides feedback based on how close the user's guess was to the winning sequence. If you're not familiar with the game, you get feedback indicating how many of your characters were guessed correctly at the same index and how many characters guessed are in the sequence, but at the wrong index. If there are duplicates in the guess, then you would not count the extra values unless they correspond to the same number of duplicates in the secret code.
Example: If the sequence is ["G","G","G","Y"] and the user guesses ["G", "Y","G","G"] then you'd want to return 2 for items at the same index and 2 for items at different indexes that are included in the secret sequence.
Another example: If the sequence is ["X","R","Y","T"] and the user guesses ["T","T","Y","Y"] then you'd return 1 for items at the same index 1 for the character guessed that is in the sequence but at the wrong index.
Anyway, to me this is not a simple problem to solve. Here's the code I used to get it to work, but it's not elegant. There must be a better way. I was hoping someone can tell me what I'm missing here?? New to Ruby...
def index_checker(input_array, sequence_array)
count = 0
leftover_input = []
leftover_sequence = []
input.each_with_index do |char, idx|
if char == sequence[idx]
count += 1
else
leftover_input << char
leftover_sequence << sequence[idx]
end
end
diff_index_checker(leftover_input, leftover_sequence, count)
end
def diff_index_checker(input, sequence, count)
count2 = 0
already_counted = []
input.each do |char|
if sequence.include?(char) && !already_counted.include?(char)
count2 += 1
already_counted << char
end
end
[count, count2]
end
Here's a clean Ruby solution, written in idiomatic Ruby object-oriented style:
class Mastermind
def initialize(input_array, sequence_array)
#input_array = input_array
#sequence_array = sequence_array
end
def matches
[index_matches, other_matches]
end
def results
[index_matches.size, other_matches.size]
end
private
attr_reader :input_array, :sequence_array
def index_matches
input_array.select.with_index { |e, i| e == sequence_array[i] }
end
def other_matches
non_exact_input & non_exact_sequence
end
def non_exact_input
array_difference(input_array, index_matches)
end
def non_exact_sequence
array_difference(sequence_array, index_matches)
end
# This method is based on https://stackoverflow.com/a/3852809/5961578
def array_difference(array_1, array_2)
counts = array_2.inject(Hash.new(0)) { |h, v| h[v] += 1; h }
array_1.reject { |e| counts[e] -= 1 unless counts[e].zero? }
end
end
You would use this class as follows:
>> input_array = ["G","G","G","Y"]
>> sequence_array = ["G", "Y","G","G"]
>> guess = Mastermind.new(input_array, sequence_array)
>> guess.results
#> [2, 2]
>> guess.matches
#> [["G", "G"], ["G", "Y"]]
Here's how it works. First everything goes into a class called Mastermind. We create a constructor for the class (which in Ruby is a method called initialize) and we have it accept two arguments: input array (the user guess), and sequence array (the answer).
We set each of these arguments to an instance variable, which is indicated by its beginning with #. Then we use attr_reader to create getter methods for #input_array and #sequence_array, which allows us to get the values by calling input_array and sequence_array from any instance method within the class.
We then define two public methods: matches (which returns an array of exact matches and an array of other matches (the ones that match but at the wrong index), and results (which returns a count of each of these two arrays).
Now, within the private portion of our class, we can define the guts of the logic. Each method has a specific job, and each is named to (hopefully) help a reader understand what it is doing.
index_matches returns a subset of the input_array whose elements match the sequence_array exactly.
other_matches returns a subset of the input_array whose elements do not match the sequence_array exactly, but do match at the wrong index.
other_matches relies on non_exact_input and non_exact_sequence, each of which is computed using the array_difference method, which I copied from another SO answer. (There is no convenient Ruby method that allows us to subtract one array from another without deleting duplicates).
Code
def matches(hidden, guess)
indices_wo_match = hidden.each_index.reject { |i| hidden[i] == guess[i] }
hidden_counts = counting_hash(hidden.values_at *indices_wo_match)
guess_counts = counting_hash(guess.values_at *indices_wo_match)
[hidden.size - indices_wo_match.size, guess_counts.reduce(0) { |tot, (k, cnt)|
tot + [hidden_counts[k], cnt].min }]
end
def counting_hash(arr)
arr.each_with_object(Hash.new(0)) { |s, h| h[s] += 1 }
end
Examples
matches ["G","G","G","Y"], ["G", "Y","G","G"]
#=> [2, 2]
matches ["X","R","Y","T"] , ["T","T","Y","Y"]
#=> [1, 1]
Explanation
The steps are as follows.
hidden = ["G","G","G","Y"]
guess = ["G", "Y","G","G"]
Save the indices i for which hidden[i] != guess[i].
indices_wo_match = hidden.each_index.reject { |i| hidden[i] == guess[i] }
#=> [1, 3]
Note that the number of indices for which the values are equal is as follows.
hidden.size - indices_wo_match.size
#=> 2
Now compute the numbers of remaining elements of guess that pair with one of the remaining values of hidden by having the same value. Begin by counting the numbers of instances of each unique element of hidden and then do the same for guess.
hidden_counts = counting_hash(hidden.values_at *indices_wo_match)
#=> {"G"=>1, "Y"=>1}
guess_counts = counting_hash(guess.values_at *indices_wo_match)
#=> {"Y"=>1, "G"=>1}
To understand how counting_hash works, see Hash::new, especially the explanation of the effect of providing a default value as an argument of new. In brief, if a hash is defined h = Hash.new(3), then if h does not have a key k, h[k] returns the default value, here 3 (the hash is not changed).
Now compute the numbers of matches of elements of guess that were not equal to the value of hidden at the same index and which pair with an element of hidden that have the same value.
val_matches = guess_counts.reduce(0) do |tot, (k, cnt)|
tot + [hidden_counts[k], cnt].min
end
#=> 2
Lastly, return the values of interest.
[hidden.size - indices_wo_match.size, val_matches]
#=> [2, 2]
In the code presented above I have substituted out the variable val_matches.
With Ruby 2.4+ one can use Enumerable#sum to replace
guess_counts.reduce(0) { |tot, (k, cnt)| tot + [hidden_counts[k], cnt].min }
with
guess_counts.sum { |k, cnt| [hidden_counts[k], cnt].min }
def judge(secret, guess)
full = secret.zip(guess).count { |s, g| s == g }
semi = secret.uniq.sum { |s| [secret.count(s), guess.count(s)].min } - full
[full, semi]
end
Demo:
> judge(["G","G","G","Y"], ["G","Y","G","G"])
=> [2, 2]
> judge(["X","R","Y","T"], ["T","T","Y","Y"])
=> [1, 1]
A shorter alternative, though I find it less clear:
full = secret.zip(guess).count(&:uniq!)
I prefer my other answer for its simplicity, but this one would be faster if someone wanted to use this for arrays larger than Mastermind's.
def judge(secret, guess)
full = secret.zip(guess).count { |s, g| s == g }
pool = secret.group_by(&:itself)
[full, guess.count { |g| pool[g]&.pop } - full]
end
Demo:
> judge(["G","G","G","Y"], ["G","Y","G","G"])
=> [2, 2]
> judge(["X","R","Y","T"], ["T","T","Y","Y"])
=> [1, 1]
I define a variable holding an empty object before pushing elements into it in an each loop (or other types of loop) like so:
foo = []
collection.each do |item|
foo << item
end
foo
or like this:
foo = []
count = 0
collection.each do |item|
count += 1
raise ArgumentError if count > 10
foo << item
end
foo
However, foo or count appears too often and clutters the code. Is there a method to shorten this chunk of code? I want to believe that the first foo can be placed inside the loop to run once.
You can use inject method:
foo = collection.inject([]) {|sum, item| sum << item }
single-line block just a Ruby style, prefer {...} over do...end for single-line blocks. Either multi-lines or just single-line blocks, you also can use the do...end, but for multi-lines block, do...end is better.
foo = collection.inject([]) do |sum, item|
sum << item
end # This is ok, but `{...}` looks better.
multi lines:
foo = collection.inject([]) do |sum, item|
# line 1
# line 2
# and more
end
For Ruby style, you can get more info from The Ruby Style Guide
An alternative that I use in scenarios like this is each_with_obejct.
collection = ['string', 1, []]
foo =
collection.each_with_object([]) do |item,array|
array << item
end
#=> ['string', 1, []]
Likewise, if you need an index, you can chain each_with_index with each_with_object like so, but it becomes slightly more complicated:
collection = ['string', 1, []]
foo =
collection.each_with_index.with_object([]) do |item_and_index,array|
item, index = item_and_index
raise ArgumentError if index > 10
array << item
end
#=> ['string', 1, []]
The item_and_index becomes an array holding the item from collection in the 0 index, and the index of the item in the 1 index each time it loops.
I have code like this:
combinedbooks = [[
"0000A|0000B",
"0000A|0000D",
"0000B|0000D"
]]
h = Hash[combinedbooks.map {|x| [x, 1]}]
The result is:
{["0000A|0000B", "0000A|0000D", "0000B|0000D"]=>1}
What I want to have is the following:
{["0000A|0000B"]=>1, ["0000A|0000D"]=>1, ["0000B|0000D"]=>1}
I cant figure out whats the problem, I believe that there is a problem with the array declaration but im not sure about it
If you want the keys to be single-item arrays, make them arrays:
arr = ["0000A|0000B", "0000A|0000D", "0000B|0000D"]
Hash[arr.map { |x| [[x], 1] }]
# => {["0000A|0000B"]=>1, ["0000A|0000D"]=>1, ["0000B|0000D"]=>1}
The format of your input has changed to a doubly-nested array. If that's accurate, simply use my solution, but map the first element of your array instead of the top-most array:
combinedbooks = [[ "0000A|0000B", "0000A|0000D", "0000B|0000D" ]]
Hash[combinedbooks[0].map { |x| [[x], 1] }]
# => {["0000A|0000B"]=>1, ["0000A|0000D"]=>1, ["0000B|0000D"]=>1}
Im not sure how to just push elements of one array into another without creating an array of arrays
There are two ways people usually append to an array, and it's important to understand the difference between them. Meditate on these:
Append/push an array to another array using << results in a sub-array:
foo = []
foo << [1]
foo # => [[1]]
Concatenate/Add an array to another array using += results in the elements of the second array being appended, not the array itself:
foo = []
foo += [1]
foo # => [1]
I want to define a method which can take an optional amount of arguments and hashes, like so
def foo(*b, **c)
2.times.map.with_index { |i|
new_hash, new_array = {}, b
c.map { |key, value| new_hash[key] = value[i] unless value[i].nil? }
new_array << new_hash if new_hash.length > 0
send(:bar, new_array)
}
end
def bar(*b)
p b
end
If I've understood the splat and double splat operators correctly (which I doubt), then this should send the array b to the bar method, and only adding the new_hash from foo if it contains something. However, something weird happens - I'll try and illustrate with some snippets below
# invoking #foo
foo(a, key: 'value')
# first iteration of loop in #foo
# i is 0
# b is []
# c is { :key => ['value1'] }
# send(:bar, new_array) => send(:bar, [{:key => 'value1'}])
# bar yields: [{:key => 'value1'}]
Now, however, something happens
# second iteration of loop in #foo
# i is 1
# b is [:key => 'value1'] <---- why?
# c is { :key => ['value1']
Why has the value of b changed inside the loop of foo?
edit Updated the code to reflect a new array is created for each iteration
new_hash, new_array = {}, b
This doesn't create a copy of b. Now new_array and b point to the same object. Modifying one in-place will modify the other.
new_array << new_hash
That modifies new_array (and thus b) in place, so the new element remains on the next iteration. Use something like +, which creates a copy:
send(:bar, *(b + (new_hash.empty? ? [] : [new_hash])))
I have written a simple screen scraping script and at the end of the script I am attempting to create an array of arrays in preparation for an activerecord insert. The structure I am trying to achieve is as follows:
Array b holds a series of 10 element arrays
b = [[0,1,2,3,4,5,6,7,8,9],[0,1,2,3,4,5,6,7,8,9],[0,1,2,3,4,5,6,7,8,9]]
Currently when I try to print out Array b the array is empty. I'm still fairly new to ruby and programming for that matter and would appreciate any feedback on how to get values in array b and to improve the overall script. Script follows:
require "rubygems"
require "celerity"
t = 0
r = 0
c = 0
a = Array.new(10)
b = Array.new
#initialize Browser
browser = Celerity::IE.new
#goto Login Page
browser.goto('http://www1.drf.com/drfLogin.do?type=membership')
#input UserId and Password
browser.text_field(:name, 'p_full_name').value = 'username'
browser.text_field(:name, 'p_password').value = 'password'
browser.button(:index, 2).click
#goto DRF Frontpage
browser.goto('http://www.drf.com/frontpage')
#goto DRF Entries
browser.goto('http://www1.drf.com/static/indexMenus/eindex.html')
#click the link to access the entries
browser.link(:text, '09').click
browser.tables.each do |table|
t = t + 1
browser.table(:index, t).rows.each do |row|
r = r + 1
browser.table(:index, t).row(:index, r).cells.each do |cell|
a << cell.text
end
b << a
a.clear
end
r = 0
end
puts b
browser.close
This a minor rewrite of your main loop to a more Ruby-like way.
b = Array.new
browser.tables.each_with_index do |table, t|
browser.table(:index, 1 + t).rows.each_with_index do |row, r|
a = Array.new(10)
browser.table(:index, 1 + t).row(:index, 1 + r).cells.each do |cell|
a << cell.text
end
b << a
end
end
puts b
I moved the array initializations to immediately above where they'll be needed. That's a programmer-choice thing of course.
Rather than create two counter variables up above, I switched to using each_with_index which adds an index variable, starting at 0. To get your 1-offsets I add 1.
They're not big changes but they add up to a more cohesive app.
Back to the original code: One issue I see with it is that you create your a array outside the loops then reuse it when you assign to b. That means that each time the same array gets used, but cleared and values stored to it. That will cause the previous array values to be overwritten, but resulting in duplicated arrays in b.
require 'pp'
a = []
b = []
puts a.object_id
a[0] = 1
b << a
a.clear
a[0] = 2
b << a
puts
pp b
b.each { |ary| puts ary.object_id }
# >> 2151839900
# >>
# >> [[2], [2]]
# >> 2151839900
# >> 2151839900
Notice that the a array gets reused repeatedly.
If I change a to a second array there are two values for b and a is two separate objects:
require 'pp'
a = []
b = []
puts a.object_id
a[0] = 1
b << a
a = []
a[0] = 2
b << a
puts
pp b
b.each { |ary| puts ary.object_id }
# >> 2151839920
# >>
# >> [[1], [2]]
# >> 2151839920
# >> 2151839780
Hopefully that'll help you avoid the problem in the future.
Your problem is there at the end:
b << a # push a *reference to* a onto b
a.clear # clear a; the reference in b now points to an empty array!
If you remove the reference to a.clear and start that loop with:
browser.tables.each do |table|
t = t + 1
a = []
...you'll be golden (at least as far as your array-building goes)
I can't tell from your question whether you have multiple tables or not. Maybe just one? In which case:
b = browser.tables.first.rows.map {|row| row.cells.map(&:text)}
If you have multiple tables, and really want an array (tables) of arrays (rows) of arrays (cells), that would be
b = browser.tables.map {|t| t.rows.map {|row| row.cells.map(&:text)}}
And if the tables all have the same structure and you just want all the rows as if they were in one big table, you can do:
b = browser.tables.map {|t| t.rows.map {|row| row.cells.map(&:text)}}.flatten(1)