Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 2 years ago.
Improve this question
This is my code:
def random_card
cards = ["two", "three", "four", "five", "six", "seven",
"eight", "nine", "ten",
"jack", "queen", "king", "ace"]
cards[rand(13)]
end
def score(hand_score)
random_card_numbers = {
"two" => 2,
"three" => 3,
"four" => 4,
"five" => 5,
"six" => 6,
"seven" => 7,
"eight" => 8,
"nine" => 9,
"ten" => 10,
"jack" => 10,
"queen" => 10,
"king" => 10,
"ace" => 11
}
new_score = 0
hand_score.each do |x|
new_score += random_card_numbers[x]
end
new_score
end
def move(hand_score)
while true do
puts "Enter hit or stick"
user_input = gets.chomp
if user_input == "hit"
hand_score.push(random_card)
puts "Score so far: #{score(hand_score)}"
elsif user_input == "stick"
if score(hand_score) <= 21
puts"You scored: #{score(hand_score)}"
elsif score(hand_score) > 21
puts "You busted with #{score(hand_score)}"
end
break
end
end
end
def run_game
overall = []
move(overall)
end
I get the following error when I run rspec with with the automated tests.
The program works fine but is not working with the automated tests. I looked at the parameters of move but dont quite understand why is it failing. I kinda think it is to do with the where I have put the while loops and if statements. If anyone can help it would be much appreciated.
Failure/Error: expect(move).to eq("hit")
ArgumentError:
wrong number of arguments (given 0, expected 1)
# ./questions/question_1.rb:85:in `move'
In the run_game method you do you call move with the correct number of arguments. But your error comes from the line in the test:
expect(move).to eq("hit")
here you're calling move directly without arguments, hence the error
Related
I'm testing a single-player blackjack program. I've made a score method that converts card names into integers, and then returns the total score from the hand array.
def random_card
cards = ["two", "three", "four", "five", "six", "seven",
"eight", "nine", "ten",
"jack", "queen", "king", "ace"]
cards[rand(13)]
end
hand = ["four", "queen", "ace", "seven"]
def score(hand)
values = {
"two" => 2,
"three" => 3,
"four" => 4,
"five" => 5,
"six" => 6,
"seven" => 7,
"eight" => 8,
"nine" => 9,
"ten" => 10,
"jack" => 10,
"queen" => 10,
"king" => 10,
"ace" => 11
}
p hand
final_score = 0
i = 0
while i < hand.length
hand[i] = values[hand[i]]
i += 1
end
hand.each do |card|
final_score += card
end
if final_score <= 21
puts "You scored: " + final_score.to_s
else
puts "You busted with: " + final_score.to_s
end
end
The issue is passing the array to the method; it works when it's local, but passing the array as an argument either gives a 0 for 1 ArgumentError, or if I use the splat op, a blank array.
hand needs to be global so that the other methods can access it, like the random_card method that will eventually generate the hand.
When hand is inside score, p hand shows the full array, and the score is 32. Where it is, a blank array is shown and the score is 0.
How do I pass the array and keep the stored values?
The issue might come from modifying the array - indeed the hand array is mutable so when you reassign values in the line hand[i] = values[hand[i]], you end up destroying the original hand. A subsequent call to the same score method will not work anymore.
You should not mutate it but just iterate on it:
VALUES = {
'two' => 2,
...
}
def score(hand)
VALUES.values_at(*hand).reduce(0, &:+)
end
def busted?(score)
score > 21
end
def print_score(score)
puts "You #{busted?(score) ? 'busted with' : 'scored'}: #{score}"
end
def random_card
VALUES.keys.sample
end
hand = Array.new(4) { random_card }
# => ["four", "queen", "ace", "seven"]
your_score = score(hand)
# => 32
print_score(your_score)
# "You busted with: 32"
The problem is called variable scope. When you are accessing variable hand inside of score method, you are accessing local variable, which was passed as an argument. But you are want to access class variable hand.
You can call self.hand when accessing it (I do not recommend, because, this make your code even more smell.), or refactor the method score to return the array of scores and handle it outside of the method.
I'm a beginner and I've been stuck on the below for a while; I can't figure out where I'm going wrong.
I am trying to write a program that prints out the 'Bottles of Beer' song, taking in a number and translating it to the equivalent English word, in each iteration of the song.
When I try to run the whole program I get the error message:
in `english_words': undefined method `[]' for nil:NilClass (NoMethodError)
from 11_classes.rb:83:in `block in print_song'
from 11_classes.rb:78:in `downto'
from 11_classes.rb:78:in `print_song'
from 11_classes.rb:116:in `<main>'
But when I test it in irb, the song prints out fine.
Please could someone help explain why this doesn't work when I try to create a new object? I know it's pretty messy and probably quite a long-winded way of doing it but thought trying to do it my own way with what I've learnt so far would be a better way to learn for now.
Thank you!
class BeerSong
attr_accessor :bottles
def initialize(bottles)
bottles = 0 if bottles < 0
bottles = 99 if bottles > 99
#bottles = bottles
end
#single_nums = {
19 => "Nineteen",
18 => "Eighteen",
17 => "Seventeen",
16 => "Sixteen",
15 => "Fifteen",
14 => "Fourteen",
13 => "Thirteen",
12 => "Twelve",
11 => "Eleven",
10 => "Ten",
9 => "nine",
8 => "eight",
7 => "seven",
6 => "six",
5 => "five",
4 => "four",
3 => "three",
2 => "two",
1 => "one",
0 => "Zero"
}
#big_nums = {
9 => "Ninety",
8 => "Eighty",
7 => "Seventy",
6 => "Sixty",
5 => "Fifty",
4 => "Fourty",
3 => "Thirty",
2 => "Twenty"
}
def print_song
#bottles.downto 1 do |n|
if #bottles.zero?
String.new
else
puts """
#{english_words(n)} #{bottle(n)} of beer on the wall,
#{english_words(n)} #{bottle(n)} of beer,
Take one down, pass it around,
#{english_words(n-1)} #{bottle(n+1)} of beer on the wall.
"""
end
end
end
def english_words(bottles)
if bottles <= 19
#single_nums[bottles].capitalize
elsif bottles % 10 == 0
split_number = bottles.to_s.split('').map(&:to_i)
#big_nums[split_number[0]]
else
split_number = bottles.to_s.split('').map(&:to_i)
"#{#big_nums[split_number[0]]}-#{#single_nums[split_number[1]]}"
end
end
def bottle(n)
if n == 1
'bottle'
else
'bottles'
end
end
end
Instance variables #single_nums and #big_nums are defined in terms of an instance and should be set up in initialize.
Move #single_nums = {... and #big_nums = {... into initialize and it should work.
Or you could make them constants: SINGLE_NUMS = {..., BIG_NUMS = {... and leave them where they are.
You are referring to #single_nums and #big_nums from instance methods. But you declared those in the class context.
Move them to the initialize or make them methods like this:
def big_nums
#big_nums ||= {
...your values here...
}
end
This uses memoization so you do not create the hash over and over again.
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 8 years ago.
Improve this question
In Ruby, what would be the best way to sort an array of objects by an order property that may or may not exist, and if it doesn't, then fall back to sorting based on a property named title?
Not sure if this is what you are after, but a quick solution could be:
arr = [{a:"never", b:"anna"}, {a:"always", b:"bob"}, {b:"colin"}, {b:"abe"}]
arr.sort_by! {|o| o[:a] ? o[:a] : o[:b] }
#=> [{:b=>"abe"}, {:a=>"always", :b=>"bob"}, {:b=>"colin"}, {:a=>"never", :b=>"anna"}]
Here's how to perform a sort with a fallback in Ruby:
Item = Struct.new(:order, :title)
items = [
Item.new(nil, "d"),
Item.new(nil, "b"),
Item.new(1, "a"),
Item.new(3, "c"),
Item.new(2, "e")
]
sorted_items = items.sort do |a, b|
if a.order && b.order
a.order <=> b.order
elsif a.order || b.order
# This prioritizes all items with an order
a.order ? -1 : 1
else
a.title.to_s <=> b.title.to_s
end
end
require 'awesome_print'
ap sorted_items
# [
# [0] {
# :order => 1,
# :title => "a"
# },
# [1] {
# :order => 2,
# :title => "e"
# },
# [2] {
# :order => 3,
# :title => "c"
# },
# [3] {
# :order => nil,
# :title => "b"
# },
# [4] {
# :order => nil,
# :title => "d"
# }
# ]
Let me also say that if you are fetching records from a database, then it would be better to do the sorting in your SQL query. If Item was an ActiveRecord model, you could do something like:
Item.order('order ASC NULLS LAST, title ASC')
(NULLS LAST can be used in Postgres, check out this answer for MySQL.)
If I understand you right here is how to do this:
arr1 = [{order: 1, title: 2},{title: 4},{order: 2, title: 1}]
arr2 = [{order: 1, title: 2},{order: 7, title: 4},{order: 2, title: 1}]
def sort_it prop1, prop2, ar
ar.map{|el| el[prop1]}.include?(nil) ?
ar.sort_by{|el| el[prop2]}
:
ar.sort_by{|el| el[prop1]}
end
p sort_it(:order, :title, arr1)
p sort_it(:order, :title, arr2)
Which gives:
#=> [{:order=>2, :title=>1}, {:order=>1, :title=>2}, {:title=>4}]
#=> [{:order=>1, :title=>2}, {:order=>2, :title=>1}, {:order=>7, :title=>4}]
So, the algorythm is simple: select all objects' properties (:order in our case) and if output temporary array contains at least one nil then sort by second given property, otherwise -- by first.
You could try
def sort_array(array)
sort_by_property_name = sort_by_property_present?(array, :order, :title)
array.sort_by { |ob| ob.public_send(sort_by_property_name) }
end
def sort_by_property_present?(array, primary_name, fallback_name)
array.all? { |ob| ob.respond_to?(name) } || return fallback_name
primary_name
end
Assuming you want to sort based on field/parameter that may or may not be present, I am assuming:
When the parameter is present sort by it.
When unavailable fall back to the next parameter.
Please review the following code that can sort an object array based on a number of fields, the system keeps falling back to the next field if the field is unavailable. Its developed with the assumption that the last field will definitely be present.
class CondtionalSort
def run(array: a, keys_to_order_by: keys)
array.sort do |e1, e2|
keys_to_order_by.each do |key|
break e1[key] <=> e2[key] if (e1.key?(key) && e2.key?(key))
end
end
end
end
ArrayTest = [{order: 1, title: 2},{title: 4},{order: 2, title: 1}]
ArrayTest_SORTED = [{:order=>1, :title=>2}, {:order=>2, :title=>1}, {:title=>4}]
sorter = CondtionalSort.new
sorter.run array: ArrayTest, keys_to_order_by: [:order, :title]
I just use an array as the sort_by:
# sample data:
Item = Struct.new(:property1, :property2, :property3)
collection = [Item.new("thing1", 3, 6), Item.new("thing1", 3, 1), Item.new("aaa", 1,1) ]
# sort
collection.sort_by{|item| [item.property1, item.property2, item.property3] }
# => [#<struct Item property1="aaa", property2=1, property3=1>,
#<struct Item property1="thing1", property2=3, property3=1>,
#<struct Item property1="thing1", property2=3, property3=6>]
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 8 years ago.
Improve this question
I am working on a program to calculate grades and am using a hash of values to help with the letter assignments. My hash looks like this
LETTERS = {
"A+" => 98, "A" => 95, "A-" => 92,
"B+" => 88, "B" => 85, "B-" => 82,
"C+" => 78, "C" => 75, "C-" => 72,
"D+" => 68, "D" => 65, "D-" => 62,
"F+" => 55, "F" => 40, "F-" => 25,
}
My question is how would I be able to assign, say, a 71 to a grade even though it is not an explicit value in the hash?
Firstly, in ruby we call it a hash - not a dictionary. You might do what you want with:
def grade(points)
LETTERS.find {|_, v| v <= points}.first
end
Note: Find method depends on the order of the hash - the function above will not work correctly if the hash is not ordered (desc) by values. Also - you didn't say what should happen if points are, say, 20 (below any threshold). Currently it will throw NoMethodError
I don't see the reason for using a hash here. In fact, the keys and the values in the OP's hash are the opposite, and useless.
[
[98, "A+"],
[95, "A"],
[92, "A-"],
[88, "B+"],
[85, "B"],
[82, "B-"],
[78, "C+"],
[75, "C"],
[72, "C-"],
[68, "D+"],
[65, "D"],
[62, "D-"],
[77, "F+"],
[40, "F"],
[25, "F-"],
]
.bsearch{|x, _| x <= 89}.to_a.last
# => "B+"
which turned out to be almost the same as BroiSatse's answer.
Not an exact answer, but:
You could instead use a function that returns the grade depending on the value
def get_grade(points)
return "A+" if points >= 98
return "A" if points < 98 and points >= 95
... # etc
end
That way you don't have to assign each value a grade.
Alternatively, you could assign each grade an array of points
Another possibility is to encode the logic into a method using case. I'm adding this option because #blueygh2's answer was burning my eyes :)
def grade(score)
case score
when 98..100
"A+"
when 95...98
"A"
when 92...95
"A-"
end
# and so forth
end
Using the non-inclusive ranges (with three dots) makes it work with fractional scores, but that may not be a requirement.
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 8 years ago.
Improve this question
pry(main)> s = {:a =>2, :d=>'foo', :x => ' ', :n => true, :z => nil}
=> {:a=>2, :d=>"foo"}
pry(main)> s.each do |k,v| p k unless v.empty? end
NoMethodError: undefined method `length' for 2:Fixnum
I understand it happens because fixnum does not have empty methods. Then how to solve this problem in a slick way, no nasty finding data type first and then check it? I want to print those k where v has some value. Yes true is considered a value, but not bunch of spaces. For me "have value" means non-empty characters and boolean true.
With your updated comments, I think that is what you want.
s = {:a =>2, :d=>'foo', :x => ' ', :n => true, :z => nil}
s.each { |k,v| p(k) if !!v && !v.to_s.strip.empty? }
# :n
# :d
# :a
Quick solution:
s.each {|k,v| p k unless v.to_s.empty?}