how does collect and struct work in ruby? - ruby

Im currently going through a book and there is a pice of code that I don't quite understand:
class RevealingReferences
attr_reader :wheels
def initialize(data)
#wheels = wheelify(data)
puts data
end
def diameters
wheels.collect do |wheel|
puts "test"
wheel.rim + (wheel.tire*2)
end
end
Wheel = Struct.new(:rim, :tire)
def wheelify(data)
data.collect{|cell|
Wheel.new(cell[0], cell[1])}
end
end
end
puts RevealingReferences.new([3,2,5,8]).diameters
and I get the following output:
3
2
5
8
test
test
test
test
3
2
1
0
1) Now the 3,2,5,8 I understand, but why does not display in array format [3,2,5,8] rather its being displayed one int at a time.
2) Also, in the wheels.collect block, the output prints "test" twice before putting in the output, should it not be "test" value "test" value
3) Also, the answer 3,2,1,0 don't make any sense, when I set #wheels should wheels not be a collection of an array of 2 elements rather then 4?

1) Now the 3,2,5,8 I understand, but why does not display in array
format [3,2,5,8] rather its being displayed one int at a time.
This is due to how puts works. When it sees an array, it prints the #to_s of each element
puts [1,2,3]
# >> 1
# >> 2
# >> 3
If you want it to look like an array, you should inspect it before printing it
puts [1,2,3].inspect
# >> [1, 2, 3]
There's also a shorthand for this, the method p
p [1,2,3]
# >> [1, 2, 3]
2) Also, in the wheels.collect block, the output prints "test" twice
before putting in the output, should it not be "test" value "test"
value
The only thing printing the values is the puts statement on the return value of diameters, so they won't print until after they have been collected. If you wanted to print it after each test, you should probably do something like
def diameters
wheels.collect do |wheel|
puts "test"
p wheel.rim + (wheel.tire*2)
end
end
Which would print:
test
3
test
2
test
1
test
0
3) Also, the answer 3,2,1,0 don't make any sense, when I set #wheels
should wheels not be a collection of an array of 2 elements rather
then 4?
Based on what you're saying here, I assume your data is not in the format you intended. You're passing in [3,2,5,8], but this implies that you meant to pass in [[3,2],[5,8]], or to map across every pair of values:
def wheelify(data)
data.each_slice(2).collect do |cell|
Wheel.new(cell[0], cell[1])
end
end
The reason it isn't doing what you think is because without doing one of these, the cell variable is actually just a number. Since numbers have the brackets method defined on them, they wind up working in this case. But the brackets method just returns 1 or 0, depending on the bit (base 2) at that position:
five = 5
five.to_s(2) # => "101"
five[2] # => 1
five[1] # => 0
five[0] # => 1
So in the case of 3, wheel.rim + (wheel.tire*2) becomes
cell = 3
cell.to_s(2) # => "11"
rim = cell[0] # => 1
tire = cell[1] # => 1
rim + tire * 2 # => 3
And in the case of 2:
cell = 2
cell.to_s(2) # => "10"
rim = cell[0] # => 0
tire = cell[1] # => 1
rim + tire * 2 # => 2
And 5:
cell = 5
cell.to_s(2) # => "101"
rim = cell[0] # => 1
tire = cell[1] # => 0
rim + tire * 2 # => 1
And 8:
cell = 8
cell.to_s(2) # => "1000"
rim = cell[0] # => 0
tire = cell[1] # => 0
rim + tire * 2 # => 0
Which is why diameters returns [3, 2, 1, 0], explaining the last four digits you see.

1) puts will output each argument on a new line, or if the argument is an array, each element of an array on a new line
2) puts "test" is running in the wheels.collect block, there are four Wheel objects created so it outputs four tests while creating the diameters array.
3) The real problem is what seems like a typo either in your book or the transfer of the code to your test environment. I think that last line was meant to read
puts RevealingReferences.new([[3,2],[5,8]]).diameters
Otherwise, the Wheel.new line
Wheel.new(cell[0], cell[1])}
is calling FixNum#[] giving you the n-th bit of the integer. This was a bit of surprise to me too - it seems like a lot could go subtly wrong when accidentally supplying an integer instead of an Array.
With the original code, cell[0] and cell[1] evaluates as 3[0] and 3[1] for the first element of data. With the correction you have the array [3,2][0] => 3, and [3,2][1] => 2 which makes much more understandable code as a "collect" example.

1- collect is a iterator method that accepts a block of code.The collect iterator returns all the elements of a collection.
2- u haven't specified the value to be displayed. do "puts wheel.rim + (wheel.tire*2)".
3- if u print the 'wheel' in the collect block of diameters method, its
"#<struct RevealingReferences::Wheel rim=1, tire=1>"
"#<struct RevealingReferences::Wheel rim=0, tire=1>"
"#<struct RevealingReferences::Wheel rim=1, tire=0>"
"#<struct RevealingReferences::Wheel rim=0, tire=0>"
When the "wheel.rim + (wheel.tire*2)" statement is executed, the result is 3,2,1,0 and each result is returned. if the statement "puts wheel" is added in the collect block for diameter and the prog executed, u wont see the values (3,2,1,0) in the output.

Related

How do I call a ruby function named []?

I am new to Ruby, so please excuse this question if it is obvious.
I am working with a Module with a function signature that I don't understand. How would I call this function?
module Facter
...
def self.[](name)
collection.fact(name)
end
...
In my code I want to reference something that should be in collection.fact, in this Facter module. What syntax to I use to call this function?
Cheers
It works like this:
class MyModule
def self.[](arg)
puts arg
end
end
MyModule["Hello world"] # will print Hello world
Please see official docs:
https://ruby-doc.org/core/doc/syntax/methods_rdoc.html
Additionally, methods for element reference and assignment may be defined: [] and []= respectively. Both can take one or more arguments, and element reference can take none.
class C
def [](a, b)
puts a + b
end
def []=(a, b, c)
puts a * b + c
end
end
obj = C.new
obj[2, 3] # prints "5"
obj[2, 3] = 4 # prints "10"
So about example from docs
# From docs
obj[2, 3]
# It's the same as
obj.[](2, 3)
More interesting example
# From docs
obj[2, 3] = 4
# will print 10
# => 4
# It's the almost as
obj.[]=(2, 3, 4)
# will print 10
# => nil
As you see when you call as obj[2, 3] = 4 Ruby takes the value after = as the last argument of the []= method and return it as method result
And regardless of whether there is return in the method body. For example
class C
def []=(a, b, c)
puts "Before return"
return 12
puts "After return"
end
end
obj = C.new
obj[2, 3] = 4
# will print Before return
# => 4
obj.[]=(2, 3, 4)
# will print Before return
# => 12
It is desirable to define such method with more than one parameter. Technically, you can have only one, but the call will be like this obj[] = 1

Why doesn't this ternary operator work (Ruby)?

I'm trying to write program that computes factorial. However, when I try to run the code below, I get this error: undefined method `*' for nil:NilClass (NoMethodError)
1.upto(number) {|x| a = x==1 ? 1 : a*x }
Am I setting up the ternary operator incorrectly, or is something else wrong?
Thanks for the help
Your ternary operator is set up correctly, but your variable a is not defined at the point where you do the multiplication.
This will work because b has a value:
b = 5
1.upto(number) {|x| a = x==1 ? 1 : b*x }
I'd do it something like:
def factorial(number)
(2 .. number).inject(1) { |m, n| m * n }
end
factorial(1) # => 1
factorial(2) # => 2
factorial(3) # => 6
factorial(5) # => 120
inject is a useful method for things like this, and isn't limited to use with numbers.
It can be written even more concisely:
(1..n).inject(:*) || 1
which comes from "Ruby factorial function". That'll give you something to chew on for a while.
Your code is doing several things that aren't correct:
a isn't ever defined prior to running, so a*x is doomed to fail because you can't multiply a nil by x.
upto will pass the current value into the block as x, but assigning to a will fail because it's always a new local variable a. There is no static memory of a unless you define it outside the block's scope.
In a lot of Ruby iterators, the value of the block can be used as a return value. upto doesn't work that way. You'll get the seed value returned, so, you'd get back 1.
Working out these sort of problems is best done using IRB, which comes with Ruby. Inside the interactive session you can try variations on your code to see what works. It's a lot faster/easier and more convenient than trying to write a script and go through the edit/run cycle.
What about this:
factorial = Hash.new{ |x,y| x[y]= y<2 ? 1 : x[y-1]*y }
Let's test that out by dropping into IRB to see what it does:
>> factorial = Hash.new{ |x,y| x[y]= y<2 ? 1 : x[y-1]*y }
{}
>> factorial[1]
1
>> factorial
{
1 => 1
}
>> factorial[2]
2
>> factorial
{
1 => 1,
2 => 2
}
>> factorial[100]
93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000
>> factorial
{
1 => 1,
2 => 2,
3 => 6,
4 => 24,
5 => 120,
6 => 720,
7 => 5040,
8 => 40320,
9 => 362880,
10 => 3628800,
...
100 => 93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000
}
YOW! You're going to compute every intermediate value!? It's going to rapidly consume memory, with very little advantage.

Difference between += for Integers/Strings and << For Arrays?

I'm confused about the different results I'm getting when performing simple addition/concatenation on integers, strings and arrays in Ruby. I was under the impression that when assigning variable b to a (see below), and then changing the value of a, that b would remain the same. And it does so in the first two examples. But when I modify Array a in the 3rd example, both a and b are modified.
a = 100
b = a
a+= 5
puts a
puts b
a = 'abcd'
b = a
a += 'e'
puts a
puts b
a = [1,2,3,4]
b = a
a << 5
puts a.inspect
puts b.inspect
The following is what was returned in Terminal for the above code:
Ricks-MacBook-Pro:programs rickthomas$ ruby variablework.rb
105
100
abcde
abcd
[1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]
Ricks-MacBook-Pro:programs rickthomas$
I was given the following explanation by my programming instructor:
Assigning something to a new variable is just giving it an additional label, it doesn't make a copy.
It looks like += is a method, just like <<, and so you'd expect it to behave similarly. But in reality, it's "syntactic sugar", something added to the language to make things easier on developers.
When you run a += 1, Ruby converts that to a = a + 1.
In this case, we're not modifying the Fixnum in a. Instead, we're actually re-assigning on top of it, effectively blowing away the previous value of a.
On the other hand, when you run b << "c", you're modifying the underlying Array by appending the String "c" to it.
My questions are these:
1) He mentions syntactic sugar, but isn't that also what << is, i.e. syntactic sugar for the .push method?
2) Why would it matter if += is syntactic sugar or a more formal method? If there is some difference between the two, then doesn't that mean my previously-understood of syntactic sugar ("syntax within a programming language that is designed to make things easier to read or to express") is incomplete, since this isn't its only purpose?
3) If assigning b to a doesn't make a copy of a, then why doesn't wiping away a's old value mean that b's old value is also wiped away for all 3 cases (Integer, String and Array)?
As you can see, I'm pretty turned around on something that I thought I understood until now. Any help is much appreciated!
You see, names (variable names, like a and b) don't hold any values themselves. They simply point to a value. When you make an assignment
a = 5
then a now points to value 5, regardless of what it pointed to previously. This is important.
a = 'abcd'
b = a
Here both a and b point to the same string. But, when you do this
a += 'e'
It's actually translated to
a = a + 'e'
# a = 'abcd' + 'e'
So, name a is now bound to a new value, while b keeps pointing to "abcd".
a = [1,2,3,4]
b = a
a << 5
There's no assignment here, method << modifies existing array without replacing it. Because there's no replacement, both a and b still point to the same array and one can see the changes made to another.
The answer to 1) and 2) of your question:
The reason why += is syntactic sugar and << is not is fairly simple: += abstracts some of the syntactic expression: a += 1 is just a short version of a = a + 1. << is a method all by itself and is not an alias for push: << can only take one argument, whereas push can take an arbitrary number of arguments: I'm demonstrating this with send here, since [1,2]<<(1,2) is syntactically incorrect:
[1,2].send(:<<, 4, 5) #=> ArgumentError: wrong number of arguments (2 for 1)
push appends all arguments to the array:
[1,2].push(4,5,6) #=> [1,2,4,5,6]
Therefore, << is an irreplaceable part of the ruby array, since there is no equivalent method. One could argue that it is some kind of syntactic sugar for push, with disregard for the differences shown above, since it makes most operations involving appending elements to an array simpler and syntactically more recognizable.
If we go deeper and have a look at the different uses of << throughout ruby:
Push An Element to an array:
[1,2] << 5
concatenate a string to another, here, << is actually an alias for concat
"hello " << "world"
Open up the singleton class and define a method on a class:
class Foo
class << self
def bar
puts 'baz'
end
end
end
And last but not least append self to self in Integers:
1 << 2 #translates to ((1 + 1) + (1 + 1))
We can see that << actually stands for append throughout ruby, since it always appears in a context where something is appended to something already existing. I would therefore rather argue that << is a significant part of the ruby syntax and not syntactic sugar.
And the answer to 3)
The reason why b's assignment is not modified (or wiped of its old value, as you put it) if you use the += operator is just that a += 1, as a short for a = a + 1, reassigns a's value and therefore assigns a new object along with that. << is modifying the original object. You can easily see this using the object_id:
a = 1
b = a
b.object_id == a.object_id #=> true
a += 1
b.object_id == a.object_id #=> false
a = [1,2]
b = a
b.object_id == a.object_id #=> true
a << 3
b.object_id == a.object_id #=> true
There are also some caveats to Integer instances (100, 101) and so on: the same number is always the same object, since it does not make any sense to have multiple instances of, for example 100:
a = 100
b = a
b.object_id == a.object_id #=> true
a += 1
b.object_id == a.object_id #=> false
a -= 1
b.object_id == a.object_id #=> true
This also shows that the value, or the Integer instance (100) is just assigned to the variable, so the variable itself is not an object, it just points to it.
String#+ :: str + other_str → new_str Concatenation—Returns a new String containing other_str concatenated to str.
String#<< :: str << integer → str : Append—Concatenates the given object to str.
<< doesn't create the new object, where as + does.
Sample1:
a = 100
p a.object_id
b = a
p b.object_id
a+= 5
p a.object_id
p b.object_id
puts a
puts b
Output:
201
201
211
201
105
100
Your example:
a = 100
b = a
a+= 5
is equivalent to:
a = 100
b = a
a = 100 + 5
Afterwards a holds a reference to 105 and b still holds a reference to 100. This is how assignment works in Ruby.
You expected += to change the object instance 100. In Ruby, however (quoting the docs):
There is effectively only one Fixnum object instance for any given integer value
So there's only one object instance for 100 and another (but always the same) one for 105. Changing 100 to 105 would change all 100's to 105. Therefore, it is not possible to modify these instances in Ruby, they are fixed.
A String instance on the other hand can be modified and unlike Integer there can be multiple instances for the same sequence of bytes:
a = "abcd"
b = "abcd"
a.equal? b # returns true only if a and b are the same object
# => false
a << "e" concatenates "e" to a, thus changing the receiver: a is still referencing the same object instance.
Other methods like a += "e" return (and assign) a new String: a would reference this new instance afterwards.
The documentation is pretty clear:
str + other_str → new_str
Concatenation—Returns a new String containing other_str concatenated to str.
str << obj → str
Append—Concatenates the given object to str.
I can answer your questions.
1) No, the << method is not syntactic sugar for push. They are both methods with different names. You can have objects in Ruby that define one but not the other (for example String).
2) For a normal method like <<, the only thing that can happen as a result of a << x is that the object that a is pointing to gets modified. The statements a << x or a.push(x) cannot create a new object and change the variable a to point at it. That's just how Ruby works. This kind of thing is called "calling a method".
The reason that += being syntactic sugar matters is that means it can be used to modify a variable without mutating the old object that the variable used to point to. Consider a += x. That statement can modify what object a is pointing to because it is syntactic sugar for an actual assignment to a:
a = a + x
There are two things happening above. First the + method is called on a with one argument of x. Then the return value of the + method, whatever it is, is assigned to the variable a.
3) The reason that your Array case is different is because you chose to mutate the array instead of creating a new array. You could have used += to avoid mutating the array. I think that these six examples that will clear things up for you and show you what is possible in Ruby:
Strings without mutations
a = "xy"
b = a
a += "z"
p a # => "xyz"
p b # => "xy"
Strings with mutations
a = "xy"
b = a
a << "z"
p a # => "xyz"
p b # => "xyz"
Arrays without mutations
a = [1, 2, 3]
b = a
a += [4]
p a # => [1, 2, 3, 4]
p b # => [1, 2, 3]
Arrays with mutations
a = [1, 2, 3]
b = a
a.concat [4]
p a # => [1, 2, 3, 4]
p b # => [1, 2, 3, 4]
Integers without mutations
a = 100
b = a
a += 5
puts a # => 105
puts b # => 100
Integers with mutations
Mutating an integer is actually not possible in Ruby. Writing a = b = 89 actually does create two copies of the number 89, and the number cannot be mutated ever. Only a few, special types of objects behave like this.
Conclusion
You should think of a variable as just a name, and an object as a nameless piece of data.
All objects in Ruby can be used in an immutable way where you never actually modify the contents of an object. If you do it that way, then you don't have to worry about the b variable in our examples changing on its own; b will always point to the same object and that object will never change. The variable b will only change when you do some form of b = x.
Most objects in Ruby can be mutated. If you have several variables referring to the same object and you choose to mutate the object (e.g. by calling push), then that change will affect all the variables that are pointing to the object. You cannot mutate Symbols and Integers.
I guess the above answers explain the reason. Note also that if you want to ensure b is no pointer, you can use b = a.dup instead of b=a (dup for duplicate )
I'll try and answer your question to the best of my ability.
Yes, both are "sugars" but they work differently and as Sergio Tulentsev said, << it's not really a sugar but it's an alias.
And those works as an alias in Unix like languages, it's a shorter shorthand for something named after your liking.
So for the first scenario: += basically what's happening is that you're saying:
for the value 100 assign label 'a'.
for label 'b' assign the value of label 'a'.
for label 'a' take the value of label 'a' and add 5 to label 'a's value and return a new value
print label 'a' #this now holds the value 105
print label 'b' #this now holds the value 100
Under the hood of Ruby this has to do with the += returning a new String when that happens.
For the second scenario: << it's saying:
for value [1,2,3,4] assign label 'a'
for label 'b' assign the value of label 'a'
for label 'a' do the '<<' thing on the value of label 'a'.
print label 'a'
print label 'b'
And if you're applying the << to a string it will modify the existing object and append to it.
So what's different. Well the difference is that the << sugar doesn't act like this:
a is the new value of a + 5
it acts like this:
5 into the value of 'a'
2) Because the way you use the syntactic sugar in this case is making it easier for the
developer to read and understand the code. It's a shorthand.
Well, shorthands, if you call them that instead, do serve diffrent purposes.
The syntactic sugar isn't homogenous ie. it doesn't work the same way for all "sugars".
3) On wiping values:
It's like this.
put value 100 into the label 'a'
put the value of label 'a' into label 'b'
remove label 'a' from the value.
So
a = 100
b = a
a = nil
puts a
puts b
=> 100
Variables in Ruby doesn't hold values they point to values!

Ruby Range printing out extra

I'm new to coding so please free to point out any errors in the way I refer to code.
rows = 5
(1..rows).each do |n|
print n, ' '
end
This prints out what I expect it to: 1 2 3 4 5.
But, when I put it into a method:
def test(rows)
(1..rows).each do |n|
print n, ' '
end
end
puts test(5)
I get 1 2 3 4 5 1..5.
Why does the 1..5 show up? And how do I get rid of it?
I need it in the method because I plan to add more code to it.
each on a Range returns the range after the looping is done, and you're probably printing the return value of test too.
Just run test(5) instead of puts test(5) or something.
Ruby always returns the last line of any function.
You are executing puts test(5), and test(5) prints the data you expect, and the extra puts prints out the data returned by test(5) method.
Hope that answers your question.
The final 1..5 is the return value from the script. You get that when you run the code in IRB. When you run that as a standalone Ruby script, it will not show up, so you do not need to worry about it.
A Ruby function will return the last statement, in your case 1..5. To illustrate I'll give it a different return value:
def test(rows)
(1..rows).each {|n| puts "#{ n } "}
return 'mashbash'
end
# Just the function invokation, only the function will print something
test(5) # => "1 2 3 4 5 "
# Same as above, plus printing the return value of test(5)
puts test(5) # => "1 2 3 4 5 mashbash"
You could write your example a little differently to achieve what you like:
def second_test(rows)
# Cast range to an array
array = (1..rows).to_a # [1, 2, 3, 4, 5]
array.join(', ') # "1, 2, 3, 4, 5", and it is the last statement => return value
end
# Print the return value ("1, 2, 3, 4, 5") from the second_test function
p second_test(5)
# => "1, 2, 3, 4, 5"

How to increment an Integer variable by X without creating a new object instance

How can I increment an Integer variable by X without creating a new object instance?
+= does not work because:
ree-1.8.7-2010.02 > x = 1
1
ree-1.8.7-2010.02 > x.object_id
3
ree-1.8.7-2010.02 > x += 1
2
ree-1.8.7-2010.02 > x.object_id
5
You can't. Not in Ruby, and not in any other programming language I am aware of.
The object which represents the mathematical number 1 will always have the value 1. Mutating the object which represents the mathematical number 1 to suddenly have the value 2 would quite simply be insane, because now all of a sudden 1 + 1 == 4.
Extend your example for a moment. Try this:
x = 2
y = 1 + 1
x.object_id
y.object_id
Every unique number will have its own identity. Ruby's object orientedness goes a bit deeper than you will find with C++ and Java (both of those have the concept of primitives and classes).
What's important is that when you query x the second time for its value the value will be what you expect. Object identifiers don't really matter unless you are the garbage collector.
It gets worse with Bignums
begin
a = 1234567890
puts a.object_id
b = 1234567890
puts b.object_id
end
gave me
10605136
10604960
Execution time is really terrible even if just you organize a simple loop. Primitives shouldn't be ditched from Ruby.
(1..16000).each do
(1..16000).each do
end
end
This itself takes 30-40 seconds to complete (Lenovo T400, Virtualboxed Ubuntu), and you haven't even done something sophisticated.
You can use a helper class:
class Variable
def initialize value = nil
#value = value
end
attr_accessor :value
def method_missing *args, &blk
#value.send(*args, &blk)
end
def to_s
#value.to_s
end
# here's the increment/decrement part
def inc x = 1
#value += x
end
def dec x = 1
#value -= x
end
end
x = Variable.new 1
puts x #=> 1
puts x.object_id #=> 22456116 (or whatever)
x.inc
puts x #=> 2
puts x.object_id #=> 22456116
x.inc 3
puts x #=> 5
puts x.object_id #=> 22456116
More uses of "class Variable" here.

Resources