Ruby array object find - ruby

I am trying out ruby by making a program i need. I have a custom class, and I need an array of objects of that class. This custom class has some attributes that change in the course of the program.
How can I find a specific object in my array, so I can access it and change it?
class Mathima
attr_accessor :id, :tmimata
def initialize(id)
#id = id
#tmimata = []
end
end
# main
mathimata = []
previd = id = ""
File.read("./leit/sortedinput0.txt").lines do |line|
array = line.split(' ') # i am reading a sorted file
id = array.delete_at(0) # i get the first two words as the id and tmima
tmima = array.delete_at(0)
if previd != id
mathimata.push(Mathima.new(id)) # if it's a new id, add it
end
# here is the part I have to go in mathimata array and add something in the tmimata array in an object.
previd = id
end

Use a Hash for mathimata as Greg pointed out:
mathimata = {}
File.read("./leit/sortedinput0.txt").lines do |line|
id, tmima, rest = line.split(' ', 3)
mathimata[id] ||= Mathima.new(id)
end

mathima = mathimata.find{|mathima| mathima.check() }
# update your object - mathima

Array.find() lets you search sequentially through an array, but that doesn't scale well.
I'd recommend that if you are dealing with a lot of objects or elements, and they're unique, then a Hash will be much better. Hashes allow indexed lookup based on their key.
Because of you are only keeping unique IDs either a Set or a Hash would be a good choice:
mathimata.push(Mathima.new(id)) # if it's a new id, add it
Set is between Array and a Hash. It only allows unique entries in the collection, so it's like an exclusive Array. It doesn't allow lookups/accesses by a key like a Hash.
Also, you can get your first two words in a more Ruby-like way:
array = line.split(' ') # i am reading a sorted file
id = array.delete_at(0) # i get the first two words as the id and tmima
tmima = array.delete_at(0)
would normally be written:
id, tmima = line.split(' ')[0, 2]
or:
id, tmima = line.split(' ')[0 .. 1]

Related

Split text while reading line by line

I am trying to read a text file with contents like this
ABC = Thefirststep
XYZ = Secondstep
ABC_XYZ = Finalstep=345ijk!r4+
I am able to read the file line by line using this
#!/usr/bin/ruby
text = '/tmp/data'
f = File.open(text , "r")
f.each_line { |line|
puts line
}
f.close
What I want to do is have the values TheFirststep Secondstep and Finalstep assigned to separate variables. better if we use split().
You could use something like this:
#!/usr/bin/ruby
text = '/tmp/data'
data = []
f = File.open(text , "r")
f.each_line { |line|
data.push( line.split("=").last)
}
f.close
You said you want to, "have the values, 'TheFirststep', 'Secondstep and 'Finalstep' assigned to separate variables.
You cannot create local variables dynamically (not since Ruby v1.8 anyway). That leaves two choices: assign those values to instance variables or use a different data structure, specifically, a hash.
First let's create a data file.
data <=-END
ABC = Thefirststep
XYZ = Secondstep
ABC_XYZ = Finalstep=345ijk!r4+
END
FName = 'test'
File.write(FName, data)
#=> 73
Assign values to instance variables
File.foreach(FName) do |line|
var, value, * = line.chomp.split(/\s*=\s*/)
instance_variable_set("##{var.downcase}", value)
end
#abc
#=> "Thefirststep"
#xyz
#=> "Secondstep"
#abc_xyz
#=> "Finalstep"
The convention for the names of instance variables (after the "#") is to use snake-case, which is why I downcased them.
Store the values in a hash
File.foreach(FName).with_object({}) do |line,h|
var, value, * = line.chomp.split(/\s*=\s*/)
h[var] = value
end
#=> {"ABC"=>"Thefirststep", "XYZ"=>"Secondstep", "ABC_XYZ"=>"Finalstep"}
As easy as this was to do, it's not generally helpful to generate instance variables dynamically or hashes with dynamically created keys. That's because they are only useful if their values can be obtained and possibly changed, which is problematic.
Note that in
var, value, * = line.chomp.split(/\s*=\s*/)
var equals the first element of the array returned by the split operation, value is the second value and * discards the remaining elements, if any.

Combining data parsed from within the same hash in Ruby

I'm trying to combine large data sets that I've filtered out from a single hash. I've tried various things such as merge, but don't seem to be able to get the data to combine the way I'm envisioning. Here are the things I'm trying to combine:
puts '','=========GET INFO'
print_data = targetprocess.comments_with_ids #get the hash
puts print_data #show the hash for verification
puts '','=========GET IDs'
story_ids = print_data['Comments']['Comment'].map {|entry| entry['General']} #filter for story ids and story name
puts story_ids
puts '','=========GET COMMENTS'
comment_description = print_data['Comments']['Comment'].map {|words| words['Description']} #get all comments, these are in the same order as the story ids
puts comment_description
Ultimately what I would like it to look like is:
story_id 1 + comment_description 1
story_id 2 + comment_description 2
etc.
Any help would be greatly appreciated.
I ended up realizing that the hash had some other nested structures I could use. In this example I use a nested hash, then store it as an array (I ultimately need this for other work) and then output.
puts '','=========GET INFO'
print_data = targetprocess.comments_with_ids #get the hash
puts print_data #show the hash for verification
puts '=========COMPLETE', ''
#=========HASH OF USEFUL DATA
results = {}
print_data['Comments']['Comment'].each{|entry|
results[entry['Id'].chomp] = {:parent_id => entry['General']['Id'].chomp, :description => entry['Description'].chomp}}
#=========STORE HASH AS AN ARRAY
csv_array = []
results.each{|key,value|
csv_array << [key, value[:parent_id], value[:description]]
#=======FRIENDLY OUTPUT
puts "Story_Id #{value[:parent_id]}, Comment_Id #{key}, Comment #{value[:description]}"}

How to re-order an array of objects based on a list of ids

I have an object Task as you can see below:
class Task
include HTTParty
attr_accessor :id, :name, :assignee_status, :order
def initialize(id, name, assignee_status)
self.id = id
self.name = name
self.status = status
self.order = order
end
end
So, when I load a list of tasks, I give them a specific order, like this:
i = 0
#tasks.each do |at|
at.order = i
i += 100
end
This list is, then, sent over json to a client app which shows is to the user and allows for drag'n'drop. After some task is re-ordered, the front-end app sends a list back to the server with all the task ids in the new order.
Example:
23,45,74,22,11,98,23
What I want to do is the re-order the array of objects based on the ids that I found. What I thought about doing was to set all order to 0 and then search each object individually on the matrix and set their priority accordingly. But this just feels wrong... feels to computing intensive.
Is there a clever way to do something like:
ArrayOfTaskObjects.orderbyAttribute(id) ??
Maybe the easy way is to iterate the params with a index, something like:
params['task_ids'].split(",").each_with_index do |task_id, idx|
task = lookup_the_task_based_on_id
task.order = idx
end
def sort_by_id(array)
r = array.inject({}) { |hash, element| hash.merge!(element.id => element) }
temp_array = r.sort_by {|k,_| k}
temp_array.flatten!.grep(temp_array.last.class)
end
What's happening:
The sort_by_id method takes in an array of objects - this being your Task array.
In the first line of code a hash is created which stores each Task id as the key and the Task object as the value.
The second line of code sorts the hash based on the keys (note that the r.sort_by method returns a two dimensional array, ex. [[23, Task], [44, Task], [54, Task]]).
And finally the third line of code flattens your two dimensional array into a one dimensional array and greps removes all id's from the two dimensional array leaving the array of Tasks in order.

How to "split and group" an array of objects based on one of their properties

Context and Code Examples
I have an Array with instances of a class called TimesheetEntry.
Here is the constructor for TimesheetEntry:
def initialize(parameters = {})
#date = parameters.fetch(:date)
#project_id = parameters.fetch(:project_id)
#article_id = parameters.fetch(:article_id)
#hours = parameters.fetch(:hours)
#comment = parameters.fetch(:comment)
end
I create an array of TimesheetEntry objects with data from a .csv file:
timesheet_entries = []
CSV.parse(source_file, csv_parse_options).each do |row|
timesheet_entries.push(TimesheetEntry.new(
:date => Date.parse(row['Date']),
:project_id => row['Project'].to_i,
:article_id => row['Article'].to_i,
:hours => row['Hours'].gsub(',', '.').to_f,
:comment => row['Comment'].to_s.empty? ? "N/A" : row['Comment']
))
end
I also have a Set of Hash containing two elements, created like this:
all_timesheets = Set.new []
timesheet_entries.each do |entry|
all_timesheets << { 'date' => entry.date, 'entries' => [] }
end
Now, I want to populate the Array inside of that Hash with TimesheetEntries.
Each Hash array must contain only TimesheetEntries of one specific date.
I have done that like this:
timesheet_entries.each do |entry|
all_timesheets.each do |timesheet|
if entry.date == timesheet['date']
timesheet['entries'].push entry
end
end
end
While this approach gets the job done, it's not very efficient (I'm fairly new to this).
Question
What would be a more efficient way of achieving the same end result? In essence, I want to "split" the Array of TimesheetEntry objects, "grouping" objects with the same date.
You can fix the performance problem by replacing the Set with a Hash, which is a dictionary-like data structure.
This means that your inner loop all_timesheets.each do |timesheet| ... if entry.date ... will simply be replaced by a more efficient hash lookup: all_timesheets[entry.date].
Also, there's no need to create the keys in advance and then populate the date groups. These can both be done in one go:
all_timesheets = {}
timesheet_entries.each do |entry|
all_timesheets[entry.date] ||= [] # create the key if it's not already there
all_timesheets[entry.date] << entry
end
A nice thing about hashes is that you can customize their behavior when a non-existing key is encountered. You can use the constructor that takes a block to specify what happens in this case. Let's tell our hash to automatically add new keys and initialize them with an empty array. This allows us to drop the all_timesheets[entry.date] ||= [] line from the above code:
all_timesheets = Hash.new { |hash, key| hash[key] = [] }
timesheet_entries.each do |entry|
all_timesheets[entry.date] << entry
end
There is, however, an even more concise way of achieving this grouping, using the Enumerable#group_by method:
all_timesheets = timesheet_entries.group_by { |e| e.date }
And, of course, there's a way to make this even more concise, using yet another trick:
all_timesheets = timesheet_entries.group_by(&:date)

common way to store single keys in ruby

Currently I have a collection of keys in my ruby code, which stored inside hash object. And when I add new element, I just check if that key already exist, if not then add new key to my collection with some default value, like this:
unless #issues.has_key?(issue_id)
#issues[issue_id] = '';
end
But I don't like this method. Is it possible to make that better, without using unnecessary value.
Use a Set.
Sets are a collection of unique objects (no repeats).
# #issues = Set.new
unless #issues.include?(issue_id)
#issues << issue_id
end
They keys of a hash are, in fact, a set (although not necessarily implemented via the Set class).
[Edit] Note that if you are storing complex objects (e.g. not builtins such as numbers, strings, symbols, etc.) you'll need to override both the hash method and the eql? method so that they can be hashed properly. The same goes if you are using complex objects as keys for hashing.
class Foo
attr_read :name, :hash
def initialize(name)
#name = name
#hash = name.hash
end
def eql?(o)
o.is_a?(Foo) && (o.name == self.name)
end
end
s = Set.new
s << Foo.new("Foo!")
s << Foo.new("Foo!")
s.to_a # => [ #<Foo:0x0123 #name="Foo!"> ]
You can use default value for hash
h = Hash.new("")
h[issue_id] => ""

Resources