Ruby find all Constants - ruby

I am new to Ruby so sorry if it's a simple question.
I want to open a ruby file and search all constants, but I don't know the right regular expression.
Here is my simplified code:
def findconst()
filename = #path_main
k= {}
akonstanten = []
k[:konstanten] = akonstanten
if (File.exists?(filename))
file = open(filename, "r")
while (line = file.gets)
if (line =~ ????)
k[:konstanten] << line
end
end
end
end

You can use Ripper library to extract the tokens.
For example, this code will return you constants and methods names for the file
A = "String" # Comment
B = <<-STR
Yet Another String
STR
class C
class D
def method_1
end
def method_2
end
end
end
require "ripper"
tokens = Ripper.lex(File.read("file.rb"))
pp tokens.group_by { |x| x[1] }[:on_ident].map(&:last)
pp tokens.group_by { |x| x[1] }[:on_const].map(&:last)
# => ["method_1", "method_2"]
# => ["A", "B", "C", "D"]

As Sergio Says searching for words with Caps won't just give you constants but if it's good enough it's good enough.
The regexpression you are looking for is something like
if (line =~ /[^a-z][A-Z]/)
Which says match any capital that is not preceded by a lower case letter. Of course this will only count one per line so you might like to consider tokenising the stream and working on tokens, not lines.

Related

Having trouble adding new elements to my hash (Ruby)

new to Ruby, new to coding in general...
I'm trying to add new elements into my hash, incrementing the value when necessary. So I used Hash.new(0) and I'm trying to add new values using the "+=" symbol, but when I do this I get an error message -
"/tmp/file.rb:6:in `+': String can't be coerced into Integer (TypeError)
from /tmp/file.rb:6:in `block in stockList'
from /tmp/file.rb:3:in `each'
from /tmp/file.rb:3:in `each_with_index'
from /tmp/file.rb:3:in `stockList'
from /tmp/file.rb:24:in `<main>'
"
Here's my code:
def stockList(stock, cat)
hash = Hash.new(0)
stock.each_with_index do |word, i|
if cat.include?(word[i])
char = word[i]
hash[char] += num(word)
end
end
new_arr = []
hash.each do |k, v|
new_arr.push(k,v)
end
return new_arr
end
def num(word)
nums = "1234567890"
word.each_char.with_index do |char, i|
if nums.include?(char)
return word[i..-1]
end
end
end
puts stockList(["ABAR 200", "CDXE 500", "BKWR 250", "BTSQ 890", "DRTY 600"], ["A", "B"])
Does anyone know why this is happening?
It's a codewars challenge -- I'm basically given two arrays and am meant to return a string that adds the numbers associated with the word that starts with the letter(s) listed in the second array.
For this input I'm meant to return " (A : 200) - (B : 1140) "
Your immediate problem is that num(word) returns a string, and a string can't be added to a number in the line hash[char] += num(word). You can convert the string representation of a numeric value using .to_i or .to_f, as appropriate for the problem.
For the overall problem I think you've added too much complexity. The structure of the problem is:
Create a storage object to tally up the results.
For each string containing a stock and its associated numeric value (price? quantity?), split the string into its two tokens.
If the first character of the stock name is one of the target values,
update the corresponding tally. This will require conversion from string to integer.
Return the final tallies.
One minor improvement is to use a Set for the target values. That reduces the work for checking inclusion from O(number of targets) to O(1). With only two targets, the improvement is negligible, but would be useful if the list of stocks and targets increase beyond small test-case problems.
I've done some renaming to hopefully make things clearer by being more descriptive. Without further ado, here it is in Ruby:
require 'set'
def get_tallies(stocks, prefixes)
targets = Set.new(prefixes) # to speed up .include? check below
tally = Hash.new(0)
stocks.each do |line|
name, amount = line.split(/ +/) # one or more spaces is token delimiter
tally[name[0]] += amount.to_i if targets.include?(name[0]) # note conversion to int
end
tally
end
stock_list = ["ABAR 200", "CDXE 500", "BKWR 250", "BTSQ 890", "DRTY 600"]
prefixes = ["A", "B"]
p get_tallies(stock_list, prefixes)
which prints
{"A"=>200, "B"=>1140}
but that can be formatted however you like.
The particular issue triggering this error is that your def num(word) is essentially a no-op, returning the word without any change.
But you actually don't need this function: this...
word.delete('^0-9').to_i
... gives you back the word with all non-digit characters stripped, cast to integer.
Note that without to_i you'll still receive the "String can't be coerced into Integer" error: Ruby is not as forgiving as JavaScript, and tries to protect you from results that might surprise you.
It's a codewars challenge -- I'm basically given two arrays and am
meant to return a string that adds the numbers associated with the
word that starts with the letter(s) listed in the second array.
For this input I'm meant to return " (A : 200) - (B : 1140) "
This is one way to get there:
def stockList(stock, cat)
hash = Hash.new(0)
stock.each do |word|
letter = word[0]
if cat.include?(letter)
hash[letter] += word.delete('^0-9').to_i
end
end
hash.map { |k, v| "#{k}: #{v}" }
end
Besides type casting, there's another difference here: always choosing the initial letter of the word. With your code...
stock.each_with_index do |word, i|
if cat.include?(word[i])
char = word[i]
... you actually took the 1st letter of the 1st ticker, the 2nd letter of the 2nd ticker and so on. Don't use indexes unless your results depend on them.
stock = ["ABAR 200", "CDXE 500", "BKWR 250", "BTSQ 890", "DRTY 600"]
cat = ["A", "B"]
I concur with your decision to create a hash h with the form of Hash::new that takes an argument (the "default value") which h[k] returns when h does not have a key k. As a first step we can write:
h = stock.each_with_object(Hash.new(0)) { |s,h| h[s[0]] += s[/\d+/].to_i }
#=> {"A"=>200, "C"=>500, "B"=>1140, "D"=>600}
Then Hash#slice can be used to extract the desired key-value pairs:
h = h.slice(*cat)
#=> {"A"=>200, "B"=>1140}
At this point you have all the information you need to display the result any way you like. For example,
" " << h.map { |k,v| "(#{k} : #{v})" }.join(" - ") << " "
#=> " (A : 200) - (B : 1140) "
If h before h.slice(*cat) is large relative to h.slice(*cat) you can reduce memory requirements and probably speed things somewhat by writing the following.
require 'set'
cat_set = cat.to_set
#=> #<Set: {"A", "B"}>
h = stock.each_with_object(Hash.new(0)) do |s,h|
h[s[0]] += s[/\d+/].to_i if cat_set.include?(s[0])
end
#=> {"A"=>200, "B"=>1140}

How to use gsub with a file in Ruby?

Hey I've a little problem, I've a string array text_word and I want to replace some letters with my file transform.txt, my file looks like this:
/t/ 3
/$/ 1
/a/ !
But when I use gsub I get an Enumerator back, does anyone know how to fix this?
text_transform= Array.new
new_words= Array.new
File.open("transform.txt", "r") do |fi|
fi.each_line do |words|
text_transform << words.chomp
end
end
text_transform.each do |transform|
text_word.each do |words|
new_words << words.gsub(transform)
end
end
You can see String#gsub
If the second argument is a Hash, and the matched text is one of its
keys, the corresponding value is the replacement string.
Also you can use IO::readlines
File.readlines('transform.txt', chomp: true).map { |word| word.gsub(/[t$a]/, 't' => 3, '$' => 1, 'a' => '!') }
gsub returns an Enumerator when you provide just one argument (the pattern). If you want to replace just add the replacement string:
pry(main)> 'this is my string'.gsub(/i/, '1')
"th1s 1s my str1ng"
You need to refactor your code:
text_transform = Array.new
new_words = Array.new
File.open("transform.txt", "r") do |fi|
fi.each_line do |words|
text_transform << words.chomp.strip.split # "/t/ 3" -> ["/t/", "3"]
end
end
text_transform.each do |pattern, replacement| # pattern = "/t/", replacement = "3"
text_word.each do |words|
new_words << words.gsub(pattern, replacement)
end
end

having trouble with .next method making a casear cipher

I'm not worried about what happens if my key will go past Z right now, or capital letters. All I want is my outcome to be something like. text=abc key=2 and it print "cde". Where am I going wrong?
puts "What would you like to cipher?"
text = gets.chomp
puts " what number key would you like?"
key = gets.chomp.to_i
def casear_cipher(text,key)
ciphered_text = []
text.chars.each do |letter|
ciphered_text = letter
ciphered_text = ciphered_text.next
end
end
puts casear_cipher(text,key)
You're not using the key yet, so it will always just do abc -> bcd. If you're really not concerned about "Z" going to "AA", you can try this:
def cipher(text, key)
text.chars.map { |c| (c.ord + key).chr }.join
end
Since 'Z'.next => 'AA' and 'z'.next #=> 'aa', we can use [-1] to select the last letter.
In the code below we perform next! on each character n times using the times method. next! modifies the character whereas next does not.
def casear_cipher(text, n)
text.chars.map do |c| n.times { c.next! }
c[-1]
end.join
end
p casear_cipher('abc',2) #=> "cde"
p casear_cipher('xyz',2) #=> "zab"
p casear_cipher('ZEBRA',2) #=> "BGDTC"
More information about these methods can be found at http://www.ruby-doc.org/core-2.4.1/

Trying to find vowels of a string using Ruby while loops

def count_vowels(string)
vowels = ["a", "e", "i", "o", "u"]
i = 0
j = 0
count = 0
while i < string.length do
while j < vowels.length do
if string[i] == vowels[j]
count += 1
break
end
j += 1
end
i += 1
end
puts count
end
I'm having trouble spotting where this goes wrong. If this program encounters a consonant, it stops. Also, how would the same problem be solved using the ".each" method?
The problem is that you never reset j to zero.
The first time your outer while loop runs, which is to compare the first character of string to each vowel, j is incremented from 0 (for "a") to 4 (for "u"). The second time the outer loop runs, however, j is already 4, which means it then gets incremented to 5, 6, 7 and on and on. vowels[5], vowels[6], etc. all evaluate to nil, so characters after the first are never counted as vowels.
If you move the j = 0 line inside the outer while loop, your method works correctly.
Your second question, about .each, shows that you're already thinking along the right lines. while is rarely seen in Ruby and .each would definitely be an improvement. As it turns out, you can't call .each on a String (because the String class doesn't include Enumerable), so you have to turn it into an Array of characters first with the String#chars method. With that, your code would look like this:
def count_vowels(string)
chars = string.chars
vowels = ["a", "e", "i", "o", "u"]
count = 0
chars.each do |char|
vowels.each do |vowel|
if char == vowel
count += 1
break
end
end
end
puts count
end
In Ruby, though, we have much better ways to do this sort of thing. One that fits particularly well here is Array#count. It takes a block and evaluates it for each item in the array, then returns the number of items for which the block returned true. Using it we could write a method like this:
def count_vowels(string)
chars = string.chars
vowels = ["a", "e", "i", "o", "u"]
count = chars.count do |char|
is_vowel = false
vowels.each do |vowel|
if char == vowel
is_vowel = true
break
end
end
is_vowel
end
puts count
end
That's not much shorter, though. Another great method we can use is Enumerable#any?. It evaluates the given block for each item in the array and returns true upon finding any item for which the block returns true. Using it makes our code super short, but still readable:
def count_vowels(string)
chars = string.chars
vowels = %w[ a e i o u ]
count = chars.count do |char|
vowels.any? {|vowel| char == vowel }
end
puts count
end
(Here you'll see I threw in another common Ruby idiom, the "percent literal" notation for creating an array: %w[ a e i o u ]. It's a common way to create an array of strings without all of those quotation marks and commas. You can read more about it here.)
Another way to do the same thing would be to use Enumerable#include?, which returns true if the array contains the given item:
def count_vowels(string)
vowels = %w[ a e i o u ]
puts string.chars.count {|char| vowels.include?(char) }
end
...but as it turns out, String has an include? method, too, so we can do this instead:
def count_vowels(string)
puts string.chars.count {|char| "aeiou".include?(char) }
end
Not bad! But I've saved the best for last. Ruby has a great method called String#count:
def count_vowels(string)
puts string.count("aeiou")
end

How do I replace a for-loop in Ruby?

In Ruby it is bad style to use for-loops. This is commonly understood.
A style guide recommended to me:
(https://github.com/bbatsov/ruby-style-guide#source-code-layout)
says:
"Never use for, unless you know exactly why. Most of the time iterators should be used instead. for is implemented in terms of each (so you're adding a level of indirection), but with a twist - for doesn't introduce a new scope (unlike each) and variables defined in its block will be visible outside it."
The example given is:
arr = [1, 2, 3]
#bad
for elem in arr do
puts elem
end
# good
arr.each { |elem| puts elem }
I have researched and I can't find an explanation as to how to simulate a for loop that provides an iterating value I can pass to places or perform arithmetic on.
For example, with what would I replace:
for i in 0...size do
puts array1[i]
puts array2[size-1 - i]
puts i % 2
end
It's easy if it's one array, but I often need the current position for other purposes.
There's either a simple solution I'm missing, or situations where for is required. Additionally, I hear people talk about for as if it is never needed. What then is their solution to this?
Can it be improved? And what is the solution, if there is one? Thanks.
If you want to iterate over a collection and keep track of the index, use each_with_index:
fields = ["name", "age", "height"]
fields.each_with_index do |field,i|
puts "#{i}. #{field}" # 0. name, 1. age, 2. height
end
Your for i in 0...size example becomes:
array1.each_with_index do |item, i|
puts item
puts array2[size-1 - i]
puts i % 2
end
Don't forget you can do cool things like this too
fields = ["name", "age", "height"]
def output name, idx
puts "#{idx}. #{name}"
end
fields.each_with_index &method(:output)
Output
0. name
1. age
2. height
You can use this technique as a class or instance method too
class Printer
def self.output data
puts "raw: #{data}"
end
end
class Kanon < Printer
def initialize prefix
#prefix = prefix
end
def output data
puts "#{#prefix}: #{data}"
end
end
def print printer, data
# separating the block from `each` allows
# you to do interesting things
data.each &printer.method(:output)
end
example using class method
print Printer, ["a", "b", "c"]
# raw: a
# raw: b
# raw: c
example using instance method
kanon = Kanon.new "kanon prints pretty"
print kanon, ["a", "b", "c"]
# kanon prints pretty: a
# kanon prints pretty: b
# kanon prints pretty: c

Resources