Inserting variables into arrays - ruby

I want to generate text and insert them into an array. Please help.
new_vrms = Array.new[3] {"placeholder"}
puts "How many registration marks do you require?"
how_many = gets.chomp!()
i = 0
while i < how_many.to_i do
prefix =('a'..'z').to_a.shuffle.first(2).join
year = 68
suffix =('a'..'z').to_a.shuffle.first(3).join
aVRM = (prefix.to_s + year.to_s + suffix.to_s)
aVRM = aVRM.upcase!
puts ("#{aVRM} added to index #{i}")
#new_vrms.insert(0, 1) <-Array.rb:14:in `<main>': undefined method `insert' for nil:NilClass (NoMethodError)
#new_vrms.push << #aVRM <-Array.rb:15:in `<main>': undefined method `push' for nil:NilClass (NoMethodError)
#new_vrms[i] = ("#{aVRM}") <- Array.rb:16:in `<main>': undefined method `[]=' for nil:NilClass (NoMethodError)
i += 1
end
puts ("Succesfully generated "+ i.to_s + " registration marks")

The error is in the array initialization. What you have there (Array.new[3]) is seen by ruby as
(Array.new)[3]
You want to pass 3 to new as an argument.
Array.new(3)

This is an extended comment about the OPs code in general, so no upvotes please (downvotes are OK).
You begin with
new_vrms = Array.new[3] {"placeholder"}
#Sergio has identified your problem here, but beyond that there is no need to initialize the value of each element of the array ("placeholder") or even fix the size of the array. Indeed, you evidently wish to return the array with how_many elements and how_many is not yet know. Therefore, you should simply create an empty array here:
new_vrms = Array.new(0)
which is the same as
new_vrms = Array.new
which is more commonly written
new_vrms = []
Next you ask the user how many elements should be in the array:
how_many = gets.chomp!()
If the user enters "3", gets will return "3\n" and gets.chomp will return "3". Notice there is no need to end a method (here chomp) with () when it has no arguments. Also, chomp! is not incorrect but the non-mutating version, chomp, is generally used. You want how_many to be an integer, however, not a string (gets always returns a string). You therefore need to convert it to an integer:
how_many = gets.chomp.to_i
If you look at the doc for String#to_i, however, you will see that "123abc".to_i #=> 123, so "123\n".to_i #=> 123, meaning that we don't need chomp when converting a string to an integer:
how_any = gets.to_i
We now know the number of times we wish to repeat the loop (how_many), so you will want to use an iterator and block rather than a while loop:
how_many.times do |i|
...<your code to add elements to new_vrms>
end
new_vrms # return the computed value
See Integer#times.
You are repeating code in calculating prefix and suffix, so let's make that a separate method:
LETTERS = ('A'..'Z').to_a
#=> ["A", "B", "C",...,"X", "Y", "Z"]
def random_3_letters
LETTERS.sample(3).join
end
See Array#sample, which provides a more direct way of computing the random triples of letters. (Also, we may draw from an array of uppercase letters so we don't need to convert the samples to uppercase later.1 I created the constant LETTERS so that we don't need to create the array each time the method is called. We can now write the block.
YEAR = "68"
how_many.times do |i|
prefix = random_3_letters
suffix = random_3_letters
aVRM = prefix + YEAR + SUFFIX
puts "#{aVRM} added to index #{i}")
new_vrms.push(aVRM)
end
new_vrms
There is, in fact, no reason to define the variables prefix and suffix, as we can simplify as follows.
YEAR = "68"
how_many.times do |i|
aVRM = random_3_letters + YEAR + random_3_letters
puts "#{aVRM} added to index #{i}")
new_vrms.push(aVRM)
end
new_vrms
If you wish to print the value of each element of aVRM it's best to do that from outside the loop--more generally from outside a method you will wrap around the code. If the statement puts "#{aVRM} added to index #{i}") is extracted from the block the block variable i is no longer used, so it can be omitted:
YEAR = "68"
def doit
new_vrms = []
gets.to_i.times do
new_vrms << random_3_letters + YEAR + random_3_letters
end
new_vrms
end
Notice that I've changed Array#push to the more-commonly used Array#<< and substituted out the variables how_many and aVRS.
An experienced Rubyist might tighten this up even more (though input values would also be checked for validity in real code):
LETTERS = ('A'..'Z').to_a
YEAR = "68"
def doit
gets.to_i.times.with_object([]) do |_,new_vrms|
new_vrms << (random_3_letters + YEAR + random_3_letters)
end
end
def random_3_letters
LETTERS.sample(3).join
end
doit # entering "4"
#=> ["ZPI68LWY", "PQV68HLD", "IZG68JCH", "EAC68WLY"]
Notice that by using Enumerator#with_object we eliminate the statement new_vrms = [] and new_vrms at the end, the latter because with_object causes the block to return the "object", the value of new_vrms.
1 Note that you should never write str = str.upcase! because str.upcase! returns nil if str is already upcased (and therefore no changes are made to str). See String#upcase!. Many "bang" methods ...! return nil when no change is made to the receiver.

Related

How to use reduce/inject in Ruby without getting Undefined variable

When using an accumulator, does the accumulator exist only within the reduce block or does it exist within the function?
I have a method that looks like:
def my_useless_function(str)
crazy_letters = ['a','s','d','f','g','h']
str.split.reduce([]) do |new_array, letter|
for a in 0..crazy_letters.length-1
if letter == crazy_letters[a]
new_array << letter
end
end
end
return true if (new_array == new_array.sort)
end
When I execute this code I get the error
"undefined variable new_array in line 11 (the return statement)"
I also tried assigning the new_array value to another variable as an else statement inside my reduce block but that gave me the same results.
Can someone explain to me why this is happening?
The problem is that new_array is created during the call to reduce, and then the reference is lost afterwards. Local variables in Ruby are scoped to the block they are in. The array can be returned from reduce in your case, so you could use it there. However, you need to fix a couple things:
str.split does not break a string into characters in Ruby 2+. You should use str.chars, or str.split('').
The object retained for each new iteration of reduce must be retained by returning it from the block each time. The simplest way to do this is to put new_array as the last expression in your block.
Thus:
def my_useless_function(str)
crazy_letters = ['a','s','d','f','g','h']
crazy_only = str.split('').reduce([]) do |new_array, letter|
for a in 0..crazy_letters.length-1
if letter == crazy_letters[a]
new_array << letter
end
end
new_array
end
return true if (crazy_only == crazy_only.sort)
end
Note that your function is not very efficient, and not very idiomatic. Here's a shorter version of the function that is more idiomatic, but not much more efficient:
def my_useless_function(str)
crazy_letters = %w[a s d f g h]
crazy_only = str.chars.select{ |c| crazy_letters.include?(c) }
crazy_only == crazy_only.sort # evaluates to true or false
end
And here's a version that's more efficient:
def efficient_useless(str)
crazy_only = str.scan(/[asdfgh]/) # use regex to search for the letters you want
crazy_only == crazy_only.sort
end
Block local variables
new_array doesn't exist outside the block of your reduce call. It's a "block local variable".
reduce does return an object, though, and you should use it inside your method.
sum = [1, 2, 3].reduce(0){ |acc, elem| acc + elem }
puts sum
# 6
puts acc
# undefined local variable or method `acc' for main:Object (NameError)
Your code
Here's the least amount of change for your method :
def my_useless_function(str)
crazy_letters = ['a','s','d','f','g','h']
new_array = str.split(//).reduce([]) do |new_array, letter|
for a in 0..crazy_letters.length-1
if letter == crazy_letters[a]
new_array << letter
end
end
new_array
end
return true if (new_array == new_array.sort)
end
Notes:
return isn't needed at the end.
true if ... isn't needed either
for loop should never be used in Ruby
reduce returns the result of the last expression inside the block. It was for in your code.
If you always need to return the same object in reduce, it might be a sign you could use each_with_object.
"test".split is just ["test"]
String and Enumerable have methods that could help you. Using them, you could write a much cleaner and more efficient method, as in #Phrogz answer.

Could someone explain line 2 to line 9 of this Ruby code?

def caesar_cipher(string, shift_factor)
string.length.times do |i|
if string[i].ord >= 97 && (string[i].ord + shift_factor) <= 122 || string[i].ord >= 65 && (string[i].ord + shift_factor) <= 90
string[i] = (string[i].ord + shift_factor).chr
elsif string[i].ord >= 97 && string[i].ord <= 122 || string[i].ord >= 65 && string[i].ord <= 90
string[i] = (string[i].ord + shift_factor - 122 + 96).chr
end
end
string
end
puts "Enter a string:"
string_input = gets.chomp
puts "Enter shift factor:"
shift_factor_input = gets.chomp.to_i
result_string = caesar_cipher(string_input, shift_factor_input)
puts result_string
https://github.com/OlehSliusar/caesar_cipher
A command line Caesar Cipher that takes in a string and the shift factor and then outputs the modified string.
I am unable to understand code line 2 to line 9. I am confused on how the .times method is used in this context. Could someone explain to me what is he doing from line 2 to line 9? How I understand .times method is that it act as a iterator as iterate based on the number time stated.
So say 5.times { puts "Dog" } = > will result in putting "Dog" five time. Hence my understanding on the method .times is very different from the way the author used it.
This is an extended comment which does not answer the question (so no upvotes please).
That piece of code is ugly and arcane, not at all Ruby-like. Here's a another way that makes better use of Ruby's tools and is effectively self-documenting.
Code
def caesar_cipher_encrypt(string, shift_size)
mapping = Hash.new { |h,k| k }.
merge(make_map('a', shift_size)).
merge(make_map('A', shift_size))
string.gsub(/./, mapping)
end
def make_map(first_char, shift_size)
base = first_char.ord
26.times.with_object({}) { |i,h| h[(base+i).chr] = (base+((i+shift_size) % 26)).chr }
end
Example
shift_size = 2
encrypted_str = caesar_cipher_encrypt("Mary said to Bob, 'Get lost!'.", shift_size)
#=> "Octa uckf vq Dqd, 'Igv nquv!'."
Explanation
The first step is to create a hash that maps letters into their shifted counterparts. We begin with
h = Hash.new { |h,k| k }
#= {}
This creates an empty hash with a default value given by the block. That means that if h does not have a key k, h[k] returns k. Since all keys of 'h' will be letters, this means the value of a digit, space, punctuation mark or any other non-letter will be itself. See Hash::new.
We then have
f = make_map('a',2)
#=> {"a"=>"c", "b"=>"d", "c"=>"e",..., "x"=>"z", "y"=>"a", "z"=>"b"}
g = h.merge(f)
#=> {"a"=>"c", "b"=>"d", "c"=>"e",..., "y"=>"a", "z"=>"b"}
f = make_map('A',2)
#=> {"A"=>"C", "B"=>"D", "C"=>"E",..., "X"=>"Z", "Y"=>"A", "Z"=>"B"}
mapping = g.merge(f)
#=> {"a"=>"c", "b"=>"d", "c"=>"e",..., "y"=>"a", "z"=>"b",
# "A"=>"C", "B"=>"D", "C"=>"E",..., "Y"=>"A", "Z"=>"B"}
mapping['!']
#=> "!"
We may now simply use the form of String#gsub that uses a hash to perform substitutions.
"Mary said to Bob, 'Get lost!'.".gsub(/./, mapping)
#=> "Octa uckf vq Dqd, 'Igv nquv!'."
Decrypting
The receiver of an encrypted message can decrypt it as follows.
def caesar_cipher_decrypt(string, shift_size)
caesar_cipher_encrypt(string, -shift_size)
end
caesar_cipher_decrypt(encrypted_str, shift_size)
#=> "Mary said to Bob, 'Get lost!'."
.times do means "execute this code a certain number of times" as you said.
.times do |i| loops a certain number of times and counts each time in i
string.length gets the number of characters in the string.
string.length.times executes a block of code a number of times equal to the number of characters in the string.
string[i] accesses the i-th character in the string.
Putting it all together:
string.length.times do |i|
do_stuff_with string[i]
end
you have code which iterates through each character in the string and does something to it. in this case, the code shifts each character according to caesar cipher.
When you use iterators like string.each_char in ruby or foreach(item in items) in other languages, you're not generally allowed to modify the collection while you iterate. Using .times and string[i] lets the code modify the string while it iterates. because the loop doesn't keep track of string, it just knows that it needs to execute some number of times.
As others have pointed out, there are more elegant, more ruby-like ways to do what this code does, but the writer of the code chose .times because it acts just like a for-loop, which is a common programming paradigm.
Perhaps this will explain it:
string = 'foo'
string.length.times {|i| puts string[i]}
Its a way of iterating through each letter in the string. They could probably do the same thing via:
string.chars.collect{|character| p(character)}.join
and have cleaner code as a result (where p(character) would be replaced by the required manipulation of the current character)
For example this code:
'foo'.chars.collect{|c| (c.ord + 1).chr}.join
Iterates through the string and returns a new string with each character replaced with the next one in the alphabet. That is: "gpp"

Geting a Typeerror with hashes (No implicit conversion of symbol into integer)

I made a sample script. Here is the sample script I am running:
def array_generator
signalp_array = Array.new(11){ Array.new(11,0) }
signalp = Hash.new
file = File.readlines("./sample.txt")
file.each_with_index do |line, idx|
row = line.gsub(/\s+/m, ' ').chomp.split(" ") # split the line into a array based on white space.
signalp_array[idx][0..row.length - 1] = row # Merge into existing array
end
signalp_array.each do |g|
seq_id = g[0]
cut_off = g[4]
d_value = g[8]
signalp[seq_id] = [:cut_off => cut_off, :d_value => d_value]
end
return signalp
end
signalp = array_generator
puts signalp
signalp.each do |id, neww|
puts id
puts neww[ :cut_off]
puts neww[ :d_value]
end
with which I am getting the following output and error:
isotig00001_f1_3
in `[]': no implicit conversion of Symbol into Integer (TypeError)
Since the puts signalp line gives me the following:
{"isotig00001_f1_3"=>[{:cut_off=>"11", :d_value=>"0.132"}], "isotig00001_f1_5"=>[{:cut_off=>"11", :d_value=>"0.162"}], "isotig00001_f1_7"=>[{:cut_off=>"11", :d_value=>"0.397"}], "isotig00001_f1_8"=>[{:cut_off=>"11", :d_value=>"0.259"}], "isotig00001_f1_9"=>[{:cut_off=>"11", :d_value=>"0.110"}], "isotig00001_f1_10"=>[{:cut_off=>"11", :d_value=>"0.135"}], "isotig00001_f1_11"=>[{:cut_off=>"1", :d_value=>"0.000"}], "isotig00001_f1_12"=>[{:cut_off=>"12", :d_value=>"0.117"}], "isotig00001_f2_0"=>[{:cut_off=>"11", :d_value=>"0.108"}], "isotig00001_f2_1"=>[{:cut_off=>"28", :d_value=>"0.122"}], "isotig00001_f2_3"=>[{:cut_off=>"19", :d_value=>"0.097"}]}
the hash is created properly. However I cannot access the :cut_off and :d_value individually (probably, because they are digits). I tried to_i, to_s methods etc.
Could someone let me know what I am doing wrong?
Any ideas on what to search for or where to learn more on the topic?
neww is not a hash, it is an array [{:cut_off=>"11", :d_value=>"0.132"}]. Do
puts neww[0][:cut_off]
puts neww[0][:d_value]
The values in your neww hash are arrays of hashes, not just bare hashes. You need to index into the array before keying into the hash. That is:
puts neww[0][:cut_off]

Subclassing Ruby Hash, object has no methods of Hash?

I'm creating a object of hash in order to write a little script that reads in a file a line at a time, and assigns arrays into my hash class. I get wildly different results depending if I subclass Hash or not, plus using super changes things which I don't' understand.
My main issue is that without subclassing hash ( < Hash) it works perfectly, but I get no methods of Hash (like to iterate over the keys and get things out of it.... Subclassing Hash lets me do those things, but it seems that only the last element of the hashed arrays is ever stored.... so any insight into how you get the methods of a subclass. The Dictionary class is a great example I found on this site, and does exactly what I want, so I'm trying to understand how to use it properly.
filename = 'inputfile.txt.'
# ??? class Dictionary < Hash
class Dictionary
def initialize()
#data = Hash.new { |hash, key| hash[key] = [] }
end
def [](key)
#data[key]
end
def []=(key,words)
#data[key] += [words].flatten
#data[key]
# super(key,words)
end
end
listData = Dictionary.new
File.open(filename, 'r').each_line do |line|
line = line.strip.split(/[^[:alpha:]|#|\.]/)
puts "LIST-> #{line[0]} SUB-> #{line[1]} "
listData[line[0]] = ("#{line[1]}")
end
puts '====================================='
puts listData.inspect
puts '====================================='
print listData.reduce('') {|s, (k, v)|
s << "The key is #{k} and the value is #{v}.\n"
}
If anyone understands what is going on here subclassing hash, and has some pointers, that would be excellent.
Running without explicit < Hash:
./list.rb:34:in `<main>': undefined method `reduce' for #<Dictionary:0x007fcf0a8879e0> (NoMethodError)
That is the typical error I see when I try and iterate in any way over my hash.
Here is a sample input file:
listA billg#microsoft.com
listA ed#apple.com
listA frank#lotus.com
listB evanwhite#go.com
listB joespink#go.com
listB fredgrey#stop.com
I can't reproduce your problem using your code:
d = Dictionary.new #=> #<Dictionary:0x007f903a1adef8 #data={}>
d[4] << 5 #=> [5]
d[5] << 6 #=> [6]
d #=> #<Dictionary:0x007f903a1adef8 #data={4=>[5], 5=>[6]}>
d.instance_variable_get(:#data) #=> {4=>[5], 5=>[6]}
But of course you won't get reduce if you don't subclass or include a class/module that defines it, or define it yourself!
The way you have implemented Dictionary is bound to have problems. You should call super instead of reimplementing wherever possible. For example, simply this works:
class Dictionary < Hash
def initialize
super { |hash, key| hash[key] = [] }
end
end
d = Dictionary.new #=> {}
d['answer'] << 42 #=> [42]
d['pi'] << 3.14 #=> [3.14
d #=> {"answer"=>[42], "pi"=>[3.14]}
If you want to reimplement how and where the internal hash is stored (i.e., using #data), you'd have to reimplement at least each (since that is what almost all Enumerable methods call to) and getters/setters. Not worth the effort when you can just change one method instead.
While Andrew Marshall's answer
already correct, You could also try this alternative below.
Going from your code, We could assume that you want to create an object that
act like a Hash, but with a little bit different behaviour. Hence our first
code will be like this.
class Dictionary < Hash
Assigning a new value to some key in the dictionary will be done differently
in here. From your example above, the assignment won't replace the previous
value with a new one, but instead push the new value to the previous or to
a new array that initialized with the new value if the key doesn't exist yet.
Here I use the << operator as the shorthand of push method for Array.
Also, the method return the value since it's what super do (see the if part)
def []=(key, value)
if self[key]
self[key] << value
return value # here we mimic what super do
else
super(key, [value])
end
end
The advantage of using our own class is we could add new method to the class
and it will be accessible to all of it instance. Hence we need not to
monkeypatch the Hash class that considered dangerous thing.
def size_of(key)
return self[key].size if self[key]
return 0 # the case for non existing key
end
Now, if we combine all above we will get this code
class Dictionary < Hash
def []=(key, value)
if self[key]
self[key] << value
return value
else
super(key, [value])
end
end
def size_of(key)
return self[key].size if self[key]
return 0 # the case for non existing key
end
end
player_emails = Dictionary.new
player_emails["SAO"] = "kirito#sao.com" # note no << operator needed here
player_emails["ALO"] = "lyfa#alo.com"
player_emails["SAO"] = "lizbeth#sao.com"
player_emails["SAO"] = "asuna#sao.com"
player_emails.size_of("SAO") #=> 3
player_emails.size_of("ALO") #=> 1
player_emails.size_of("GGO") #=> 0
p listData
#=> {"SAO" => ["kirito#sao.com", "lizbeth#sao.com", "asuna#sao.com"],
#=> "ALO" => ["lyfa#alo.com"] }
But, surely, the class definition could be replaced with this single line
player_emails = Hash.new { [] }
# note that we wont use
#
# player_emails[key] = value
#
# instead
#
# player_emails[key] << value
#
# Oh, if you consider the comment,
# it will no longer considered a single line
While the answer are finished, I wanna comment some of your example code:
filename = 'inputfile.txt.'
# Maybe it's better to use ARGF instead,
# so you could supply the filename in the command line
# and, is the filename ended with a dot? O.o;
File.open(filename, 'r').each_line do |line|
# This line open the file anonimously,
# then access each line of the file.
# Please correct me, Is the file will properly closed? I doubt no.
# Saver version:
File.open(filename, 'r') do |file|
file.each_line do |line|
# ...
end
end # the file will closed when we reach here
# ARGF version:
ARGF.each_line do |line|
# ...
end
# Inside the each_line block
line = line.strip.split(/[^[:alpha:]|#|\.]/)
# I don't know what do you mean by that line,
# but using that regex will result
#
# ["listA", "", "", "billg#microsoft.com"]
#
# Hence, your example will fail since
# line[0] == "listA" and line[1] == ""
# also note that your regex mean
#
# any character except:
# letters, '|', '#', '|', '\.'
#
# If you want to split over one or more
# whitespace characters use \s+ instead.
# Hence we could replace it with:
line = line.strip.split(/\s+/)
puts "LIST-> #{line[0]} SUB-> #{line[1]} "
# OK, Is this supposed to debug the line?
# Tips: the simplest way to debug is:
#
# p line
#
# that's all,
listData[line[0]] = ("#{line[1]}")
# why? using (), then "", then #{}
# I suggest:
listData[line[0]] = line[1]
# But to make more simple, actually you could do this instead
key, value = line.strip.split(/\s+/)
listData[key] = value
# Outside the block:
puts '====================================='
# OK, that's too loooooooooong...
puts '=' * 30
# or better assign it to a variable since you use it twice
a = '=' * 30
puts a
p listData # better way to debug
puts a
# next:
print listData.reduce('') { |s, (k, v)|
s << "The key is #{k} and the value is #{v}.\n"
}
# why using reduce?
# for debugging you could use `p listData` instead.
# but since you are printing it, why not iterate for
# each element then print each of that.
listData.each do |k, v|
puts "The key is #{k} and the value is #{v}."
end
OK, sorry for blabbering so much, Hope it help.

ruby string array iteration. Array of arrays

I have a ruby problem
Here's what i'm trying to do
def iterate1 #define method in given class
#var3 = #var2.split(" ") #split string to array
#var4 = #var3
#var4.each do |i| #for each array item do i
ra = []
i.each_char {|d| ra << counter1(d)} # for each char in i, apply def counter1
#sum = ra.inject(:+)
#sum2 = #sum.inject(:+) #have to do the inject twice to get values
end
#sum2
I know i have over complicated this
Basically the input is a string of letters and values like "14556 this word 398"
I am trying to sum the numbers in each value, seperated by the whitespace like (" ")
When i use the def iterate1 method the block calls the counter1 method just fine, but i can only get the value for the last word or value in the string.
In this case that's 398, which when summed would be 27.
If i include a break i get the first value, which would be 21.
I'm looking to output an array with all of the summed values
Any help would be greatly appreciated
I think you're after:
"10 d 20 c".scan(/\b\d+\b/).map(&:to_i).inject(:+) # Returns 30
scan(/\b\d+\b/) will extract all numbers that are made up of digits only in an array, map(&:to_i) will convert them to integers and I guess you already know what inject(:+) will do.
I'm not sure if I understand what you're after correctly, though, so it might help if you provide the answer you expect to this input.
EDIT:
If you want to sum the digits in each number, you can do it with:
"12 d 34 c".scan(/\b\d+\b/).map { |x| x.chars.map(&:to_i).inject(:+) }
x.chars will return an enumerator for the digits, map(&:to_i) will convert them to integers and inject(:+) will sum them.
The simplest answer is to use map instead of each because the former collects the results and returns an array. e.g:
def iterate1 #define method in given class
#var3 = #var2.split(" ") #split string to array
#var4 = #var3
#var4.map do |i| #for each array item do i
ra = []
i.each_char {|d| ra << counter1(d)} # for each char in i, apply def counter1
#sum = ra.inject(:+)
#sum2 = #sum.inject(:+) #have to do the inject twice to get values
end
end
You could write it a lot cleaner though and I think Stefan was a big help. You could solve the issue with a little modification of his code
# when you call iterate, you should pass in the value
# even if you have an instance variable available (e.g. #var2)
def iterate(thing)
thing.scan(/\b\d+\b/).map do |x|
x.chars.map{|d| counter1(d)}.inject(:+)
end
end
The above assumes that the counter1 method returns back the value as an integer

Resources