ruby simple substitutions - ruby

very new to Ruby, I've got the following situation. I have a file with values separated by new lines, they look like this:
18917
18927
18929
...
I want to prepend a folder path to all of them, then grab the first 2 characters and prepend that as well, then the value in the file and then append a '.jpg' at the end so they would end up looking like this:
path/to/foler/18/18917.jpg
So I've code this ruby code:
folder = "/path/to/folder"
lines = File.readlines("values.csv")
images = lines.collect.to_s.gsub("\n", ".jpg,")
images.split(',').collect { |dogtag | puts "bad dog: #{folder}/#{dogtag[0,2]}/#{dogtag}" }
Now, this almost works, the part that is not working is the grabbing of the first 2 characters. I also tried it with the method outside quotes (and without the #{} of course) but it just produces an empty result.
Could someone please point out my folly?
Eventually I want to delete those images but I'm guessing that substituting 'File.delete' for 'puts' above would do the trick?
As usual, thanks in advance for taking the time to look at this.

You don't seem to be understanding what collect does.
I would rewrite your snippet like this:
File.read("values.csv").each do |line|
puts "bad dog: /path/to/folder/#{line[0,2]}/#{line.chomp}.jpg"
end
-- Update for last comment: --
If you don't want to use an if statement to check if a file exists before deleting it, you have two option (AFAIK).
Use rescue:
File.read("values.csv").each do |line|
begin
File.delete "/path/to/folder/#{line[0,2]}/#{line.chomp}.jpg"
rescue Errno::ENOENT
next
end
end
Or extend the File class with a delete_if_exists method. You can just put this at the top of your script:
class File
def self.delete_if_exists(f)
if exists?(f)
delete(f)
end
end
end
With this, you can do:
File.read("values.csv").each do |line|
File.delete_if_exists "/path/to/folder/#{line[0,2]}/#{line.chomp}.jpg"
end

Related

Recursive in Ruby - Return method in itself

I want to return the method in itself
def self.open_folder(file)
Dir.glob(file+"*") do |subfiles|
if File.directory?(subfiles)
open_folder(subfiles) ###Problem here
end
if File.file?(subfiles)
open_file(subfiles)
end
end
end
What I want is to return the "open_folder" to keep open the sub-folder. I got an error
block in open_folder': stack level too deep
Can you help me to find the solution for it?
If you just want to apply some method to every file in subdirectories, you could use :
Dir.glob("**/*").select{ |path| File.file?(path) }.each{ |file| open_file(file) }
This code works for me:
def open_file(file)
# Do your stuff here
puts file
end
def open_folder(file)
Dir.glob("#{file}/*") do |subfile|
File.directory?(subfile) ? open_folder(subfile) : open_file(subfile)
end
end
open_folder('path/to/directory')
NOTES:
You don't need to define the methods as self.* if you are running this code directly in irb or outside any class defined by you.
I used string interpolation (#{foo}) instead of concatenating the string.
Appending a '/*' to file path will look for all files and directories directly under the parent (not the nested subdirectories and files).
Instead of using 2 ifs, you can use elsif in this case as only 1 of the condition can be true in each iteration.

Outputting hash to text file

I am having trouble outputting the contents of my hash to a file. The program is one that manages a list of student records, including their StudentID, first name, last name, Major, and catalog year. Once the user is finished adding records, it is then added to the hash.
Everything in the program works perfectly, except when I try running the quit_program function, it doesn't save the contents in the file. Additionally, i am not getting any errors, any ideas?
could it potentially not be working because it is having trouble with converting the text in my hash, which is alphanumeric, into the text file?
def quit_program()
puts "Save Changes? y/n"
#changes = gets().chomp
if #changes=="y"
#fh=File.open(#file_name, 'w')
#this_string=""
#sDB.each do |key, store_account_data| #line 50
puts "#{key}: #{store_account_data.join(',')}"
end
end
#fh.puts(#this_string)
#fh.close()
end
You're not writing anything to the file. The string #this_string is empty. You should do
#sDB.each do |key, store_account_data|
#fh.puts "#{key}: #{store_account_data.join(',')}"
end
it doesn't save the contents in the file.
The following is NOT how you write to a file:
puts "#{key}: #{store_account_data.join(',')}"
That is how you write to your terminal/console window.
And this code:
#this_string=""
#fh.puts(#this_string)
writes a blank string to the file.
Here is how you write to a file:
class Student
def initialize(sDB, filename)
#sDB = sDB
#filename = filename
end
def save_changes()
puts "Save Changes? y/n"
user_answer = gets().chomp
if user_answer == "y"
File.open(#file_name, 'w') do |f|
#sDB.each do |key, store_account_data| #line 50
f.puts "#{key}: #{store_account_data.join(',')}"
end
end
end
end
could it potentially not be working because it is having trouble with
converting the text in my hash, which is alphanumeric, into the text
file?
No. Here is a concrete example you can try:
data = {
"John" => ['a', 123, 'b', 456],
"Sally" => ['c', 789, 'b', 0]
}
File.open('data.txt', 'w') do |f|
data.each do |name, data|
f.puts "#{name}: #{data.join(',')}"
end
end
$ ruby myprog.rb
$ cat data.txt
John: a,123,b,456
Sally: c,789,b,0
Also, ruby indenting is 2 spaces--not 0 spaces or 3 spaces, or anything else.
The answer is given in the error message: undefined local variable or method 'sDB'. (Which you have since removed from your question making the edited version next to impossible to answer.) Where and when is sDB defined in your program? You are evidently attempting to quit before initializing it.
In any case it is not a good thing to be accessing instance variables directly inside other methods. You should use accessor (getter and setter) methods instead. That would have probably prevented this situation from biting you in the first place.
def sdb
#sDB ||= Hash.new
end
def sdb=( key, value )
sdb
#sDB[ key ] = value
end
. . .
You are not properly writing to a file even if #sDB is defined. See Ruby - Printing a hash to txt file for an example.
Your question is missing essential input data, so there's no way to test our suggested changes.
Here's untested code I'd work from:
def quit_program
puts "Save Changes? y/n"
if gets.chomp.downcase == 'y'
File.write(
#file_name,
#s_db.map{ |k, v| "#{ k }: #{ v.join(',') }" }.join("\n")
)
end
end
Note:
#sDB isn't a proper variable name in Ruby. We use snake_case, not camelCase for variables and method names. ItsAMatterOfReadability. Follow the convention or suffer the wrath of your team members the first time you have a code review.
Don't add empty parenthesis to method names (quit_program()) or calls (gets()) unless it's essential to tell the difference between a variable and a method invocation. You should also never name a variable the same as a method because it'll confuse everyone working on the code, so that should never be a consideration.
Don't create a variable (#changes) you use once and throw away, unless what you're doing is so complex you need to break down the operation into smaller chunks. And, if you're doing that, it'd be a really good candidate for refactoring into separate methods, so again, just don't.
When comparing user-input to something you expect, fold the case of their input to match what you expect. (gets.chomp.downcase == 'y'). It really irritates users to enter "y" and fail because you insisted on "Y".
While you can use File.open to create or write to a file, there's less visual noise to use File.write. open is great when you need to use various options for the mode but for plain text write is sufficient.
The whole block used for writing looks like it can be cleaned up to a single map and join, which coerces the data into an array of strings then into a single string.

Puts arrays in file using ruby

This is a part of my file:
project(':facebook-android-sdk-3-6-0').projectDir = new File('facebook-android-sdk-3-6-0/facebook-android-sdk-3.6.0/facebook')
project(':Forecast-master').projectDir = new File('forecast-master/Forecast-master/Forecast')
project(':headerListView').projectDir = new File('headerlistview/headerListView')
project(':library-sliding-menu').projectDir = new File('library-sliding-menu/library-sliding-menu')
I need to extract the names of the libs. This is my ruby function:
def GetArray
out_file = File.new("./out.txt", "w")
File.foreach("./file.txt") do |line|
l=line.scan(/project\(\'\:(.*)\'\).projectDir/)
File.open(out_file, "w") do |f|
l.each do |ch|
f.write("#{ch}\n")
end
end
puts "#{l} "
end
end
My function returns this:
[]
[["CoverFlowLibrary"]]
[["Android-RSS-Reader-Library-master"]]
[["library"]]
[["facebook-android-sdk-3-6-0"]]
[["Forecast-master"]]
My problem is that I find nothing in out_file. How can I write to a file? Otherwise, I only need to get the name of the libs in the file.
Meditate on this:
"project(':facebook-android-sdk-3-6-0').projectDir'".scan(/project\(\'\:(.*)\'\).projectDir/)
# => [["facebook-android-sdk-3-6-0"]]
When scan sees the capturing (...), it will create a sub-array. That's not what you want. The knee-jerk reaction is to flatten the resulting array of arrays but that's really just a band-aid on the code because you chose the wrong method.
Instead consider this:
"project(':facebook-android-sdk-3-6-0').projectDir'"[/':([^']+)'/, 1]
# => "facebook-android-sdk-3-6-0"
This is using String's [] method to apply a regular expression with a capture and return that captured text. No sub-arrays are created.
scan is powerful and definitely has its place, but not for this sort of "find one thing" parsing.
Regarding your code, I'd do something like this untested code:
def get_array
File.new('./out.txt', 'w') do |out_file|
File.foreach('./file.txt') do |line|
l = line[/':([^']+)'/, 1]
out_file.puts l
puts l
end
end
end
Methods in Ruby are NOT camelCase, they're snake_case. Constants, like classes, start with a capital letter and are CamelCase. Don't go all Java on us, especially if you want to write code for a living. So GetArray should be get_array. Also, don't start methods with "get_", and don't call it array; Use to_a to be idiomatic.
When building a regular expression start simple and do your best to keep it simple. It's a maintainability thing and helps to reduce insanity. /':([^']+)'/ is a lot easier to read and understand, and accomplishes the same as your much-too-complex pattern. Regular expression engines are greedy and lazy and want to do as little work as possible, which is sometimes totally evil, but once you understand what they're doing it's possible to write very small/succinct patterns to accomplish big things.
Breaking it down, it basically says "find the first ': then start capturing text until the next ', which is what you're looking for. project( can be ignored as can ).projectDir.
And actually,
/':([^']+)'/
could really be written
/:([^']+)'/
but I felt generous and looked for the leading ' too.
The problem is that you're opening the file twice: once in:
out_file = File.new("./out.txt", "w")
and then once for each line:
File.open(out_file, "w") do |f| ...
Try this instead:
def GetArray
File.open("./out.txt", "w") do |f|
File.foreach("./file.txt") do |line|
l=line.scan(/project\(\'\:(.*)\'\).projectDir/)
l.each do |ch|
f.write("#{ch}\n")
end # l.each
end # File.foreach
end # File.open
end # def GetArray

Performing same action for each value in array in Ruby

I have a text file of stock symbols, each symbol is on its own line. In Ruby, I have created an array from the text file like so:
symbols = []
File.read('symbols.txt').each_line do |line|
symbols << line.chop!
end
For each symbol in the array, I want to read from a json file (ex. MSFT.json) and perform a number of calculations (all of that is now working) and then do the same thing for the next symbol in the array.
When attempting to "call" and perform calculations on the first item in the array I did this:
json = File.read("#{symbols[0]}.json")
#...calculations come after this
This worked fine, and it did run through the whole program for the first symbol, but of course doesn't go on to perform the same steps for the remaining symbols (I know thats because I specified an index in the array].
Now that I know that the program works for a single symbol, I now want it to run on all the symbols in the array...so after the first block, I tried adding: symbols.each do, and removed the [0] from the File.read line (and added end at the end of the calculations). I was hoping it would loop through everything between the "do" and "end" for each symbol. That didn't work.
Then I tried adding this after the first block:
def page(symbols, i)
page[i]
end
And changing the File.read line to: json = File.read("#{page[i]}.json)
But that didn't work either.
Any help is appreciated. Thanks a lot
You can simply use .each instead of an iterator index:
symbols.each do |symbol|
json = File.read("#{symbol}.json")
# do some calculation for symbol
end
No need to iterate twice:
open('symbols.txt').lines.each do |line|
symbol = line.strip
json = File.read("#{symbol}.json")
# process json
end

Triple-loop Ruby

I know the idea of a triple loop brings fear to the minds of some, but I have a code with the following structure:
paragraph.split(/(\.|\?|\!)[\s\Z]/).each do |sentence|
myArrayOfFiles.each_with_index { |ma,j|
ma.each_with_index { |word,i|
sentence.gsub!(...)
}
}
end
The two outer loops run as expected, but for some reason, the inner loop runs over the first sentence only. Do you know why this is? How can I make the inner loop run over all sentences too?
I am running on Ruby 1.8.7, and have tried the same code above using just the each loop and got the same results. Any ideas?
EDIT:
myArrayOfFiles is an array filled by:
AFile = File.open("A.txt")
BFile = File.open("B.txt")
myArrayOfFiles << [Afile,BFile]
myArrayOfFiles.flatten!
Your problem is that myArrayOfFiles contains File instances. When you iterate through one of your Files with ma.each_with_index, it will go through the file line by line and stop at EOF. Then, you try to iterate again with the next sentence but the File is already at EOF so ma.each_with_index has nothing to iterate over and nothing interesting happens. You need to call rewind to move the Files back to the beginning before you try to each_with_index them again:
paragraph.split(/(\.|\?|\!)[\s\Z]/).each do |sentence|
myArrayOfFiles.each_with_index do |ma, j|
ma.rewind # <------------------------- You need this
ma.each_with_index do |word, i|
sentence.gsub!(...)
end
end
end

Resources