Confused about using reduce and what it's actually trying to do - ruby

I have done Rails development a bit but haven't for a while.
I have never used reduce / inject other than to sum up values (which seems to be the main example I've seen online) but would like to understand better what it does (and to be honest, just its syntax). I have heard it referred to as the functional part of Ruby but based upon examples, not sure what that means.
Looking at the example in the docs (the longest word example) https://ruby-doc.org/core-2.4.0/Enumerable.html#method-i-reduce seems to imply that it is useful for selecting a value out of a series at end but this doesn't seem very functional.
My understanding is that it accumulates to the initial value passed into reduce. But in this code fragment, I'm not sure why it's not accumulating (I'm expecting "some joe birds joe bees" or some variation).
# expect 'some joe birds joe bees'
def say_joe(name)
puts "within say_joe #{name}"
" joe #{name}"
end
a = %w(birds bees)
starting = "some"
b = a.reduce(starting) do |total, x|
puts x
say_joe(x)
end
puts "-----"
puts "starting: #{starting}"
puts "a: #{a}"
puts "b: #{b}"
with output:
birds
within say_joe birds
bees
within say_joe bees
-----
starting: some
a: ["birds", "bees"]
b: joe bees
What is the magic of reduce?

The accumulation (in this case a string concatenation) doesn’t happen automatically. You still need to return the new accumulator value (remember in Ruby the last statement is also implicitly the return value).
This does what you’d expect:
def say_joe(name)
puts "within say_joe #{name}"
" joe #{name}"
end
a = %w(birds bees)
starting = "some"
b = a.reduce(starting) do |total, x|
puts x
total << say_joe(x)
end
puts "-----"
puts "starting: #{starting}"
puts "a: #{a}"
puts "b: #{b}” # some joe birds joe bees

Related

List in Ruby gem cli

I am making a ruby cli that outputs a list of game deals scraped from a site.
The list prints out promptly using
def games_sales
Deal.all.each_with_index do |deal, index|
puts "#{index + 1}. #{deal.title}"
end
puts "What game do you want to see?"
input = gets.strip
game_selection(input.to_i)
end
My problem comes when asking the user to select an item from the list.
def game_selection(input)
deal = Deal.find_by_index(input)
#binding.pry
deal.each do |deal|
puts "#{deal.index}"
puts " Name: #{deal.title}"
puts " Price: #{deal.price}"
puts " Store: #{deal.store}"
puts " Expiration: #{deal.expiration}"
end
deal
end
It returns the int input but only the first item on the list every time.
I forgot my find_by_index method:
def self.find_by_index(input)
all.select do |deal|
end
end
which is incomplete
Not 100% sure if I got your question right and if you're using Rails, but Deals.all let me think of this.
I had to replace Deals.all with DEALS for testing as I haven't got a rails app running. So I used an Array of OpenStructs to fake your Model result.
# this fakes Deals.all
require 'ostruct'
DEALS = [
# add any more properties the same way as title, separated by comma
OpenStruct.new(title: 123),
OpenStruct.new(title: 456)
]
def games_sales
DEALS.each_with_index do |deal, index|
puts "#{index + 1}. #{deal.title}"
end
puts "What game do you want to see?"
input = gets.strip
game_selection(input.to_i)
end
def game_selection(input)
deal = DEALS.at(input-1)
p deal[:title]
end
def self.find_by_index(input)
all.select do |deal|
deal.index == input
end
end
games_sales
Result when choosing 1 is 123, choosing 2 you'll get 456, due to p deal[:title] above in the code.
I think your find_by_index need to get the right index and in my example I had to use at(index) as at(input-1) in order to get the right result.
I really hope this helps somehow and I suggest that you add the expected result to your question, in case my answer does not help you.

Ruby loop create empty lines

I'm newbie in Ruby (frankly, I've just started learning it for fun, without any future plans), and I've noticed strange behaviour of loops. I assume my problem comes from lacks in knowledge in the mechanics of Ruby.
boysNames = ["Martin", "Lucas", "John"]
#class only for one method. I know it's not absolutely correct, but huh I'm just learning Ruby
class Human
def initialize(name)
#name = name;
end
String name = ""
def getName()
puts #name;
end
end
Array boys = []
#create objects
for x in boysNames
tempBoy = Human.new(x)
boys.push(tempBoy)
end
#output for class
puts "Method 1: for in loop \n"
for x in boys
puts x.getName()
end
puts "\nMethod 2: manual array[var] \n"
boys[0].getName()
boys[1].getName()
boys[2].getName()
puts "\nMethod 3: .each do \n"
boys.each do |i|
puts i.getName()
end
#output for Array
puts "Method 1: for in loop \n"
for x in boysNames
puts x
end
puts "\nMethod 2: manual array[var] \n"
puts boysNames[0]
puts boysNames[1]
puts boysNames[2]
puts "\nMethod 3: .each do \n"
boysNames.each do |i|
puts i
end
#loop through the boys array
puts "\nboys array: \n"
for x in boys
puts x
end
So my question is: Why when I loop through the array everything is fine, and when I loop through the class, my results are divided by newlines? Are there any "invisible" objects that I couldn't find? As you can see my last loop found only three objects with different places in the memory.
I will be pleased, if the answer contains explanation of "how it works".
Thank you in advance ;)
P.S. Output:
Method 1: for in loop
Martin
Lucas
John
Method 2: manual array[var]
Martin
Lucas
John
Method 3: .each do
Martin
Lucas
John
Method 1: for in loop
Martin
Lucas
John
Method 2: manual array[var]
Martin
Lucas
John
Method 3: .each do
Martin
Lucas
John
boys array:
#<Human:0x007f2a586db788>
#<Human:0x007f2a586db648>
#<Human:0x007f2a586db5d0>
P.S.S. I'm using this as the Ruby interpreter/compiler (I heard that Ruby can also be compiled, so...)
You're calling puts x.getName(), but getName() already has puts inside of it.
puts adds a newline to the end of each argument if there is not one already.
print does not add a new line.
(What is the difference between print and puts?)

Undefined Method "attack" for Player::Class

So I am trying to make a text based game with classes for an assignment. I have worked for 2 hours and could not find what the problem is.
class Rankuun
attr_accessor :rankuun_damage, :rankuun_health
def initialize
rankuun_health = 200
rankuun_damage = 100
end
def monolouge
puts 'Rankuun: "So, I see that you have lived this long. I am suprised.'
puts "Not a single libing creature has lived for this long inside my dungeon."
puts "But it's time that your endless slaughter of my brethren are halted."
puts "Now face what true fear really is!"
puts "Hoc vanitas est, et non est fere ut serves!"
puts "You see a mystical aura rise around Rankuun, and hear the shouts of agony"
puts "Rankuun has grown twice in size, and has taken the form of some kind of lich"
puts 'Rankuun: WELCOME TO DIE!"'
end
end
class Player
attr_accessor :health, :gold
def initialize
health = 100
money = 200
puts "Health: #{health}"
puts "Gold: #{money}"
end
def attack
puts "You attack the monster!"
hitmiss = 1
if hitmiss == 1
dmg = rand(5..10)
puts "You hit the monster, and do #{dmg} damage!"
monster_health = monster_health - dmg
elsif hitmiss == 2
puts "You missed!"
end
end
def guard
puts "You attempt to defend yourself"
guard = rand(1..2)
if guard == 1
counter = rand(5..10)
puts "You block the damage, and counterstrike for #{counter} damage"
monster_health = monster_health - counter
elsif guard == 2
monster_counter = rand(1..5)
puts "You try to guard, but the enemy hits harder than you expected, and you get dealt #{monster_counter}"
health = health = monster_counter
end
end
def loot
puts "You search the room and find:"
loot_item = rand (2..3)
if loot_item == 2
puts "You find some gold!"
money = money + 50
puts "Health: #{health}"
puts "Gold: #{money}"
elsif loot_item == 3
puts "You find a curious potion that seems to heal you"
health = health + 50
puts "Health: #{health}"
puts "Gold: #{money}"
end
end
def encounter
encounter = rand(1..2)
if encounter == 1
puts "A monster confronts you!"
monster = Monster.new
elsif encounter == 2
puts "There appears to be no monsters in this room"
end
end
end
class Monster
attr_accessor :monster_health, :monster_damage
def initialize
monster_health = 50
monster_damage = 10
end
def monster_attack
puts "The monster attacks you!"
end
end
puts "There has been a saying in your town for as long as you can remember:"
puts "Ne pas entrer dans le Donjon De Rankuun"
puts 'It means: "Do not enter The Dungeon of Rankuun"'
puts "Many adventurers died inside, and the only living creature in there is the man named Rankuun"
puts "He has great power over the Dungeon, reviving the dead and casting black magic"
puts "You have been selected by the village to go into the Dungeon and exterminate Rankuun"
puts "You have been given a sword, a shield, and some gold. Now you must enter:"
puts "T H E D U N G E O N O F R A N K U U N!"
puts ""
puts ""
player = Player.new
player.encounter
room1 = gets.chomp
if room1 == "attack"
player.attack
elsif room1 == "loot"
player.loot
end
It would be great if this problem were solved. Thanks for responding and aiding me in my assignment.
Welcome to the exciting world of object-oriented design. Many adventurers died inside.
I think you may have a small misunderstanding about the difference between classes and instances. If so, I strongly advise you to read about it before continuing.
You created a new instance of Player when you called Player.new. Your first mistake was not putting it in a variable.
Try something like this:
my_player = Player.new
Secondly, you are trying to call encounter on the Player class, while you should call it on the new instance.
my_player.encounter
You do the same thing inside the Monster class with Player.attack.
I could tell you how to solve each of these problems individually, but I think you would benefit more from redesigning some parts of the project to be easier to change in the future. Hopefully, most of the problems will resolve themselves along the way.
Generally speaking, the shorter a method is, the better. When you tell the Player to attack, that is all it should do. Instead, it does all sorts of things, including getting the monster to attack!
It suddenly becomes apparent that the two classes have quite a lot in common: they both attack; they both take damage, and they both die. It's time to make a superclass. (If you are not familiar with how classical inheritance works, you should learn - this truly is the perfect use case for it.)
class Character
attr_accessor :health
def attack damageable, damage
damageable.take_damage damage
end
def take_damage damage
health -= damage # Equivenent to health = health - damage
potential_death
end
def potential_death
if dead?
die
end
end
def dead?
health <= 0 # With random damage, it could be less than 0.
end
def die # overruled by subclass
end
end
The greatest advantage to doing it like this is you only have to write the code in one place, and it will work for everything. If you change your mind about a design decision, you can change it in one place and know that everything will be adjusted.
You can make a subclass similar to this:
class Monster < Character
def die
super # Call the copy of die in Character, in case it contains something important
reward killer
puts "You kill the monster..."
end
def reward rewardable
rewardable.gain_money 30
end
end
class Player < Character
def die
super # Call the copy of die in Character, in case it contains something important
puts "You died..."
game.over
end
end
(These are only examples; they are not as complete as the code you already have.)
Do you see how each method only does one thing? If you apply that principle to everything you write, it will become much easier to reuse bits and pieces.
I hope this has been useful. If you decide to stick with what you have and just fix the errors, just say so in the comments, and I'll help you with that.
Good luck!

My case statement is not working, but the logic looks correct to me

This program prints the first statement, and exits after I enter a number e.g. "5", without printing anything else. From the logic I put in the case statement, I would expect it to output "You're not an adult :(" for 5. Other values lower than 120 do not work as expected either.
What is wrong?
print "Enter you age "
age = gets.chomp
if age.to_i<120
case age.to_i
when age.to_i<18
puts "You're not an adult :("
puts "Sorry"
when age.to_i>18
puts "You are now an adult!"
puts "phew"
end
end
Try this. Notice I've dropped the age.to_i from the case statement:
print "Enter you age "
age = gets.chomp
if age.to_i<120
case
when age.to_i<18
puts "You're not an adult :("
puts "Sorry"
when age.to_i>18
puts "You are now an adult!"
puts "phew"
end
end
EDIT
A little explanation is in order.
When you write this:
case foo
when "bar"
...
That essentially means:
if "bar" === foo
...
So your code was sort of like this:
if age.to_i<18 === age.to_i
...
which doesn't make a lot of sense. If you just write case with nothing after it, then it works more like a regular if statement. E.g.
case
when foo === "bar"
...
means roughly
if foo === "bar"
...
which is what you want. I hope that helps!
Here's a cleaned up version:
print "Enter you age "
age = gets.chomp.to_i
case age
when 0..18
puts "You're not an adult :("
puts "Sorry"
when 18..120
puts "You are now an adult!"
puts "phew"
else
puts "I think you're lying!"
end
Folding all of this into a single case statement and using ranges makes what's happening a lot more clear.

Looking for help cleaning up code

This is for an assignment for a class. I want to create a register type application for a donut shop. The application should accept "l" to list flavors, "a" to add flavors, "d" to delete flavors, and "e" to exit. The prices should be listed in ascending order. When adding a flavor, it should ask first the flavor, then the price.
I have the application working. It uses Jekyll (what the class uses).
item = {}
item_asc = {}
puts "Welcome to d0nutz flavor menu"
input = ""
division_line = lambda {30.times {|x| print "="}}
while input != "e"
division_line.call
puts "\n(l)ist flavors\n(a)dd a flavor\n(d)elete a flavor\n(e)xit application"
division_line.call
print "\nYour choice: "
input = gets.chomp
case input
when "l"
item_asc.each {|x,y| puts "Flavor: #{x} - Cost: $#{y}"}
when "a"
puts "Enter new flavor: "
flavor = gets.chomp
puts "Enter cost: "
cost = gets.chomp.to_i
item[flavor] = cost
item_asc = item.sort_by {|flavor,price| price}
input = ""
when "d"
puts "Enter a flavor to remove"
to_delete = gets.chomp
item.delete("#{to_delete}") {|x| puts "#{x} not found."}
item_asc = item.sort_by {|flavor,price| price}
item_asc.each {|x,y| puts "Flavor: #{x} - Cost: $#{y}"}
input = ""
when "e"
else
puts "\nThat is not a recognised command"
end
end
However it's even uglier than what I have here. I appreciate any input. I am interested in seeing what I should have done for clean up purposes, and maybe adding classes/methods where they should be, and making this more Rubyesque.
I would change the code to the following. This is not refactor since it is not equivalent to your code. In particular, it is not inconsistent like your code is. For example, when it prompts, it does not sometimes change a line and sometimes not as in your code; My code never changes a line. My code also always prints the list after an operation, unlike your code, which does not do so particularly when an item is added. Also, unlike your code, it does not end a message sometimes with and sometimes without a period; Messages in my code never end with period.
def prompt s
print "#{s}: "
gets.chomp
end
def list_items
#items.each{|k, v| puts "Flavor: #{k} - Cost: $#{v}"}
end
def sort_items
#items = #items.sort_by{|flavor, price| price}.to_h
list_items
end
puts "Welcome to d0nutz flavor menu"
#items = {}
loop do
puts(
?= * 30,
"(l)ist flavors",
"(a)dd a flavor",
"(d)elete a flavor",
"(e)xit application",
?= * 30,
)
case prompt("Your choice")
when ?l
list_items
when ?a
#items[prompt("Enter new flavor")] = prompt("Enter cost").to_i
sort_items
when ?d
#items.delete(prompt("Enter a flavor to remove")){|k| puts "#{k} not found"}
sort_items
when ?e
break
else
puts "That is not a recognised command"
end
end

Resources