how to solve this equation in ruby - ruby

path1="c:/kabab.txt"
path2="c:/kabab2.txt"
for v in 1..2
puts "#{path}"#{v}"
end
I would like to create a file, but I'm not able to do that.

In Ruby you can't retrieve the value of a local variable from its name using the approach you've tried. There is instance_variable_get for instance variables but there isn't an equivalent for local variables as far as I know.
"path#{v}" is a string containing the name of your variable so if you evaluate that using eval the result from the eval will be the value of the variable. Therefore you could do something like:
filename = eval("path#{v}")
open(filename, 'w')
but you always need to be careful when using eval because of potential security issues.
Instead, I would put the list of files in an array
paths = ["c:/kabab.txt", "c:/kabab2.txt"]
and do:
paths.each do |path|
f = open(path, 'w')
# use file here
end
or if all the files share a common prefix and extension then something like:
prefix = "c:/kabab"
extension = ".txt"
for v in 1..2
filename = "#{prefix}#{v}#{extension}"
# use filename here
end

Related

Defining a method to update ruby object property

Hey I am having trouble with the following question to be solved using ruby.
Question
Write a function that provides change directory (cd) function for an abstract file system.
Notes:
Root path is '/'.
Path separator is '/'.
Parent directory is addressable as '..'.
Directory names consist only of English alphabet letters (A-Z and a-z).
For example:
path = Path.new('/a/b/c/d')
puts path.cd('../x').current_path
should display '/a/b/c/x'.
Note: Do not use built-in path-related functions.
My Answer
class Path
def initialize(path)
#current_path = path
end
def current_path
#current_path
end
def cd(new_path)
if new_path.include? ".."
z = new_path.split("/")
b = #current_path
a = b.split('/')
a.shift
a.pop
#current_path = a.push(z[z.length-1]).join("/")
else
end
end
end
path = Path.new('/a/b/c/d')
path = path.cd('../x')
However this returns a string instead of an object from the 'path' variable.
You need to create a chain method. There are 2 ways to address it.
The immutable one - just create new instance of the class instead of modifying, e.g. return Path.new(calculated_path)
The mutable one - modify #current_path and return self in the end of the method #cd
After you've changed #current_path in the object, just return the object ('self')
#current_path = a.push(z[z.length-1]).join("/")
return self

Initialize array with filename and unique parent directories, eliminating common directories

After initializing an array of file names from a base directory like so:
mydir = Dir[Dir.pwd + '/**/*.{txt,bat}']
I'm sorting the array in various ways such as:
mydir.sort_by! {|file| File.size(file)}.reverse!
The array names are comprised of files with a full pathname, with subdirectories ranging from 1 to 3 levels deep. Is there a way to initialize the array to eliminate the drive and common directories leaving only filenames (with extension) and their unique parent directories? In this case, I want to eliminate the following from every array name: C:\Downloads\My Files\ to achieve this. Note the space in "My Files".
If you know what the common directory is, you can initialize the array with a code like this:
mydir = Dir.chdir(Dir.pwd) { Dir['**/*.{txt,bat}'] }
So with your example:
mydir = Dir.chdir('C:\Downloads\My Files') { Dir['**/*.{txt,bat}'] }
Note that Dir.chdir(Dir.pwd) { Dir['**/*.{txt,bat}'] } is equivalent to Dir['**/*.{txt,bat}'].
The Pathname class has the relative_path_from method that might just fit your needs.
To use this class you need to require it:
require 'pathname'
You need to create instances of the Pathname class to use the method. You can do it like this:
mydir.map{|p| Pathname.new(p)}
You can then get the relative path from your current dir:
mydir.map{|p| Pathname.new(p).relative_path_from(Pathname.pwd)}
You said you just wanted the file names. To achieve this you can reject the directories:
mydir.map{|p| Pathname.new(p).relative_path_from(Pathname.pwd)}.reject(&:directory?)
Finally, you can sort by size the same way you did. Pathname does give you a shorter syntax though:
mydir.map{|p| Pathname.new(p).relative_path_from(Pathname.pwd)}.reject(&:directory?).sort_by(&:size).reverse
This will give you an array of Pathname objects, you can only get the strings with the to_s method:
mydir.map{|p| Pathname.new(p).relative_path_from(Pathname.pwd)}.reject(&:directory?).sort_by(&:size).reverse.map(&:to_s)
Also, as a size note, the &:method syntax is a shorthand for {|x| x.method}.
The following method detects the common prefix from the strings in an array:
class Array
def common_prefix
first, *others = self
i = 0
loop{break unless first[i] and others.all?{|s| first[i] == s[i]}; i += 1}
first[0, i]
end
end
Using this, you can do
common_prefix = mydir.common_prefix
mydir_without_prefix = mydir.map{|s| s.sub(common_prefix, "")}
You can use the File::basename.
mydir = Dir[Dir.pwd + '/**/*.{txt,bat}']
mydir.sort_by! { |file| File.size(file) }.reverse!
files = mydir.map { |file| File.basename(file) }

string compare in Ruby not working

I'm not sure what is going on here. I need to run a string compare on two variables that are Times. One variable is a Time object using the .mtime function. The other variable is taken from a sqlite3 database. I would like to compare these times to see if the modification date is different from the last modification date that is listed in the sqlite3 table. here is the code for that part.
When I print out the values they look identical...So why is the compare not working
def scanfile
dir = Dir.new(Dir.pwd)
dir.each do |file|
fileName = File.basename(file)
modTime = File.mtime(file).strftime("%F %T")
lastMod = nil
exists = checkDB(fileName)
if exists == true
$db.execute("SELECT DateMod FROM Files WHERE fileName = '#{fileName}'") do |mod|
lastMod = mod
end
mod = modTime.to_s
printf("modTime: #{mod} lastMod: #{lastMod}\n")
if mod != lastMod
$db.execute("UPDATE Files SET NumMods=NumMods+1 WHERE fileName = '#{fileName}'")
$db.execute("UPDATE Files SET DateMod='#{modTime}' WHERE fileName = '#{fileName}'")
print "#{fileName} updated...\n"
end
else
if fileName != "." && fileName != ".."
inputRecord(fileName, modTime, modTime, 1)
print "#{fileName} inserted...\n"
end
end
end
end
When you use execute (or this version), you'll be working with the result set's rows as arrays of strings, not simple strings. So in here:
$db.execute(...) do |mod|
#...
end
your mod will be an array which contains a single string. The problem is that you're saving that array and treating it like a string; with sufficient to_s calls and similar mangling, you'll get a string that looks right to both you and Ruby and everything will work.
You should unpack the row array yourself:
$db.execute(...) do |mod|
lastMod = mod.first
# ------------^^^^^
end
Well I figured it out. I am not sure why this fixed it because I thought I was basically doing this but using:
if !modTime.to_s.eql? lastMod.to_s
worked out well..

loop, array and file problem in ruby

I'm currently learning ruby and here what I'm trying to do:
A script which open a file, make a subsitution, then comparing every lines to each other to see if it exist many times.
So, I tried to work directly with the string, but I didn't find how to do it, so I put every line in an array, and comparing every row.
But I got a first problem.
Here is my code:
#!/usr/bin/env ruby
DOC = "test.txt"
FIND = /,,^M/
SEP = "\n"
#make substitution
puts File.read(DOC).gsub(FIND, SEP)
#open the file and put every line in an array
openFile = File.open(DOC, "r+")
fileArray = openFile.each { |line| line.split(SEP) }
#print fileArray #--> give the name of the object
#Cross the array to compare every items to every others
fileArray.each do |items|
items.chomp
fileArray.each do |items2|
items2.chomp
#Delete if the item already exist
if items = items2
fileArray.delete(items2)
end
end
end
#Save the result in a new file
File.open("test2.txt", "w") do |f|
f.puts fileArray
end
At the end, I only have the name of the array object "fileArray". I print the object after the split, and i've got the same, so I guess the problem is from here. Little help required (if you know how to do this without array, just with the line in the file, answer appreciate too).
Thanks !
EDIT:
So, here's my code now
#!/usr/bin/env ruby
DOC = "test.txt"
FIND = /,,^M/
SEP = "\n"
#make substitution
File.read(DOC).gsub(FIND, SEP)
unique_lines = File.readlines(DOC).uniq
#Save the result in a new file
File.open('test2.txt', 'w') { |f| f.puts(unique_lines) }
Can't figure out how to chomp this.
Deleting duplicate lines in a file:
no_duplicate_lines = File.readlines("filename").uniq
No need to write so much code :)
Modify your code like this:
f.puts fileArray.join("\n")
Alternate way:
unique_lines = File.readlines("filename").uniq
# puts(unique_lines.join("\n")) # Uncomment this line and see if the variable holds the result you want...
File.open('filename', 'w') {|f| f.puts(unique_lines.join("\n"))}
Just a couple of points about the original code:
fileArray = openFile.each { |line| line.split(SEP) }
sets fileArray to a File object, which I suspect wasn't your intention. File#each (the # notation is Ruby convention to describe a particular method on an object of the supplied class) executes your supplied block for each line (it's also available with a synonym: each_line), where a line is defined by default as your OS's end-line character(s).
If you were looking to build an array of lines, then you could just have written
fileArray = openFile.readlines
and if you wanted those lines to be chomped (often a good idea) then that could be achieved by something like
fileArray = openFile.readlines.collect { |line| line.chomp }
or even (since File mixes in Enumerable)
fileArray = openFile.collect { |line| line.chomp }
And one other tiny thing: Ruby tests for equality with ==, = is only for assignment, so
if items = items2
will set items to items2 (and will always evaluate as true)

How do you assign new variable names when its already assigned to something? Ruby

The title really really doesn't explain things. My situation is that I would like to read a file and put the contents into a hash. Now, I want to make it clever, I want to create a loop that opens every file in a directory and put it into a hash. Problem is I don't know how to assign a name relative to the file name. eg:
hash={}
Dir.glob(path + "*") do |datafile|
file = File.open(datafile)
file.each do |line|
key, value = line.chomp("\t")
# Problem here is that I wish to have a different
# hash name for every file I loop through
hash[key]=value
end
file.close
end
Is this possible?
Why don't you use a hash whose keys are the file names (in your case "datafile") and whose value are hashes in which you insert your data?
hash = Hash.new { |h, key| h[key] = Hash.new }
Dir.glob(path + '*') do |datafile|
next unless File.stat(datafile).file?
File.open(datafile) do |file|
file.each do |line|
key, value = line.split("\t")
puts key, value
# Different hash name for every file is now hash[datafile]
hash[datafile][key]=value
end
end
end
You want to dynamically create variables with the names of the files you process?
try this:
Dir.glob(path + "*") do |fileName|
File.open(fileName) {
# the variable `hash` and a variable named fileName will be
# pointing to the same object...
hash = eval("#{fileName} = Hash.new")
file.each do |line|
key, value = line.chomp("\t")
hash[key]=value
end
}
end
Of course you would have to make sure you rubify the filename first. A variable named "bla.txt" wouldn't be valid in ruby, neither would "path/to/bla.csv"
If you want to create a dynamic variable, you can also use #instance_variable_set (assuming that instance variables are also OK.
Dir.glob(path + "*") do |datafile|
file = File.open(datafile)
hash = {}
file.each do |line|
key, value = line.chomp("\t")
hash[key] = value
end
instance_variable_set("#file_#{File.basename(datafile)}", hash)
end
This only works when the filename is a valid Ruby variable name. Otherwise you would need some transformation.
Can't you just do the following?
filehash = {} # after the File.open line
...
# instead of hash[key] = value, next two lines
hash[datafile] = filehash
filehash[key] = value
You may want to use something like this:
hash[file] = {}
hash[file][key] = value
Two hashes is enough now.
fileHash -> lineHash -> content.

Resources