when I run this code in ruby:
updated_users = []
users = [{:name => "sam" , :number => 001 }]
ids = ["aa" , "bb" , "cc"]
users.each do |user|
ids.each do |id|
user[:guid] = id
updated_users << user
end
end
p updated_users
I get:
[{:name=>"sam", :number=>1, :guid=>"cc"},
{:name=>"sam", :number=>1, :guid=>"cc"},
{:name=>"sam", :number=>1, :guid=>"cc"}]
I expected to get:
[{:name=>"sam", :number=>1, :guid=>"aa"},
{:name=>"sam", :number=>1, :guid=>"bb"},
{:name=>"sam", :number=>1, :guid=>"cc"}]
What is happening and how should I get the desired output?
Your question reflects a common misunderstanding held by budding Rubyists. You begin with the following:
updated_users = []
users = [{:name => "sam" , :number => 001 }]
ids = ["aa" , "bb" , "cc"]
users is an array containing a single hash. Let's note the id of that hash object:
users.first.object_id
#=> 1440
Now let's execute your code with some puts statements added to see what is going on.
users.each do |user|
puts "user = #{user}"
puts "user.object_id = #{user.object_id}"
ids.each do |id|
puts "\nid = #{id}"
user[:guid] = id
puts "user = #{user}"
puts "user.object_id = #{user.object_id}"
updated_users << user
puts "updated_users = #{updated_users}"
updated_users.size.times do |i|
puts "updated_users[#{i}].object_id = #{updated_users[i].object_id}"
end
end
end
This displays the following.
user = {:name=>"sam", :number=>1}
user.object_id = 1440
So far, so good. Now begin the ids.each loop:
id = aa
user = {:name=>"sam", :number=>1, :guid=>"aa"}
user.object_id = 1440
As user did not previously have a key :guid, the key-value pair :guid=>"aa" was added to user. Note that user's id has not changed. user is then appended to the (empty) array updated_users:
updated_users = [{:name=>"sam", :number=>1, :guid=>"aa"}]
updated_users[0].object_id = 1440
Again this is what we should expect. The next element of ids is then processed:
id = bb
user = {:name=>"sam", :number=>1, :guid=>"bb"}
user.object_id = 1440
Since user has a key :guid this merely changes the value of that key from "aa" to "bb". user is then appended to updated_users:
updated_users = [{:name=>"sam", :number=>1, :guid=>"bb"},
{:name=>"sam", :number=>1, :guid=>"bb"}]
updated_users[0].object_id = 1440
updated_users[1].object_id = 1440
The first and second elements of this array are seen to be the same object user, so changing the value of :user's key :guid affected both elements in the same way.
The same thing happens when the third and last element of ids is processed:
id = cc
user = {:name=>"sam", :number=>1, :guid=>"cc"}
user.object_id = 1440
updated_users = [{:name=>"sam", :number=>1, :guid=>"cc"},
{:name=>"sam", :number=>1, :guid=>"cc"},
{:name=>"sam", :number=>1, :guid=>"cc"}]
updated_users[0].object_id = 1440
updated_users[1].object_id = 1440
updated_users[2].object_id = 1440
Got it?
To obtain the result you want you need to append updated_users with distinct hashes derived from user:
users.each do |user|
ids.each do |id|
updated_users << user.merge(guid: id)
end
end
updated_users
#=> [{:name=>"sam", :number=>1, :guid=>"aa"},
# {:name=>"sam", :number=>1, :guid=>"bb"},
# {:name=>"sam", :number=>1, :guid=>"cc"}]
Note that users has not been mutated (changed):
users
#=> [{:name=>"sam", :number=>1}]
See Hash#merge. Note that user.merge(guid: id) is shorthand for user.merge({ guid: id }) or, equivalently, user.merge({ :guid => id }).
Related
class Player
def initialize(hp, attack, defence, gold)
#hp = hp
#attack = attack
#defence = defence
#gold = gold
#inventory = inventory
end
def inventory
#inventory = []
end
def buy(item)
if #gold >= item.price
#gold-=item.price
puts "You hand over #{item.price} gold, and get the #{item.name}."
puts "You have #{gold} gold left over."
#inventory.push([item.name,item.price,item.attack,item.defence])
puts "ITEMS IN INV: #{#inventory}" # output: ITEMS IN INV: [["Dagger", 4, 1, 0], ["Mucky Tunic", 2, 0, 2]]
else
puts "You can't afford this item."
end
end
end
player = Player.new(10,1,2,6)
puts player.inventory.inspect # output: []
The inventory.push line pushes the element to the array while it is inside the method, but when returned outside the method, the inventory array is empty. This is confusing because other variables that were changed inside that method in the same way come back as altered.
sample output when printed from inside the buy method:
ITEMS IN INV: [["Dagger", 4, 1, 0], ["Mucky Tunic", 2, 0, 2]]
output with player.inventory.inspect outside of the method:
[]
Whenever you call your inventory method:
def inventory
#inventory = []
end
... it assigns a new (empty) array to #inventory, thus overwriting any existing items.
The correct way is to either assign #inventory in initialize and just return it from within the getter:
class Player
def initialize(hp, attack, defence, gold)
#hp = hp
#attack = attack
#defence = defence
#gold = gold
#inventory = []
end
def inventory
#inventory
end
# ...
end
or to not assign it at all in initialize and use the conditional assignment operator:
class Player
def initialize(hp, attack, defence, gold)
#hp = hp
#attack = attack
#defence = defence
#gold = gold
end
def inventory
#inventory ||= []
end
# ...
end
which will assign [] to #inventory only if it was nil or false (i.e. the first time you call inventory).
A getter that just returns the corresponding instance variable (as in the former example) can also be created via attr_reader:
class Player
attr_reader :inventory
# ...
end
I figured it out 10 seconds after posting this, after messing about with it for an hour.
I needed to add inventory to def initialize, and then pass an empty array to player = Player.new(10,1,2,6) so it became player = Player.new(10,1,2,6,[]).
I still don't know why this works.
How would I go about compiling values from a table using a string?
i.e.
NumberDef = {
[1] = 1,
[2] = 2,
[3] = 3
}
TextDef = {
["a"] = 1,
["b"] = 2,
["c"] = 3
}
If I was for example to request "1ABC3", how would I get it to output 1 1 2 3 3?
Greatly appreciate any response.
Try this:
s="1ABC3z9"
t=s:gsub(".",function (x)
local y=tonumber(x)
if y~=nil then
y=NumberDef[y]
else
y=TextDef[x:lower()]
end
return (y or x).." "
end)
print(t)
This may be simplified if you combine the two tables into one.
You can access values in a lua array like so:
TableName["IndexNameOrNumber"]
Using your example:
NumberDef = {
[1] = 1,
[2] = 2,
[3] = 3
}
TextDef = {
["a"] = 1,
["b"] = 2,
["c"] = 3
}
print(NumberDef[2])--Will print 2
print(TextDef["c"])--will print 3
If you wish to access all values of a Lua array you can loop through all values like so (similarly to a foreach in c#):
for i,v in next, TextDef do
print(i, v)
end
--Output:
--c 3
--a 1
--b 2
So to answer your request, you would request those values like so:
print(NumberDef[1], TextDef["a"], TextDef["b"], TextDef["c"], NumberDef[3])--Will print 1 1 2 3 3
One more point, if you're interested in concatenating lua string this can be accomplished like so:
string1 = string2 .. string3
Example:
local StringValue1 = "I"
local StringValue2 = "Love"
local StringValue3 = StringValue1 .. " " .. StringValue2 .. " Memes!"
print(StringValue3) -- Will print "I Love Memes!"
UPDATE
I whipped up a quick example code you could use to handle what you're looking for. This will go through the inputted string and check each of the two tables if the value you requested exists. If it does it will add it onto a string value and print at the end the final product.
local StringInput = "1abc3" -- Your request to find
local CombineString = "" --To combine the request
NumberDef = {
[1] = 1,
[2] = 2,
[3] = 3
}
TextDef = {
["a"] = 1,
["b"] = 2,
["c"] = 3
}
for i=1, #StringInput do --Foreach character in the string inputted do this
local CurrentCharacter = StringInput:sub(i,i); --get the current character from the loop position
local Num = tonumber(CurrentCharacter)--If possible convert to number
if TextDef[CurrentCharacter] then--if it exists in text def array then combine it
CombineString = CombineString .. TextDef[CurrentCharacter]
end
if NumberDef[Num] then --if it exists in number def array then combine it
CombineString = CombineString .. NumberDef[Num]
end
end
print("Combined: ", CombineString) --print the final product.
everyone, I'm a super Ruby n00b having problems getting my first Ruby program to work. I have found Q and A here on Stack Overflow that are closely related to the problem that I am having but no matter what I try, I just can't get rid of this error.
I have written two classes, Checkout and Register. Here's the full Register class:
<code>
load 'Register.rb'
class Checkout
def initialize
#register = Register.new
#itemCount = Hash['CH1', 0, 'AP1', 0, 'CF1', 0, 'MK1', 0]
##inventory = Hash['CH1', 3.11, 'AP1', 6.00, 'CF1', 11.23, 'MK1', 4.75]
##discount = Hash['CF1', ['BOGO', '#itemCount["CF1"]%2==0', -11.23, '#register.findLast("CF1")'], 'AP1', ['APPL', '#itemCount["AP1"]>=3', -1.50, '#itemCount["AP1"]==3 ? #register.findAll("AP1") : #register.findLast("AP1")'], 'MK1', ['CHMK', '#itemCount["MK1"]==1 && #itemCount["CH1"]==1', -4.75, '#register.findAll("MK1")']]
end
def scan(item)
#get price of item from inventory
price = ##inventory[item]
#add item and price to register
#register.ringUp(item, price)
#itemCount[item]+=1
#find and apply any applicable special
discountCheck = ##discount[item]
unless discountCheck == nil
nameOfDiscount = ##discount[item][0]
discountCondition = ##discount[item][1]
moneyOff = ##discount[item][2]
howToGetItemIndex = ##discount[item][3]
if(eval(discountCondition))
ind = eval(howToGetItemIndex)
if(ind.class == "Array")
#register.applyDiscount(ind, moneyOff, nameOfDiscount)
else #it's a Fixnum so we want to put it in an array first
indArray = [ind]
#register.applyDiscount(indArray, moneyOff, nameOfDiscount)
end
end
end
end
end
</code>
Here is the Register class:
<code>
class Register
def initialize
#itemsInOrderOfScan = Array.new
#itemInfoInOrderOfScan = Array.new
end
def ringUp(item, price)
#itemsInOrderOfScan.push(item)
#itemInfoInOrderOfScan.push(['', price])
end
def applyDiscount(indices, moneyOff, nameOfDiscount)
for i in 0..indices.length-1
ind = indices[i]
newInd = ind + 1
#itemsInOrderOfScan.insert(newInd, '')
#itemInfoInOrderOfScan.insert(newInd, [nameOfDiscount, moneyOff])
end
end
def findLast(item)
arr = Array.new
ind = #itemsInOrderOfScan.rindex(item)
arr.push(ind)
arr
end
def findAll(item)
indexOfFirstInstanceOfItem = #itemsInOrderOfScan.index(item)
arr = findLast(item)
indexOfLastInstanceOfItem = arr.at(0)
for i in indexOfFirstInstanceOfItem..indexOfLastInstanceOfItem
if(#itemsInOrderOfScan.at(i) == item)
arr.push(i)
end
end
arr
end
def printReceipt
puts "Item\t\t\tPrice"
puts "----\t\t\t-----"
total = 0
for i in 0..#itemsInOrderOfScan.length-1
currentItem = #itemsInOrderOfScan.at(i)
currentDiscountName = #itemInfoInOrderOfScan.at(i)[0]
currentPrice = #itemInfoInOrderOfScan.at(i)[1]
total += currentPrice
puts "#{currentItem}\t#{currentDiscountName}\t\t#{currentPrice}"
end
puts "-----------------------------------\n\t\t\t#{total}"
end
end
</code>
The way these classes should work when I try to run them is as follows: I feed Checkout various items and the two classes work together to create a receipt showing what has been purchased and any discounts that could be applied whenever I call the printReceipt method.
The test that I am using to debug looks like this:
<code>
load 'Checkout.rb'
basket = ['CH1', 'AP1', 'AP1', 'AP1', 'MK1']
co = Checkout.new
for i in 0..basket.length
puts basket.at(i)
co.scan(basket[i])
end
co.register.print()
</code>
The output when I run the test looks like this:
<code>
CH1
AP1
AP1
AP1
Register.rb:16:in `+': no implicit conversion of Fixnum into Array (TypeError)
from Register.rb:16:in `block in applyDiscount'
from Register.rb:14:in `each'
from Register.rb:14:in `applyDiscount'
from Checkout.rb:33:in `scan'
from main.rb:9:in `block in <main>'
from main.rb:7:in `each'
from main.rb:7:in `<main>'
</code>
Please help!
Thank you in advance.
you need to fix at 4 places,
In Checkout.rb, if(ind.class == "Array") is wrong comparison
use if(ind.class.to_s == "Array") or better use if(ind.instance_of? Array)
In you main.rb, use for i in 0...basket.length instead of for i in 0..basket.length because 1..2 => [1,2] but 1...3 => [1,2]
In Checkout.rb, after Class first line should be attr_accessor :register because you are access register in main.rb
In main.rb, co.register.printReceipt instead of co.register.print()
main.rb :
load 'Checkout.rb'
basket = ['CH1', 'AP1', 'AP1', 'AP1', 'MK1']
co = Checkout.new
for i in 0...basket.length
puts basket.at(i)
co.scan(basket[i])
end
co.register.printReceipt
Checkout.rb :
load 'Register.rb'
class Checkout
attr_accessor :register
def initialize
#register = Register.new
#itemCount = Hash['CH1', 0, 'AP1', 0, 'CF1', 0, 'MK1', 0]
##inventory = Hash['CH1', 3.11, 'AP1', 6.00, 'CF1', 11.23, 'MK1', 4.75]
##discount = Hash['CF1', ['BOGO', '#itemCount["CF1"]%2==0', -11.23, '#register.findLast("CF1")'], 'AP1', ['APPL', '#itemCount["AP1"]>=3', -1.50, '#itemCount["AP1"]==3 ? #register.findAll("AP1") : #register.findLast("AP1")'], 'MK1', ['CHMK', '#itemCount["MK1"]==1 && #itemCount["CH1"]==1', -4.75, '#register.findAll("MK1")']]
end
def scan(item)
#get price of item from inventory
price = ##inventory[item]
#add item and price to register
#register.ringUp(item, price)
p item
p #itemCount[item]
#itemCount[item]+=1
#find and apply any applicable special
discountCheck = ##discount[item]
unless discountCheck == nil
nameOfDiscount = ##discount[item][0]
discountCondition = ##discount[item][1]
moneyOff = ##discount[item][2]
howToGetItemIndex = ##discount[item][3]
if(eval(discountCondition))
ind = eval(howToGetItemIndex)
if(ind.class.to_s == "Array")
#register.applyDiscount(ind, moneyOff, nameOfDiscount)
else #it's a Fixnum so we want to put it in an array first
indArray = [ind]
#register.applyDiscount(indArray, moneyOff, nameOfDiscount)
end
end
end
end
end
I have a 2D array:
a = [["john doe", "01/03/2017", "01/04/2017", "event"], ["jane doe", "01/05/2017", "01/06/2017", "event"]...]
I would like to convert it to a value object in ruby. I found how to do it with a hash Ruby / Replace value in array of hash in the second answer of this question but not a 2D array. I would like to assign the value at a[0][0] to an attribute named "name", a[0][1] to "date1", a[0][2] to "date2" and a[0][3] to "event".
This is something like what I'd like to accomplish although it is not complete and I dont know how to assign multiple indexes to the different attributes in one loop:
class Schedule_info
arrt_accessor :name, :date1, :date2, :event
def initialize arr
#I would like this loop to contain all 4 attr assignments
arr.each {|i| instance_variable_set(:name, i[0])}
This should be short and clean enough, without unneeded metaprogramming :
data = [["john doe", "01/03/2017", "01/04/2017", "event"],
["jane doe", "01/05/2017", "01/06/2017", "event"]]
class ScheduleInfo
attr_reader :name, :date1, :date2, :type
def initialize(*params)
#name, #date1, #date2, #type = params
end
def to_s
format('%s for %s between %s and %s', type, name, date1, date2)
end
end
p info = ScheduleInfo.new('jane', '31/03/2017', '01/04/2017', 'party')
# #<ScheduleInfo:0x00000000d854a0 #name="jane", #date1="31/03/2017", #date2="01/04/2017", #type="party">
puts info.name
# "jane"
schedule_infos = data.map{ |params| ScheduleInfo.new(*params) }
puts schedule_infos
# event for john doe between 01/03/2017 and 01/04/2017
# event for jane doe between 01/05/2017 and 01/06/2017
You can't store the key value pairs in array index. Either you need to just remember that first index of array is gonna have "name" and assign a[0][0] = "foo" or just use array of hashes for the key value functionality you want to have
2.3.0 :006 > a = []
=> []
2.3.0 :007 > hash1 = {name: "hash1name", date: "hash1date", event: "hash1event"}
=> {:name=>"hash1name", :date=>"hash1date", :event=>"hash1event"}
2.3.0 :008 > a << hash1
=> [{:name=>"hash1name", :date=>"hash1date", :event=>"hash1event"}]
2.3.0 :009 > hash2 = {name: "hash2name", date: "hash2date", event: "hash2event"}
=> {:name=>"hash2name", :date=>"hash2date", :event=>"hash2event"}
2.3.0 :010 > a << hash2
=> [{:name=>"hash1name", :date=>"hash1date", :event=>"hash1event"}, {:name=>"hash2name", :date=>"hash2date", :event=>"hash2event"}]
It sounds like you want to call the attribute accessor method that corresponds to each array value. You use send to call methods programmatically. So you need an array of the method names that corresponds to the values you have in your given array. Now, assuming the class with your attributes is called Data.
attrs = [:name, :date1, :date2, :event]
result = a.map do |e|
d = Data.new
e.each.with_index do |v, i|
d.send(attrs[i], v)
end
d
end
The value result is an array of Data objects populated from your given array.
Of course, if you control the definition of your Data object, the best things would be to give it an initialize method that takes an array of values.
Try this:
class Schedule_info
arrt_accessor :name, :date1, :date2, :event
def initialize arr
#name = []
#date1 = []
#date2 = []
#event = []
arr.each |i| do
name << i[0]
date1 << i[1]
date2 << i[2]
event << i[3]
end
end
end
Code in the controller
def sql = groovy.sql.Sql.newInstance ( dbUrl, dbUser, dbPassword, dbDriver )
String[] allUser = sql.rows("select user_id from users")
allUser.each()
{
print "${it}"
}
The output in the console
{user_id=9}
{user_id=10}
{user_id=11}
{user_id=12}
{user_id=13}
{user_id=14}
How do I print it in such a way that it will display just the value? Example:
9
10
11
12
13
14
This makes it a bit more intricate than it needs to be. Sql.rows() will return a list of GroovyRowResult objects, which make it easy to retrieve the fields; you have an implicit cast to String[], based on your variable declaration. You can change it as follows:
def allUser = sql.rows("select user_id from users")
or, if you prefer,
List allUser = sql.rows("select user_id from users")
Then you can print the userid's like so:
allUser.each {
println it["user_id"]
}
String userid = "${it}".substring(0, "${it}".length()-1);
String[] test = userid.split("=")