How sort this hash of hashes - ruby

I am struggling to sort this hash listed below:
{
17=>{:id=>17, :count=>1, :created_at=>Wed, 05 Sep 2012 19:02:34 UTC +00:00},
14=>{:id=>14, :count=>2, :created_at=>Sun, 02 Sep 2012 19:20:28 UTC +00:00},
9=>{:id=>9, :count=>0, :created_at=>Sun, 02 Sep 2012 17:09:35 UTC +00:00},
10=>{:id=>10, :count=>2, :created_at=>Sat, 01 Sep 2012 17:09:56 UTC +00:00},
11=>{:id=>11, :count=>0, :created_at=>Fri, 31 Aug 2012 19:13:57 UTC +00:00},
12=>{:id=>12, :count=>2, :created_at=>Thu, 30 Aug 2012 19:19:32 UTC +00:00},
13=>{:id=>13, :count=>0, :created_at=>Thu, 23 Aug 2012 19:20:09 UTC +00:00}
}
The above hash should be sorted on count and created_of and look like this hash:
{
12=>{:id=>12, :count=>2, :created_at=>Thu, 30 Aug 2012 19:19:32 UTC +00:00},
10=>{:id=>10, :count=>2, :created_at=>Sat, 01 Sep 2012 17:09:56 UTC +00:00},
14=>{:id=>14, :count=>2, :created_at=>Sun, 02 Sep 2012 19:20:28 UTC +00:00},
17=>{:id=>17, :count=>1, :created_at=>Wed, 05 Sep 2012 19:02:34 UTC +00:00},
13=>{:id=>13, :count=>0, :created_at=>Thu, 23 Aug 2012 19:20:09 UTC +00:00},
11=>{:id=>11, :count=>0, :created_at=>Fri, 31 Aug 2012 19:13:57 UTC +00:00},
9=>{:id=>9, :count=>0, :created_at=>Sun, 02 Sep 2012 17:09:35 UTC +00:00}
}

Assuming you are using Ruby 1.9 where hashes have order:
Hash[data.sort_by { |key, h| [-h[:count], h[:created_at]] }]

In Ruby 1.8, you cannot sort a hash as a hash has no order. In Ruby 1.9, a hash iteration order is defined by the insertion order. You therefore have to create a new hash in which you will insert the elements in the right order.
sorted_keys = hash.keys.sort
sorted_hash = Hash.new
sorted_keys.each do |k|
sorted_hash[k] = hash[k]
end

Related

RFC822 Date format in Golang

Why Golang time format (RFC-822 in this case) is different from the one described in the RFC?
In time package RFC822 const value is defined as:
RFC822 = "02 Jan 06 15:04 MST"
11 Nov 09 00:00 CET
But regarding to RFC-822 or w3.org Validator date format should be rather as RFC1123:
RFC1123 = "Mon, 02 Jan 2006 15:04:05 MST"
Wed, 11 Nov 2009 00:00:00 CET
or even better:
RFC1123Z = "Mon, 02 Jan 2006 15:04:05 -0700"
Wed, 11 Nov 2009 00:00:00 +0100
Because the Central European Time Zone (CET) is not correct in this RFC.
Why such a difference?
I'm writing an RSS server and it took me a while to figure out why I'm getting badly formatted results.

Excluding root from prt_jobs

I'm making a program that will run through a printer server and cancel jobs that are hung. As of right now it outputs everything, and what I want to do is exclude root:
Output:
credjet-898837 cdd 5312512 Wed 14 Oct 2015 03:42:32 PM CDT
credjet-898839 cdd 1998848 Wed 14 Oct 2015 03:45:32 PM CDT
credjet-940485 cdd 1206272 Mon 04 Jan 2016 01:10:30 PM CST
credjet-940499 cdd 342016 Mon 04 Jan 2016 01:21:42 PM CST
credjet-940505 cdd 342016 Mon 04 Jan 2016 01:29:26 PM CST
credjet-940509 cdd 342016 Mon 04 Jan 2016 01:38:24 PM CST
credjet-940514 cdd 342016 Mon 04 Jan 2016 02:00:02 PM CST
credjet-940515 cdd 2387968 Mon 04 Jan 2016 02:00:17 PM CST
credjet-940525 cdd 2387968 Mon 04 Jan 2016 02:10:46 PM CST
credjet-940526 cdd 2387968 Mon 04 Jan 2016 02:11:01 PM CST
credjet-940528 cdd 2387968 Mon 04 Jan 2016 02:12:44 PM CST
credjet-940602 cdd 2382848 Mon 04 Jan 2016 02:26:09 PM CST
devljet-931153 siv 1798144 Fri 18 Dec 2015 02:38:30 PM CST
devljet-931157 siv 3278848 Fri 18 Dec 2015 02:47:18 PM CST
devljet-931158 siv 1538048 Fri 18 Dec 2015 02:47:18 PM CST
laser11-917719 root 78848 Wed 18 Nov 2015 09:56:47 PM CST
laser11-918257 root 78848 Thu 19 Nov 2015 09:45:23 PM CST
laser11-918262 root 79872 Thu 19 Nov 2015 09:49:30 PM CST
laser11-918263 root 78848 Thu 19 Nov 2015 09:53:45 PM CST
Expected Output:
credjet-898837 cdd 5312512 Wed 14 Oct 2015 03:42:32 PM CDT
credjet-898839 cdd 1998848 Wed 14 Oct 2015 03:45:32 PM CDT
credjet-940485 cdd 1206272 Mon 04 Jan 2016 01:10:30 PM CST
credjet-940499 cdd 342016 Mon 04 Jan 2016 01:21:42 PM CST
credjet-940505 cdd 342016 Mon 04 Jan 2016 01:29:26 PM CST
credjet-940509 cdd 342016 Mon 04 Jan 2016 01:38:24 PM CST
credjet-940514 cdd 342016 Mon 04 Jan 2016 02:00:02 PM CST
credjet-940515 cdd 2387968 Mon 04 Jan 2016 02:00:17 PM CST
credjet-940525 cdd 2387968 Mon 04 Jan 2016 02:10:46 PM CST
credjet-940526 cdd 2387968 Mon 04 Jan 2016 02:11:01 PM CST
credjet-940528 cdd 2387968 Mon 04 Jan 2016 02:12:44 PM CST
credjet-940602 cdd 2382848 Mon 04 Jan 2016 02:26:09 PM CST
devljet-931153 siv 1798144 Fri 18 Dec 2015 02:38:30 PM CST
devljet-931157 siv 3278848 Fri 18 Dec 2015 02:47:18 PM CST
devljet-931158 siv 1538048 Fri 18 Dec 2015 02:47:18 PM CST
#<= No more root
Is there a way that I can output the same information, but exclude the root jobs?
Source:
#!/local/usr/bin/ruby
require 'rubygems'
require 'net/ssh'
require 'etc'
class PrintJobs
HOST = '<server here>' #<= Left blank for security
USERNAME = Etc.getlogin
PASSWORD = nil
def scan_for_jobs
check_jobs = Net::SSH.start(HOST, USERNAME, :password => PASSWORD) do |ssh|
cmd = "prt_jobs"
res = ssh.exec!(cmd)
puts res
end
end
end
test = PrintJobs.new
test.scan_for_jobs
You can get rid of unwanted lines by using below code:
res = ssh.exec!(cmd)
res = res.split("\n").reject {|line| line.match(/\s+root\s+/)}.join("\n")
puts res
The result of exec! is one string containing the output of command issued over ssh. Hence, we need to split it by newline and iterate over the array to reject the lines containing root and re-join the array using new line.

Listing missing months in an array of dates

I have a list of transactions in an array.
=> [Wed, 23 Oct 2013, Mon, 18 Nov 2013, Fri, 22 Nov 2013, Mon, 13 Jan 2014, Tue, 28 Jan 2014, Mon, 03 Feb 2014, Mon, 10 Feb 2014, Tue, 18 Feb 2014, Fri, 07 Mar 2014, Mon, 31 Mar 2014, Mon, 07 Apr 2014, Tue, 10 Jun 2014, Mon, 30 Jun 2014, Mon, 22 Sep 2014, Mon, 06 Oct 2014, Fri, 14 Nov 2014, Tue, 18 Nov 2014, Fri, 26 Dec 2014, Thu, 15 Jan 2015, Mon, 23 Mar 2015, Mon, 20 Apr 2015]
I need to compare the dates of each transaction and list any months that are missing in the list of months and year. Here is what I have now...
#find_transactions = (#user.transactions.find_all { |t| (t.name 'name' })
#trans_dates = #find_transactions.map(&:date).sort!.map { |s| Date.strptime(s, '%Y-%m') }.each_cons(2).map{ |d1,d2| d1.next_month == d2 }
This method currently gives me a true or false if each month is there but I need to actually have the method print a list of months that are missing. I would like to have it print the month and year together.
Here is the response this method gives me...
=> [true, false, true, false, false, true, false, true, false, false, false, true, true]
I want a response like this...
=> [March 2015, December 2014, September 2014]
Thanks in advance!
Edit: For array already being composed of date objects you can do:
require 'date'
dates = [Wed, 23 Oct 2013, Mon, 18 Nov 2013, Fri, 22 Nov 2013, Mon, 13 Jan 2014, Tue, 28 Jan 2014, Mon, 03 Feb 2014, Mon, 10 Feb 2014, Tue, 18 Feb 2014, Fri, 07 Mar 2014, Mon, 31 Mar 2014, Mon, 07 Apr 2014, Tue, 10 Jun 2014, Mon, 30 Jun 2014, Mon, 22 Sep 2014, Mon, 06 Oct 2014, Fri, 14 Nov 2014, Tue, 18 Nov 2014, Fri, 26 Dec 2014, Thu, 15 Jan 2015, Mon, 23 Mar 2015, Mon, 20 Apr 2015]
all_dates = []
dates.first.upto(dates.last) {|x| all_dates << x.strftime('%b %Y') if x.day == 1 || x == dates.first}
d = dates.map {|x| x.strftime('%b %Y')}.uniq
p (all_dates - d)
#=> ["Dec 2013", "May 2014", "Jul 2014", "Aug 2014", "Feb 2015"]
Edit: Below methods are for an array of date strings
You can try this:
require 'date'
dates = ["Wed, 23 Oct 2013", "Mon, 18 Nov 2013", "Fri, 22 Nov 2013", "Mon, 13 Jan 2014", "Tue, 28 Jan 2014", "Mon, 03 Feb 2014", "Mon, 10 Feb 2014", "Tue, 18 Feb 2014", "Fri, 07 Mar 2014", "Mon, 31 Mar 2014", "Mon, 07 Apr 2014", "Tue, 10 Jun 2014", "Mon, 30 Jun 2014", "Mon, 22 Sep 2014", "Mon, 06 Oct 2014", "Fri, 14 Nov 2014", "Tue, 18 Nov 2014", "Fri, 26 Dec 2014", "Thu, 15 Jan 2015", "Mon, 23 Mar 2015", "Mon, 20 Apr 2015"]
all_dates = []
d = dates.map {|x| Date.parse(x[8..-1])}.uniq
counter = d.first
until counter == d.last
all_dates << counter
counter = counter.next_month
end
p (all_dates - d).map {|x| x.strftime('%b %Y')}
#=> ["Dec 2013", "May 2014", "Jul 2014", "Aug 2014", "Feb 2015"]
Another (more concise) way would be:
require 'date'
dates = ["Wed, 23 Oct 2013", "Mon, 18 Nov 2013", "Fri, 22 Nov 2013", "Mon, 13 Jan 2014", "Tue, 28 Jan 2014", "Mon, 03 Feb 2014", "Mon, 10 Feb 2014", "Tue, 18 Feb 2014", "Fri, 07 Mar 2014", "Mon, 31 Mar 2014", "Mon, 07 Apr 2014", "Tue, 10 Jun 2014", "Mon, 30 Jun 2014", "Mon, 22 Sep 2014", "Mon, 06 Oct 2014", "Fri, 14 Nov 2014", "Tue, 18 Nov 2014", "Fri, 26 Dec 2014", "Thu, 15 Jan 2015", "Mon, 23 Mar 2015", "Mon, 20 Apr 2015"]
all_dates = []
d = dates.map {|x| Date.parse(x[8..-1])}.uniq
d.first.upto(d.last) {|x| all_dates << x if x.day == 1}
p (all_dates - d).map {|x| x.strftime('%b %Y')}
#=> ["Dec 2013", "May 2014", "Jul 2014", "Aug 2014", "Feb 2015"]
This is one way you could do that.
Code
require 'date'
def missing_months(dates)
a = dates.map { |s| d = Date.strptime(s, '%a, %d %b %Y'); d - d.day + 1 }
(all_months_in_range(*a.minmax) -a).map { |d| d.strftime('%b %Y') }
end
def all_months_in_range(f,l)
(12*(l.year-f.year)+l.month-f.month+1).times.map do |i|
y,m = (f.month+i).divmod(12)
y += f.year
(m=12; y-=1) if m ==0
Date.new(y,m)
end
end
Example
dates = ['Wed, 23 Oct 2013', 'Mon, 18 Nov 2013', 'Fri, 22 Nov 2013',
'Fri, 14 Nov 2014', 'Tue, 18 Nov 2014', 'Fri, 26 Dec 2014',
'Mon, 13 Jan 2014', 'Tue, 28 Jan 2014', 'Mon, 03 Feb 2014',
'Mon, 31 Mar 2014', 'Mon, 07 Apr 2014', 'Tue, 10 Jun 2014',
'Mon, 30 Jun 2014', 'Mon, 22 Sep 2014', 'Mon, 06 Oct 2014',
'Mon, 10 Feb 2014', 'Tue, 18 Feb 2014', 'Fri, 07 Mar 2014',
'Thu, 15 Jan 2015', 'Mon, 23 Mar 2015', 'Mon, 20 Apr 2015']
missing_months(dates)
#=> ["Dec 2013", "May 2014", "Jul 2014", "Aug 2014", "Feb 2015"]
Notice that the dates needn't be sorted.
Explanation
For the example above:
a = dates.map { |s| d = Date.strptime(s, '%a, %d %b %Y'); d - d.day + 1 }
#=> [#<Date: 2013-10-01 ((2456567j,0s,0n),+0s,2299161j)>,
# #<Date: 2013-11-01 ((2456598j,0s,0n),+0s,2299161j)>,
# ...
# #<Date: 2015-04-01 ((2457114j,0s,0n),+0s,2299161j)>]
Notice that each of these dates is on the first of the month. Next, obtain the first and last of these dates:
f,l = a.minmax
f #=> [#<Date: 2013-10-01 ((2456567j,0s,0n),+0s,2299161j)>,
l #=> #<Date: 2015-04-01 ((2457114j,0s,0n),+0s,2299161j)>]
Now pass f and l to all_months_in_range to create an array that contains a date object for the first day of each month between f and l.
b = all_months_in_range(f,l)
#=> [#<Date: 2013-10-01 ((2456567j,0s,0n),+0s,2299161j)>,
# #<Date: 2013-11-01 ((2456598j,0s,0n),+0s,2299161j)>,
# ...
# #<Date: 2015-04-01 ((2457114j,0s,0n),+0s,2299161j)>]
b.size #=> 19
I will skip an explanation of this helper method, as it is quite straightforward.
Compute that difference between arrays b and a to obtain the missing beginning-of-month dates:
c = b-a
#=> [#<Date: 2013-12-01 ((2456628j,0s,0n),+0s,2299161j)>,
# #<Date: 2014-05-01 ((2456779j,0s,0n),+0s,2299161j)>,
# #<Date: 2014-07-01 ((2456840j,0s,0n),+0s,2299161j)>,
# #<Date: 2014-08-01 ((2456871j,0s,0n),+0s,2299161j)>,
# #<Date: 2015-02-01 ((2457055j,0s,0n),+0s,2299161j)>]
Lastly, convert these dates to the desired format:
c.map { |d| d.strftime('%b %Y') }
#=> ["Dec 2013", "May 2014", "Jul 2014", "Aug 2014", "Feb 2015"]
Addendum: after reading #Sid's answer, I see I could have saved myself some trouble in my helper method by using Date#next_month:
def all_months_in_range(f,l)
(12*(l.year-f.year)+l.month-f.month+1).times.map { |i| f.next_month(i) }
end
This isn't very elegant, but it worked. I started with your original code, #SupremeA, and built off of that.
require 'date'
dates = ['Wed, 23 Oct 2013', 'Mon, 18 Nov 2013', 'Fri, 22 Nov 2013', 'Mon, 13 Jan 2014', 'Tue, 28 Jan 2014', 'Mon, 03 Feb 2014', 'Mon, 10 Feb 2014', 'Tue, 18 Feb 2014', 'Fri, 07 Mar 2014', 'Mon, 31 Mar 2014', 'Mon, 07 Apr 2014', 'Tue, 10 Jun 2014', 'Mon, 30 Jun 2014', 'Mon, 22 Sep 2014', 'Mon, 06 Oct 2014', 'Fri, 14 Nov 2014', 'Tue, 18 Nov 2014', 'Fri, 26 Dec 2014', 'Thu, 15 Jan 2015', 'Mon, 23 Mar 2015', 'Mon, 20 Apr 2015']
new_dates = []
dates.each { |d| new_dates.push(Date.parse(d).strftime('%B %Y')) }
sorted_dates = new_dates.map { |s| Date.strptime(s, '%B %Y') }.sort.uniq
missing_months = []
sorted_dates.each_cons(2) do |d1,d2|
d = d1
while d.next_month != d2
missing_months.push(d.next_month.strftime('%B %Y'))
d = d >> 1
end
end
p missing_months
=> ["December 2013", "May 2014", "July 2014", "August 2014", "February 2015"]

Two dimensional array to hash

In Ruby, given an array:
{"server"=>["nginx/1.1.19"], "date"=>["Wed, 08 Jan 2014 18:48:02 GMT"],"content-type"=>["application/json; charset=utf-8"]}
What is the best way to convert this into a hash:
{"server"=>"nginx/1.1.19", "date"=>"Wed, 08 Jan 2014 18:48:02 GMT","content-type"=>"application/json; charset=utf-8"}
What you have is a Hash not an Array
h = {"server"=>["nginx/1.1.19"], "date"=>["Wed, 08 Jan 2014 18:48:02 GMT"],"content-type"=>["application/json; charset=utf-8"]}
Hash[h.map(&:flatten)]
# {"server"=>"nginx/1.1.19", "date"=>"Wed, 08 Jan 2014 18:48:02 GMT", "content-type"=>"application/json; charset=utf-8"}
Do as below :
Hash[h.map{|k,v| [k,v[0]] }]
# => {"server"=>"nginx/1.1.19",
# "date"=>"Wed, 08 Jan 2014 18:48:02 GMT",
# "content-type"=>"application/json; charset=utf-8"}
hash = {"server"=>["nginx/1.1.19"], "date"=>["Wed, 08 Jan 2014 18:48:02 GMT"],"content-type"=>["application/json; charset=utf-8"]}
hash.each{|k, v| hash[k] = v.first}
# => {"server"=>"nginx/1.1.19", "date"=>"Wed, 08 Jan 2014 18:48:02 GMT", "content-type"=>"application/json; charset=utf-8"}
h.map do |k, v|
if v.size > 1
{k => v.join("; ")}
else
{k => v.first}
end
end
# => [{"server"=>"nginx/1.1.19"}, {"date"=>"Wed, 08 Jan 2014 18:48:02 GMT"}, {"content-type"=>"application/json; charset=utf-8"}]
and then merge it to one hash:
h2.inject({}) do |acc, el|
acc.merge el
end
#=> {"server"=>"nginx/1.1.19", "date"=>"Wed, 08 Jan 2014 18:48:02 GMT", "content-type"=>"application/json; charset=utf-8"}
I assumed that every value could be array of strings, if value contain only one string, go for other, easier answer.

How do you get DateTime.parse to return a time in your time zone?

I need this
require 'date'
DateTime.parse "Mon, Dec 27 6:30pm"
to return a DateTime for 6:30pm in the EDT timezone, but it returns one in UTC. How can I get a EST DateTime or convert the UTC one into an EDT DateTime with a 6:30pm value?
OK I'm going to offer an answer to my own question
require 'time'
ENV["TZ"] = "US/Eastern"
Time.parse("Mon, Dec 27 6:30pm").to_datetime
=> #<DateTime: 2011-12-27T18:30:00-05:00 (117884327/48,-5/24,2299161)>
In Rails, this worked nicely for me
DateTime.parse "Mon, Dec 27 6:30pm #{Time.zone}"
It won't work in vanilla Ruby though.
Final answer ;-)
require 'date'
estHoursOffset = -5
estOffset = Rational(estHoursOffset, 24)
date = (DateTime.parse("Mon, Dec 27 6:30pm") - (estHoursOffset/24.0)).new_offset(estOffset)
(or -4 for EDT)
DateTime#change()
You can try using change() after parsing it to alter the timezone offset:
DateTime.parse( "Mon, Dec 27 6:30pm" ).change( offset: '-0400' )
# => Wed, 27 Dec 2017 18:30:00 -0400
You can also just use the hours:
DateTime.parse( "Mon, Dec 27 6:30pm" ).change( offset: '-4' )
# => Wed, 27 Dec 2017 18:30:00 -0400
But, be careful, you cannot use an integer:
DateTime.parse( "Mon, Dec 27 6:30pm" ).change( offset: -4 )
# => Wed, 27 Dec 2017 18:30:00 +0000
If you need to determine the correct offset to use based on a time zone you can do something like this:
offset = ( Time.zone_offset('EDT') / 1.hour ).to_s
# => "-4"
DateTime.parse( "Mon, Dec 27 6:30pm" ).change( offset: offset )
# => Wed, 27 Dec 2017 18:30:00 -0400
You can also use change() to manually set other parts of the DateTime as well, like setting the hour to noon:
DateTime.parse( "Mon, Dec 27 6:30pm" ).change( offset: '-4', hour: 12 )
# => Wed, 27 Dec 2017 12:00:00 -0400
Be careful with that one because you can see that it's cleared the minutes as well.
Here's the docs for the change() method: http://api.rubyonrails.org/v5.1/classes/DateTime.html#method-i-change
If you're using Rails' ActiveSupport:
"Mon, Dec 27 6:30pm".in_time_zone(-4.hours).to_datetime
# => Mon, 27 Dec 2021 18:30:00 -0400
Time.find_zone(-4.hours).parse("Mon, Dec 27 6:30pm").to_datetime
# => Mon, 27 Dec 2021 18:30:00 -0400
If you want to use the local daylight saving time (DST) rules, you could use:
"Mon, Dec 27 6:30pm".in_time_zone("Eastern Time (US & Canada)")
# => Mon, 27 Dec 2021 18:30:00 EST -05:00
Time.find_zone("Eastern Time (US & Canada)").parse("Mon, Dec 27 6:30pm")
# => Mon, 27 Dec 2021 18:30:00 EST -05:00
Time.find_zone("Eastern Time (US & Canada)").parse("Mon, Dec 27 6:30pm").to_datetime
# => Mon, 27 Dec 2021 18:30:00 -0500
Time.find_zone("Eastern Time (US & Canada)").parse("Mon, Jun 27 6:30pm")
# => Sun, 27 Jun 2021 18:30:00 EDT -04:00
Time.find_zone("Eastern Time (US & Canada)").parse("Mon, Jun 27 6:30pm").to_datetime
# => Sun, 27 Jun 2021 18:30:00 -0400
Time.find_zone("EST5EDT").parse("Mon, Jun 27 6:30pm").to_datetime
# => Sun, 27 Jun 2021 18:30:00 -0400
Notice the date in June, above, is automatically set to EDT (-0400) because this date is in DST, contrary to the December date.
To force EST regardless if date is within DST or not:
Time.find_zone("EST").parse("Mon, Jun 27 6:30pm")
# => Sun, 27 Jun 2021 18:30:00 EST -05:00
Time.find_zone("EST").parse("Mon, Jun 27 6:30pm").to_datetime
# => Sun, 27 Jun 2021 18:30:00 -0500

Resources