sortやsort_byの対象にnilが含まれていると扱いにくい
例えば、以下のような例。
- ユーザーを最終ログイン時刻でソートする
class User attr_accessor :name,:last_login def initialize(name,last_login) @name = name @last_login = last_login end def to_s "#{self.name}:#{self.last_login}" end end users = [] users << User.new("bob",Time.mktime(2007,1,1)) << User.new("jon",Time.mktime(2007,5,6)) << User.new("ben",Time.mktime(2008,4,1)) users.sort_by{|user| user.last_login} puts users #結果---------------------- bob:Mon Jan 01 00:00:00 +0900 2007 jon:Sun May 06 00:00:00 +0900 2007 ben:Tue Apr 01 00:00:00 +0900 2008
この場合は良いのですが、最終ログインがnilのユーザーが含まれていると面倒。
- 最終ログインがnilのユーザーが含まれている場合
users = [] users << User.new("bob",Time.mktime(2007,1,1)) << User.new("jon",Time.mktime(2007,5,6)) << User.new("ben",Time.mktime(2008,4,1)) << User.new("kai",nil) << User.new("rai",nil) users.sort_by{|user| user.last_login} puts users #結果---------------------- array_partition_test.rb:21:in `sort_by': comparison of Time with nil failed (ArgumentError) from array_partition_test.rb:21
ソート対象にnilが含まれているので、内部で、Time.mktime(2008,4,1) <=> nilというような状態が発生し、nilとTimeを比較しようとしてエラーになる。
解決策
Enumerable#partitionを使う
各要素に対してブロックを評価した値が真であった要素からなる配列と偽であった要素からなる配列からなる配列を返します。
#Userにメソッド追加 class User def logged_in? !self.last_login.nil? end end users = [] users << User.new("bob",Time.mktime(2007,1,1)) << User.new("jon",Time.mktime(2007,5,6)) << User.new("ben",Time.mktime(2008,4,1)) << User.new("kai",nil) << User.new("rai",nil) #last_loginがあるないでユーザーを分割 logged_in_users, non_logged_in_users = users.partition{|user| user.logged_in?} #last_loginがあるユーザーだけソートして、ないユーザーとの和をとったものをusersに代入 users = (non_logged_in_users + logged_in_users.sort_by{|user| user.last_login}) puts user #結果---------------------- kai: rai: bob:Mon Jan 01 00:00:00 +0900 2007 jon:Sun May 06 00:00:00 +0900 2007 ben:Tue Apr 01 00:00:00 +0900 2008
もう少し綺麗にならないものかな