I have the following:
arr = [["1/31/2012 8:00 PM"]]
Right now, to get that string I end up doing arr.first.first...which just seems awkward.
What's a more direct way to get 1/31/2012 8:00 PM?
It depends on your array purpose, you have several options:
arr[0][0] is is equal to arr.first.first. But I think arr.first.first is normal solution
arr.flatten.first
Consider other structure for arr with which you will be able to query for this data more naturally like meeting.nearest # => "1/31/2012 8:00 PM".
Your code seems the correct way to do it, but, you can also do
arr.to_s
=> "1/31/2012 8:00 PM"
But be cautious. This will concatenate the elements in the array into a single string, if more than one element is present in the array
[["this", "is"]].to_s
=> "thisis"
[["this", "is"], ["test"]].to_s
=> "thisistest"
Related
I have an array with hashes:
test = [
{"type"=>1337, "age"=>12, "name"=>"Eric Johnson"},
{"type"=>1338, "age"=>18, "name"=>"John Doe"},
{"type"=>1339, "age"=>22, "name"=>"Carl Adley"},
{"type"=>1340, "age"=>25, "name"=>"Anna Brent"}
]
I am interested in getting all the hashes where the name key equals to a value that can be found in an array:
get_hash_by_name = ["John Doe","Anna Brent"]
Which would end up in the following:
# test_sorted = would be:
# {"type"=>1338, "age"=>18, "name"=>"John Doe"}
# {"type"=>1340, "age"=>25, "name"=>"Anna Brent"}
I probably have to iterate with test.each somehow, but I still trying to get a grasp of Ruby. Happy for all help!
Here's something to meditate on:
Iterating over an array to find something is slow, even if it's a sorted array. Computer languages have various structures we can use to improve the speed of lookups, and in Ruby Hash is usually a good starting point. Where an Array is like reading from a sequential file, a Hash is like reading from a random-access file, we can jump right to the record we need.
Starting with your test array-of-hashes:
test = [
{'type'=>1337, 'age'=>12, 'name'=>'Eric Johnson'},
{'type'=>1338, 'age'=>18, 'name'=>'John Doe'},
{'type'=>1339, 'age'=>22, 'name'=>'Carl Adley'},
{'type'=>1340, 'age'=>25, 'name'=>'Anna Brent'},
{'type'=>1341, 'age'=>13, 'name'=>'Eric Johnson'},
]
Notice that I added an additional "Eric Johnson" record. I'll get to that later.
I'd create a hash that mapped the array of hashes to a regular hash where the key of each pair is a unique value. The 'type' key/value pair appears to fit that need well:
test_by_types = test.map { |h| [
h['type'], h]
}.to_h
# => {1337=>{"type"=>1337, "age"=>12, "name"=>"Eric Johnson"},
# 1338=>{"type"=>1338, "age"=>18, "name"=>"John Doe"},
# 1339=>{"type"=>1339, "age"=>22, "name"=>"Carl Adley"},
# 1340=>{"type"=>1340, "age"=>25, "name"=>"Anna Brent"},
# 1341=>{"type"=>1341, "age"=>13, "name"=>"Eric Johnson"}}
Now test_by_types is a hash using the type value to point to the original hash.
If I create a similar hash based on names, where each name, unique or not, points to the type values, I can do fast lookups:
test_by_names = test.each_with_object(
Hash.new { |h, k| h[k] = [] }
) { |e, h|
h[e['name']] << e['type']
}.to_h
# => {"Eric Johnson"=>[1337, 1341],
# "John Doe"=>[1338],
# "Carl Adley"=>[1339],
# "Anna Brent"=>[1340]}
Notice that "Eric Johnson" points to two records.
Now, here's how we look up things:
get_hash_by_name = ['John Doe', 'Anna Brent']
test_by_names.values_at(*get_hash_by_name).flatten
# => [1338, 1340]
In one quick lookup Ruby returned the matching types by looking up the names.
We can take that output and grab the original hashes:
test_by_types.values_at(*test_by_names.values_at(*get_hash_by_name).flatten)
# => [{"type"=>1338, "age"=>18, "name"=>"John Doe"},
# {"type"=>1340, "age"=>25, "name"=>"Anna Brent"}]
Because this is running against hashes, it's fast. The hashes can be BIG and it'll still run very fast.
Back to "Eric Johnson"...
When dealing with the names of people it's likely to get collisions of the names, which is why test_by_names allows multiple type values, so with one lookup all the matching records can be retrieved:
test_by_names.values_at('Eric Johnson').flatten
# => [1337, 1341]
test_by_types.values_at(*test_by_names.values_at('Eric Johnson').flatten)
# => [{"type"=>1337, "age"=>12, "name"=>"Eric Johnson"},
# {"type"=>1341, "age"=>13, "name"=>"Eric Johnson"}]
This will be a lot to chew on if you're new to Ruby, but the Ruby documentation covers it all, so dig through the Hash, Array and Enumerable class documentation.
Also, *, AKA "splat", explodes the array elements from the enclosing array into separate parameters suitable for passing into a method. I can't remember where that's documented.
If you're familiar with database design this will look very familiar, because it's similar to how we do database lookups.
The point of all of this is that it's really important to consider how you're going to store your data when you first ingest it into your program. Do it wrong and you'll jump through major hoops trying to do useful things with it. Do it right and the code and data will flow through very easily, and you'll be able to massage/extract/combine the data easily.
Said differently, Arrays are containers useful for holding things you want to access sequentially, such as jobs you want to print, sites you need to access in order, files you want to delete in a specific order, but they're lousy when you want to lookup and work with a record randomly.
Knowing which container is appropriate is important, and for this particular task, it appears that an array of hashes isn't appropriate, since there's no fast way of accessing specific ones.
And that's why I made my comment above asking what you were trying to accomplish in the first place. See "What is the XY problem?" and "XyProblem" for more about that particular question.
You can use select and include? so
test.select {|object| get_hash_by_name.include? object['name'] }
…should do the job.
I want to compare every object in lectures with each other and if some_condition is true, the second object has to be deleted:
toDelete=[]
lectures.combination(2).each do |first, second|
if (some_condition)
toDelete << second
end
end
toDelete.uniq!
lectures=lectures-toDelete
I got some weird errors while trying to delete inside the .each loop, so I came up with this approach.
Is there a more efficient way to do this?
EDIT after first comments:
I wanted to keep the source code free of unnecessary things, but now that you ask:
The elements of the lectures array are hashes containing data of different university lectures, like the name, room,the calendar weeks in which they are taught and begin and end time.
I parse the timetables of all student groups to get this data, but because some lectures are held in more than one student group and these sometimes differ in the weeks they are taught, I compare them with each other. If the compared ones only differ in certain values, I add the values from the second object to the first object and delete the second object. That's why.
The errors when deleting while in .each-loop: When using the Rails Hash.diff method, I got something like "Cannot convert Symbol to Integer". Turns out there was suddenly an Integer value of 16 in the array, although I tested before the loop that there are only hashes in the array...
Debugging is really hard if you have 9000 hashes.
EDIT:
Sample Data:
lectures = [ {:day=>0, :weeks=>[11, 12, 13, 14], :begin=>"07:30", :end=>"09:30", :rooms=>["Li201", "G221"], :name=>"TestSubject1", :kind=>"Vw", :lecturers=>["WALDM"], :tut_groups=>["11INM"]},
{:day=>0, :weeks=>[11, 12, 13, 14], :begin=>"07:30", :end=>"09:30", :rooms=>["Li201", "G221"], :name=>"TestSubject1", :kind=>"Vw", :lecturers=>["WALDM"], :tut_groups=>["11INM"]} ]
You mean something like this?
cleaned_lectures = lectures.combination(2).reject{|first, second| some_condition}
This might be a dumb question.
I know sample returns random number of elements from an array.
For example,
[1,2,3].sample.times do
Is there a way to return a fixed number of elements in a certain order always?
I dont know how to do this in ruby.
EDIT:
Lets say I always want to return penalty_name, severity and name only from the second and last array here always.:
offenses = PERSON_SUMMARY[:offenses].map do |offense|
offense[:penalties].map do |penalty|
penalty.merge(name: offense[:offense_name])
end
end.flatten
=> [{:penalty_name=>"Prison", :severity=>"Medium", :name=>"Speeding"}, {:penalty_name=>"Ticket", :severity=>"Low", :name=>"Speeding"}, {:penalty_name=>"Prison", :severity=>"Medium", :name=>"Shoplifting"}, {:penalty_name=>"Fine", :severity=>"Low", :name=>"Shoplifting"}]
right now I am doing:
offenses.each do |hash|
hash.sample
I think you want something like:
[1,2,3,4,5].sample(4).sort
It will take 4 random number from the array and order it...
edit - after your comment:
[5,4,3,2,1].values_at(1,-1).sort #second element(1) and last one(-1)
=>[1, 4]
You can specify which ones you want with values_at (negative numbers count from the back.)
ar=[{:penalty_name=>"Prison", :severity=>"Medium", :name=>"Speeding"}, {:penalty_name=>"Ticket", :severity=>"Low", :name=>"Speeding"}, {:penalty_name=>"Prison", :severity=>"Medium", :name=>"Shoplifting"}, {:penalty_name=>"Fine", :severity=>"Low", :name=>"Shoplifting"}]
p ar.values_at(1,-1).map{|h|h.values}
#=> [["Ticket", "Low", "Speeding"], ["Fine", "Low", "Shoplifting"]]
I need to loop through all of the days and months for the past couple decades numerically as well as to have the name of the month and day for each date. Obviously a few series of loops can accomplish this, but I wanted to know the most concise ruby-like way to accomplish this.
Essentially I'd need output like this for each day over the past X years:
3 January 2011 and 1/3/2011
What's the cleanest approach?
Dates can work as a range, so it's fairly easy to iterate over a range. The only real trick is how to output them as a formatted string, which can be found in the Date#strftime method, which is documented here.
from_date = Date.new(2011, 1, 1)
to_date = Date.new(2011, 1, 10)
(from_date..to_date).each { |d| puts d.strftime("%-d %B %Y and %-m/%-d/%Y") }
# => 1 January 2011 and 1/1/2011
# => 2 January 2011 and 1/2/2011
# => ...
# => 9 January 2011 and 1/9/2011
# => 10 January 2011 and 1/10/2011
(Note: I recall having some bad luck a ways back with unpadded percent formats like %-d in Windows, but if the above doesn't work and you want them unpadded in that environment you can remove the dash and employ your own workarounds.)
Given start_date & end_date:
(start_date..end_date).each do |date|
# do things with date
end
as David said, this is possible because of Date#succ. You can use Date#strftime to get the date in any format you'd like.
See if you can construct a Range where the min and max are Date objects, then call .each on the range. If the Date object supports the succ method this should work.
Given an array of DateTime Strings, I want to just fetch the times e.g. 10:30:00.
So far I come up with this, but it wont give me the right result:
["2011-07-30 10:00:00","2011-07-30 12:00:00"].each{|item| item.match(/\d{2}:\d{2}:\d{2}/)}
If all the strings really are like that then just do some substring mangling:
a = ["2011-07-30 10:00:00","2011-07-30 12:00:00"]
times = a.map { |e| e[11,8] }
This will also work if your timestamps include things like 2011-07-30 10:00:00.1123, 2011-07-30T10:00:00, or 2011-07-30 10:00:00 +0700.
If you wanted to be friendlier to the future, then you could do this:
off = '9999-99-99 '.length
len = '99:99:99'.length
times = a.map { |e| e[off, len] }
so no one would have to guess what the 11 and 8 were all about.
You could achieve the same using the excellent DateTime library from Ruby.
require 'date'
["2011-07-30 10:00:00","2011-07-30 12:00:00"].map{|item|
DateTime.parse(item).strftime("%H:%M:%S")
}
=> ["10:00:00", "12:00:00"]
Though, mu's answer is great.