accessing variables in loaded source while in irb - ruby

Say I have a file named test1.rb with the following code:
my_array = [1, 2, 3, 4 5]
Then I run irb and get an irb prompt and run "require 'test1'. At this point I am expecting to be able to access my_array. But if I try to do something like...
puts my_array
irb tells me "my_array" is undefined. Is there a way to access "my_array"

like this:
def my_array
[1, 2, 3, 4, 5]
end

You can also require your script and access that data in a few other ways. A local variable cannot be accessed, but these other three data types can be accessed within the scope, similar to the method definition.
MY_ARRAY = [1, 2, 3, 4 5] #constant
#my_array = [1, 2, 3, 4 5] #instance variable
##my_array = [1, 2, 3, 4 5] #class variable
def my_array # method definition
[1, 2, 3, 4 5]
end

No, there isn't. Local variables are always local to the scope they are defined in. That's why they are called local variables, after all.

In irb:
eval(File.read('myarray.rb'),binding)
Or you could drop to irb

Related

Reassign entire array to the same reference

I've searched extensively but sadly couldn't find a solution to this surely often-asked question.
In Perl I can reassign an entire array within a function and have my changes reflected outside the function:
#!/usr/bin/perl -w
use v5.20;
use Data::Dumper;
sub foo {
my ($ref) = #_;
#$ref = (3, 4, 5);
}
my $ref = [1, 2];
foo($ref);
say Dumper $ref; # prints [3, 4, 5]
Now I'm trying to learn Ruby and have written a function where I'd like to change an array items in-place by filtering out elements matching a condition and returning the removed items:
def filterItems(items)
removed, items = items.partition { ... }
After running the function, items returns to its state before calling the function. How should I approach this please?
I'd like to change an array items in-place by filtering out elements matching a condition and returning the removed items [...] How should I approach this please?
You could replace the array content within your method:
def filter_items(items)
removed, kept = items.partition { |i| i.odd? }
items.replace(kept)
removed
end
ary = [1, 2, 3, 4, 5]
filter_items(ary)
#=> [1, 3, 5]
ary
#=> [2, 4]
I would search for pass by value/reference in ruby. Here is one I found first https://mixandgo.com/learn/is-ruby-pass-by-reference-or-pass-by-value.
You pass reference value of items to the function, not the reference to items. Variable items is defined out of method scope and always refers to same value, unless you reassign it in the variable scope.
Also filterItems is not ruby style, see https://rubystyle.guide/
TL;DR
To access or modify an outer variable within a block, declare the variable outside the block. To access a variable outside of a method, store it in an instance or class variable. There's a lot more to it than that, but this covers the use case in your original post.
Explanation and Examples
In Ruby, you have scope gates and closures. In particular, methods and blocks represent scope gates, but there are certainly ways (both routine and meta) for accessing variables outside of your local scope.
In a class, this is usually handled by instance variables. So, as a simple example of String#parition (because it's easier to explain than Enumerable#partition on an Array):
def filter items, separator
head, sep, tail = items.partition separator
#items = tail
end
filter "foobarbaz", "bar"
#=> "baz"
#items
#=> "baz"
Inside a class or within irb, this will modify whatever's passed and then assign it to the instance variable outside the method.
Partitioning Arrays Instead of Strings
If you really don't want to pass things as arguments, or if #items should be an Array, then you can certainly do that too. However, Arrays behave differently, so I'm not sure what you really expect Array#partition (which is inherited from Enumerable) to yield. This works, using Enumerable#slice_after:
class Filter
def initialize
#items = []
end
def filter_array items, separator
#items = [3,4,5].slice_after { |i| i == separator }.to_a.pop
end
end
f = Filter.new
f.filter_array [3, 4, 5], 4
#=> [5]
Look into the Array class for any method which mutates the object, for example all the method with a bang or methods that insert elements.
Here is an Array#push:
ary = [1,2,3,4,5]
def foo(ary)
ary.push *[6, 7]
end
foo(ary)
ary
#=> [1, 2, 3, 4, 5, 6, 7]
Here is an Array#insert:
ary = [1,2,3,4,5]
def baz(ary)
ary.insert(2, 10, 20)
end
baz(ary)
ary
#=> [1, 2, 10, 20, 3, 4, 5]
Here is an example with a bang Array#reject!:
ary = [1,2,3,4,5]
def zoo(ary)
ary.reject!(&:even?)
end
zoo(ary)
ary
#=> [1, 3, 5]
Another with a bang Array#map!:
ary = [1,2,3,4,5]
def bar(ary)
ary.map! { |e| e**2 }
end
bar(ary)
ary
#=> [1, 4, 9, 16, 25]

How to pass arguments directy to a class without calling new method?

In Ruby a Hash can be created by:
Hash(a: 5, b: 6)
An Array can be created like this:
Array(100)
Sets can be created with the code:
require 'set'
Set[1,2,3]
So, how can I define a class that can accept arguments without calling the initialize method?
So, how can I define a class that can accept arguments without calling the initialize method?
You can't. In your examples, Hash and Array are actually methods.
And example with Set uses Set::[], naturally. And so it's not any different from any other class method that returns you instances of that class. For instance, User::create (or what-have-you).
In Ruby a Hash can be created by:
Hash(a: 5, b: 6)
Hash() is actually a method of the Kernel module:
p defined?(Hash()) # => "method"
p defined?(Kernel.Hash()) # => "method"
But without parentheses, Hash, Array, String, etc. all are just classes:
defined?(Hash) # => "constant"
defined?(Array) # => "constant"
In Ruby 2.6.3, the same goes for Arrays(), Complex(), Float(), Hash(), Integer(), Rational(), String(), and URI() - they all are methods.
But Set is a class:
require 'set'
p defined?(Set) # => "constant"
p set = Set[1,2,3] # => #<Set: {1, 2, 3}>
p set.to_a # => [1, 2, 3]
So, Set[1,2,3] is actually calling the [] method of Set. It looks kind of like this:
class Something
def initialize(*a)
#hash = {}
a.each { |v| #hash.store(v, nil) }
end
def self.[](*a) new(*a) end
define_method(:to_a) { #hash.keys }
define_method(:inspect) { "#<#{self.class}: {#{#hash.keys.to_s[1..-2]}}>" }
alias :to_s :inspect
end
p defined?(Something) # => "constant"
p set = Something[1,2,3] # => #<Something: {1, 2, 3}>
p set1 = Something[[1, 2, 3], 2, 2, 3, 4, {4 => :a}, 5] # => #<Something: {[1, 2, 3], 2, 3, 4, {4=>:a}, 5}>
p set.to_a # => [1, 2, 3]
p set1.to_a # => [[1, 2, 3], 2, 3, 4, [4, 4], 5]
Back to the question:
So, how can I define a class that can accept arguments without calling
the initialize method?
I don't think it's possible!

Access `self` of an object through the parameters

Let's say I want to access an element of an array at a random index this way:
[1, 2, 3, 4].at(rand(4))
Is there a way to pass the size of the array like the following?
[1, 2, 3, 4].at(rand(le_object.self.size))
Why would I do that?--A great man once said:
Science isn't about why, it is about why not.
Not recommended, but instance_eval would somehow work:
[1, 2, 3, 4].instance_eval { at(rand(size)) }
And you can also break out of tap:
[1, 2, 3, 4].tap { |a| break a.at(rand(a.size)) }
There's an open feature request to add a method that yields self and returns the block's result. If that makes it into Ruby, you could write:
[1, 2, 3, 4].insert_method_name_here { |a| a.at(rand(a.size)) }
No, you can't do that. Receiver of a method (that array) is not accessible by some special name at the call site. Your best bet is assigning a name to that object.
ary = [1, 2, 3, 4]
ary.at(rand(ary.size))
Of course, if all you need is a random element, then .sample should be used. Which does not require evaluation of any arguments at the call site and its self is the array.
You can use instance_eval to execute ruby code with the binding of the array variable
[1, 2, 3, 4].instance_eval { at(rand(size)) }
Assuming you are interested in a random element as Array#at returns an element at given index, you can use Array#sample to pick a random element from an array.
[1,2,3,4].sample
#=> 3
If you do not want to use instance_eval (or any form of eval), then, you can add a method to Array class by monkey patching - generally speaking, I am not sure whether it's a wise idea to monkey patch though
class Array
def random_index
rand(size)
end
end
["a","b","c","d"].random_index
#=> 2
You could do something similar with lambda:
getrand = ->(x) { x[rand(x.count)] }
getrand.call [1,2,3]
# => 2

Create an array of arrays

I'm trying to create an array of arrays to be used in a JavaScript function.
Here is the format of the array that I'm trying to create:
[[1,1],[2,3],[3,6],[4,10],[5,15],[6,21]]
Here is the ruby code to create the array:
total=0
foo=[]
(1..6).each do |number|
foo.push [number, total+=number]
end
puts foo
Here is the output of puts foo:
1
1
2
3
3
6
4
10
5
15
6
21
Any ideas how to output the correctly formatted array?
If I understand that correctly, you want to output the array somewhere in a document to be interpreted as JavaScript by the browser.
When it comes to using Ruby objects in JavaScript, you can use the JSON gem.
require 'json'
#create the array
foo.to_json
should do the trick.
This also works for hashes and some other object types.
Change puts foo to foo.inspect
total=0
foo=[]
(1..6).each do |number|
foo.push [number, total+=number]
end
foo.inspect
You can use p foo to print out the array:
total=0
foo=[]
(1..6).each do |number|
foo.push [number, total+=number]
end
p foo
This prints out: [[1, 1], [2, 3], [3, 6], [4, 10], [5, 15], [6, 21]]

Why do Ι get this error when trying to scrape a reddit Json file?

I'm trying to write a Ruby program that will retrieve all the reddit usernames from a Json file. I can get it do display the list, but there is an error after the first username every time.
require 'net/http'
require 'rubygems'
require 'json'
require 'pp'
#response = Net::HTTP.get(URI.parse("http://www.reddit.com/r/AskReddit/comments/sl1nn /could_codeine_help_me_sleep_is_it_dangerous/.json"))
result = JSON.parse(#response)
comments = result[1]['data']['children'] #this is now an array of comment hashes
(0..comments.length).each do |i|
comment = comments[i]['data']
puts comment['author']
end
Although it displays the list, I also get this error:
in block in <main>': undefined method[]' for nil:NilClass (NoMethodError)
Does anyone know I can solve this?
My guess is it's the off by one error.
a = [1, 2, 3, 4, 5]
(0..a.length).map { |n| a[n] }
# => [1, 2, 3, 4, 5, nil]
This is because
(0..5).to_a
# => [0, 1, 2, 3, 4, 5]
To be non-inclusive, you should use ..., that is:
(0...comments.length).each do |i|
Better yet, since comments is an array, you can do:
comments.each do |comment|
puts comment["data"]["author"]
end

Resources