This is my first attempt at learning Ruby, and I'm almost there.. this is working except for one piece.. in the function pc_draw the script asks the user if they have a card. If they do then they should be removed from both the "computer's" hand as well as the users hand.. But some reason its not.. Any idea why?
Love to hear some feedback on the code in general as well, any tips or ways to write more concisely or efficiently??
Thanks! and here is the code (edited to just include the relevant code..
def pc_draw # this is the PC playing, id dups, if none then draw, if some then delete and draw..
#first I have to identify the duplicates..
dup = #pc_cards.detect {|e| #pc_cards.rindex(e) != #pc_cards.index(e) }
if dup == nil # if no dups exist then PC has to ask for a card..
#ask_card = rand(#pc_cards.length) # assigns a random number limited to the length of the array
puts "#{#name} do you have a #{#pc_cards[#ask_card]}? Yes or No? " # ask for a random card in the array..
#user_answer = $stdin.gets.chomp
pc_gofish(#user_answer, #ask_card)
else #if there are dups then take them out of the array.
#pc_cards.delete(dup)
#ask_card = rand(#pc_cards.length) # assigns a random number limited to the length of the array
puts "#{#name} do you have a #{#pc_cards[#ask_card]}? Yes or No? " # ask for a random card in the array..
#user_answer = $stdin.gets.chomp
pc_gofish(#user_answer, #ask_card)
end
end
def pc_gofish(take, card_delete) #this will deal with the PC asking the user for cards
if take == "yes" # if the user enters in that their is a match we delete it from pc & user array
#pc_cards.delete(card_delete)
#kates_cards.delete(card_delete)
puts "The computer has #{#pc_cards.length} cards left!" #lets user know how many cards the PC has left
if #pc_cards.length == 0 # if the array is empty then the pc won!
puts "The computer won!! Sorry!"
else #pc_cards.length != 0 # if the array isn't empty, turn reverts back to user.
draw
end
else #this is when there is no match.. so PC has to take a card..
pc_random = rand(#cards.length) # this assigns a random number constrained to the length of the cards remaining
#pc_cards << pc_random # this inserts that random card into the users ask
#cards.delete(pc_random) # this takes the card from the deck of cards..
puts "The computer has #{#pc_cards.length} cards left!" #lets user know how many cards the PC has left
draw
end
end
It looks to me like your detect block is only going to return the first value of the first duplicated item; if you have more than one duplicate, the other duplicates won't be removed.
Eg:
a = [1,2,2,3,4,5,5,5,6]
dups = a.detect{|e| a.rindex(e) != a.index(e)} # => returns 2, but not 5
I don't know how you've modeled your cards, but you could possibly just uniq the array if you're concerned that duplicates exist:
#pc_cards.uniq! # deletes non-uniq elements from the array.
Update: I re-read your question, I see you're asking about the computers cards, which I assume are kept in #cards? Ultimately, I need to agree with the comments above: you have too much code here, and you aren't clearly saying which part is failing.
I would look into either debugger or pry to inspect your code as it runs, or simply put some puts statements into your code so you can see exactly what is really happening at each point.
Try using delete! Instead of delete.
Related
I am trying to use reject method in ruby but I need to stop after a certain count. For example
users = Users.all # this will be list of 1000 users
count = 0
user.reject! do |user|
user.name == 'python'
end
I want to stop the reject condition when count reaches 100, so something like
count = 0
user.reject! do |user|
user.name == 'python'
count += 1
if count == 100
break
end
end
I agree the syntax above is wrong, and need some help from you. I am fairly new to ruby so I will appreciate any help. Thanks
The reject method takes a block that evaluates to true or false, and it will reject all elements that evaluates to true.
So, having all the other code inside the block will cause reject to malfunction.
I think the way to do it is to split the users array into two: the first 100 and last 900.
first_users = users[0...100]
last_users = users[100...1000]
Now, you can use the reject method on first_users and then combine the arrays.
first_users.reject! do |user|
user.name == 'python'
end
Notice the == instead of =. You need a boolean expression inside the block.
Finally, you just add the arrays together
users = first_users + last_users
EDIT
You can run a counter from 99 to 0, which will loop through each of the first 100 elements, and you delete any one that matches the condition.
99.downto(0).each do |i|
users.delete_at(i) if users[i].name == 'python'
end
It's also likely User.all, but remember, calling all is hazardous as it could load in so much data your server crashes.
What you want is probably:
User.where.not(name: 'python').order(:id).limit(100)
This will select the first 100 people that do not have the name python in order of ID. Try and do as much filtering in the database as possible before hitting the Ruby layer.
The error in your code is you've done an assignment. To rewrite this so it works:
User.all.reject do |user|
user.name == 'python'
end.first(100)
Though as I've mentioned this is very inefficient, it will use tons of memory for no reason, and might actually crash your server for non-trivial numbers of users.
Edit: If you want to mask out the first 100 people named 'python' then here you go:
User.all.reject do |user|
case (user.name)
when 'python'
excluded += 1
excluded < 100
else
true
end
end
It's a rather odd thing to need to do. You can probably just delete these from the database if you don't want them:
DELETE FROM users WHERE name='python' LIMIT 100
Sorry if this has come up before - I promise I've tried to find it.
My rails app compares a user's football score predictions against actual football scores. If the result is right (eg Barcelona win), you get 1 point. If the result AND score are right (Barcelona win 3-0), you get three points.
The 'if' logic etc is all fine, but I'm struggling to access the 'points' record in my user model. Below is what I have in the controller - any idea what should go in the lines where I'm assigning points to the user (highlighted below)?
This is just an excerpt and the index is there for something separate, so please don't be sidelined too much by that - I'm just trying to work out what replaces the two highlighted lines. All advice welcome!
Predictions controller
def update scores
Prediction.joins(:user).each_with_index do |p, :|
if p.predictionarray[0] == Leagueresult.last.resultarray[0] && p.predictionarray[1..2] == Leagueresult.last.resultarray[1..2]
User.points = User.points + 3 <<<<< What goes on this line?
elsif p.predictionarray[0] == Leagueresult.last.resultarray[0] && p.predictionarray[1..2] != Leagueresult.last.resultarray[1..2]
User.points = User.points + 1 <<<<< And this line?
end
end
You are trying to reference the User-model, instead of a User-classed object. It seems that the user-object you are looking for would be p.user.
I have looked around for info on how to do this, just can't quite get it myself. Fairly new to Ruby and I'm building a gem. I can return a list of results from a website ok with Nokogiri.
The issue I'm having is how to take an input number and relate it to another piece of text from the website I'm scraping. So you pick a movie title by number from a list and then you return the relevant movie outline. Maybe I don't want to iterate through all the objects (again) at all.
This is what I'm looking at:
def menu
input = nil
while input != "exit"
input = gets.strip.downcase
if input.to_i < 24
#movies.each.with_index(1) do |movie, i|
puts "Description: #{movie.outline}"
end
end
end
end
So we create a variable for input. While the input is not the word exit and less than the number 24, we iterate through the movies and put the relevant one by index number. Currently putting ALL the movie outlines so I feel like I should ditch the iteration. I've tried a number of things around adding the input to movie.outline...
Any help or hints would be great!
It's hard to say for sure, but, are you maybe just trying to do this?
input = gets.to_i
if input < 24
movie = #movies[input]
puts "Description: #{movie.outline}"
end
Or something to that effect?
If you want to access an Array element, you use the [] notation (I'm assuming #movies is an Array, but if it's some other enumerable, you'll need to tell us what that is).
I've flirted with learning web dev in the past and haven't had the time as I am a full time Business Student.
I started digging back in today and decided to take a break from the learning and practice what I've learned today by writing a simple program that allows the user to enter in their bills and will eventually calculate how much disposable income they have after their bills are paid each month.
My problem is that the program runs through perfectly, the loop is continuing/exiting when it should, but either the program is not storing the users input in the hash like I'm wanting it to or it's not displaying all the bills entered as it should. Here is my program:
# This program allows you to assign monthly payments
# to their respective bills and will automatically
# calculate how much disposable income you have
# after your bills are paid
# Prompts user to see if they have any bills to enter
puts "Do you have any bills you would like to enter, Yes or No?"
new_bill = gets.chomp.downcase
until new_bill == 'no'
# Creates a hash to store a key/value pair
# of the bill name and the respection payment amount
bills = {}
puts "Enter the bill name: "
bill_name = gets.chomp
puts "How much is this bill?"
pay_amt = gets.chomp
bills[bill_name] = pay_amt
puts "Would you like to add another bill, Yes or No?"
new_bill = gets.chomp.downcase
end
bills.each do |bill_name, pay_amt|
puts "Your #{bill_name} bill is $#{pay_amt}."
end
My questions are:
Is my hash set up properly to store the key/value pairs from the users input?
If not, how can I correct it?
I'm getting only the last bill that was entered by the user. I've tried several bills at a time but only getting the last entry.
As I stated, I'm a noob but I'm extremely ambitious to learn. I've referred to to the ruby docs on hashes to see if there is an error in my code but was able to locate a solution (still finding my way around ruby docs).
Any help is appreciated! Also, if you have any recommendations on ways I can make my code more efficient, could you point me in the direction where I can obtain the appropriate information to do so?
Thank you.
Edit:
The main question has been answered. This is a follow up question to the same program - I'm getting an error message budget_calculator.rb:35:in -': Hash can't be coerced into Float (TypeError)
from budget_calculator.rb:35:in'
From the following code (keep in mind of the program above) -
# Displays the users bills
bills_hash.each {|key,value| puts "Your #{key} bill is $#{value}."}
# Get users net income
puts "What is your net income?"
net_income = gets.chomp.to_f
#Calculates the disposable income of the user
disposable_income = net_income - bills_hash.each {|value| value}
puts disposable_income
I understand the error is appearing from this line of code:
disposable_income = net_income - bills_hash.each {|value| value}
I'm just not understanding why this is unacceptable. I'm trying to subtract all of the values in the hash (pay_amt) from the net income to derive the disposable income.
This is the part that's getting you:
bills = {}
You're resetting the hash every time the program loops. Try declaring bills at the top of the program.
As to your second question about bills_hash, it's not working because the program is attempting to subtract a hash from a float. You've got the right idea, but the way it's set up, it's not going to just subtract each key from the net_income in turn.
The return value of #each is the original hash that you were looping over. You can see this if you open IRB and type
[1,2,3].each {|n| puts n}
The block is evaluated for each element of the list, but the final return value is the original list:
irb(main):007:0> [1,2,3].each {|n| puts n}
1
2
3
=> [1, 2, 3] # FINAL RETURN VALUE
So according to the order of operations, your #each block is iterating, then returning the original bills_hash hash, and then trying to subtract that hash from net_income, which looks like this (assuming my net_income is 1000):
1000 - {rent: 200, video_games: 800}
hence the error.
There are a couple ways you could go about fixing this. One would be to sum all of the values in bills_hash as its own variable, then subtract that from the net_income:
total_expenditures = bills_hash.values.inject(&:+) # sum the values
disposable_income = net_income - total_expenditures
Using the same #inject method, this could also be done in one function call:
disposable_income = bills_hash.values.inject(net_income, :-)
# starting with net_income, subtract each value in turn
See the documentation for Enumerable#inject.
It's a very powerful and useful method to know. But make sure you go back and understand how return values work and why the original setup was raising an exception.
Now i am making a web application (Online word learning) that allow user to choose the correct meaning of the word. When they click start, it will select randomly one word from the database and show to the user. After the user choose the answer, it will go to the next question.
Please see the image below:
If i use, Word.order("rand()").limit(1), i wonder can the word will be repeated with the last selected word?
With the app as in the image above, any better ideas to solve this problem?
I would add the following scopes to the model (depends on the database you are using):
# in app/models/word.rb
# 'RANDOM' works with postgresql and sqlite, whereas mysql uses 'RAND'
scope :random, -> { order('RAND()') }
scope :without, ->(ids) { where.not(id: ids) }
With that scopes you can write the following query in your controller:
#word = Word.random.without(params[:last_ids]).limit(1)
When you want to load new random elements in the view, just add the ids of the current words to the request. This ensures that this ids (params[:last_ids]) are not randomly choosen.
Long story short, in order not to repeat yourself, you have to store those words somewhere. Either the ones that are yet to be shown, or the ones that have been already displayed. And If I were you I would go one of the following routes:
Fetch all the words before starting the quiz and randomize them. This could be something like:
session[:words] = Word.order("RAND()").select(:id).take(10)
Or even better by defining a scope for your random words:
class Word < ActiveRecord::Base
# ...
scope :random_quiz, -> { order("RAND()").take(10).pluck(:id) }
# ...
end
# ... in the controller when the quiz is getting started:
session[:words] = Word.random_quiz
# ... in the controller when you want to show the word:
new_word = Word.find(sessions[:words].pop)
As ORDER BY RAND() is a very expensive operation, this might make sense. And then you just pop the word ID's one by one by using session[:words].pop and present the questions.
This way it will guarantee that you won't repeat the words in the quiz and give you pretty optimal performance.
Fetch words one by one as you're progressing with giving out the questions and save the ones you've already asked about.
class Word < ActiveRecord::Base
# ...
def self.random_word(exclusions)
eligible = where('id NOT IN (?)', exclusions)
eligible.offset(rand(0..eligible.count)).take!(1)
end
# ..
end
# ... in the controller when you need a new word:
session[:words_shown] ||= [ ]
new_word = Word.random_word(session[:words_shown])
# mark the word as shown:
session[:words_shown].push(new_word.id)
You might have noticed the weird way of getting a random record in the second example. It turns out to be more efficient as it generates the following query:
SELECT * FROM words OFFSET _random_number_ LIMIT 1
Instead of:
SELECT * FROM words ORDER BY RAND() LIMIT 1
The first one is just an ordinary select, while the second one requires unindexed sorting by RAND() of the entire table before giving you that random result. Turns out to be the former is almost tenfold faster than the latter.
Hope that makes sense!