Order Hash and delete first key-value pair
Asked Answered
E

3

5

I have a Hash with timestamp as keys.

hash = {
  "2016-05-31T22:30:58+02:00" => {
          "path" => "/",
        "method" => "GET"
  },
  "2016-05-31T22:31:23+02:00" => {
          "path" => "/tour",
        "method" => "GET"
  },
  "2016-05-31T22:31:05+02:00" => {
          "path" => "/contact_us",
        "method" => "GET"
  }
}

I order the collection and get the first pair like this:

hash.sort_by {|k, _| k}.first.first

But how do I remove it?

The delete method requires you to know the exakt spelling of the key. Of course I could return the key and then use it in the delete method, but I was thinking if there was any more straight forward way?

Enenstein answered 31/5, 2016 at 20:47 Comment(2)
Note that sort_by returns an array.Waiver
I have now clarified the question.Enenstein
I
9

The method you are looking for is hash.keys it returns an array of the keys:

hash.delete(hash.keys.min)

EDIT: I've updated the answer to reflect that keys must be sorted first, this has been added in the original question and brought up by @Shadwell in comments to this post.

I replaced hash.keys.sort.first for hash.keys.min as suggested by @Cary Swoveland, it is not only more performant but better semantically.

Impressment answered 31/5, 2016 at 20:52 Comment(9)
You'd need to sort the keys too I think to get the first one in order : hash.delete(hash.keys.sort.first) (I appreciate the title says an ordered hash but the question then sorts the keys explicitly)Sauer
I guess it depends if the keys are sorted or not, but that's not clear in the question. But yeah, you could sort them out too.Holsinger
yeah, agreed, it's slightly unclear. Think you have the right approach though in both cases.Sauer
@Shadwell, why do you have to sort to find the smallest key?Davies
I suggest hash.keys.min or hash.min_by(&:first).first rather than hash.keys.sort.first. Sorting should be avoided when only the smallest or largest value is needed.Davies
Thanks for the suggestion, it definitely looks/reads better and I'll be using it. But why do you say sort should be avoided? As far as I can tell both .min and .sort depend on <=>.Holsinger
Finding the minimum is much faster than sorting. Think about it, finding the minimum requires just a single pass through the array.Davies
I don't know how I missed that! Thanks again for keeping up with me and explaining, you are right, a sort is likely O(n log n), while finding the min is just O(n).Holsinger
You don't have to state that you have updated your answer. Consider putting attributions in comments, to keep your answers "clean". If (in a comment) you thank the author of a comment for a suggestion and say that you've edited your answer to incorporate it), that's sufficient.Davies
W
10

Also note that shift can be used on the Hash values.

hash.shift

Removes the first key-value pair from the hash. Works on arrays too.

Water answered 31/5, 2016 at 20:58 Comment(4)
Is not an array of hashes, it's simply a hash. .shift still works though.Holsinger
You need to add the line hash after hash.shift, or possibly replace your line with hash.tap { |h| hash.shift }.Davies
You only need to add hash after if you are returning the value, but the statement alone modifies hash, what happens later is out of the scope of the question.Holsinger
I wasn't aware of Hash#shift (why no Hash#pop?). Good to know.Davies
I
9

The method you are looking for is hash.keys it returns an array of the keys:

hash.delete(hash.keys.min)

EDIT: I've updated the answer to reflect that keys must be sorted first, this has been added in the original question and brought up by @Shadwell in comments to this post.

I replaced hash.keys.sort.first for hash.keys.min as suggested by @Cary Swoveland, it is not only more performant but better semantically.

Impressment answered 31/5, 2016 at 20:52 Comment(9)
You'd need to sort the keys too I think to get the first one in order : hash.delete(hash.keys.sort.first) (I appreciate the title says an ordered hash but the question then sorts the keys explicitly)Sauer
I guess it depends if the keys are sorted or not, but that's not clear in the question. But yeah, you could sort them out too.Holsinger
yeah, agreed, it's slightly unclear. Think you have the right approach though in both cases.Sauer
@Shadwell, why do you have to sort to find the smallest key?Davies
I suggest hash.keys.min or hash.min_by(&:first).first rather than hash.keys.sort.first. Sorting should be avoided when only the smallest or largest value is needed.Davies
Thanks for the suggestion, it definitely looks/reads better and I'll be using it. But why do you say sort should be avoided? As far as I can tell both .min and .sort depend on <=>.Holsinger
Finding the minimum is much faster than sorting. Think about it, finding the minimum requires just a single pass through the array.Davies
I don't know how I missed that! Thanks again for keeping up with me and explaining, you are right, a sort is likely O(n log n), while finding the min is just O(n).Holsinger
You don't have to state that you have updated your answer. Consider putting attributions in comments, to keep your answers "clean". If (in a comment) you thank the author of a comment for a suggestion and say that you've edited your answer to incorporate it), that's sufficient.Davies
D
2

As the OP has not stated that the hash's keys are in sorted order, we must assume that there is no guarantee that they are.

hash = { "2016-05-31T22:31:05+02:00"=>{ "path"=>"/tour", "method"=>"GET" },
         "2016-05-31T22:30:58+02:00"=>{ "path"=>"/", "method"=>"GET" },
         "2016-05-31T22:31:23+02:00"=>{ "path"=>"/contact_us", "method"=>"GET" } }

First, find the smallest key (the second one):

smallest_key = hash.keys.min
  #=> "2016-05-31T22:30:58+02:00" 

This is obviously more efficient than sorting the keys then taking the smallest.

Because the date-time strings are in iso8601 format, they can be sorted as strings, without having to first convert them to time objects.

Then use Hash#reject to obtain the desired hash:

hash.reject { |k,_| k == smallest_key }
  #=> {"2016-05-31T22:31:05+02:00"=>{"path"=>"/tour", "method"=>"GET"},
  #    "2016-05-31T22:31:23+02:00"=>{"path"=>"/contact_us", "method"=>"GET"}} 

To change hash in place, write

hash.delete(smallest_key }
hash
Davies answered 31/5, 2016 at 21:19 Comment(4)
Don't loop to find the entry matching the key, that's O(n), if you have the key, simply use it, access to hashes is O(1).Holsinger
Thanks, @Leito. That's a good suggestion for the case where the hash is to be mutated. (I did an edit.) If the hash is not to be mutated (the working assumption), one could write h = hash.dup; h.delete(smallest_key); h, but I prefer reject, which reads better (imo), does not require a non-block variable, is one line versus three and is probably not much worse performance-wise.Davies
You are right, it depends on the desired result, mutateted hash or not. Performance depends on n, for smaller n's probably not, for bigger n's yes.Holsinger
Downvoter: note that the OP does not reorder the hash's keys.Davies

© 2022 - 2024 — McMap. All rights reserved.