简体   繁体   中英

Sorting array elements by date

I have the following array.

dates = ["6/23/2014", "8/5/2014", "8/19/2014", "6/26/2014", "8/19/2014", "8/19/2014",
"7/8/2014", "6/3/2014", "7/30/2014", "7/3/2014", "6/3/2014", "6/26/2014"]

Our array stores 10 string objects. In order to sort them my way, I need to re-arrange each element.

"2014/8/5", "2014/8/19", "2014/8/19", etc.

This way, 1/1/2014 doesn't come before 12/13/1994 for example.

I tried the following

dates.sort_by {|date|date.split(/[\/]/).rotate(-1).join('/')}

I would like to sort this array by date, with the newest dates first.

Actual Output

8/5/2014
8/19/2014
8/19/2014
8/19/2014
7/8/2014
7/30/2014
7/3/2014
6/3/2014
6/3/2014
6/26/2014

Expected Output

8/19/2014
8/19/2014
8/19/2014
8/5/2014
7/30/2014
7/3/2014
6/26/2014
6/3/2014
6/3/2014

The solution involves utilizing the Ruby Array Class sort_by method. I would like to sort with the date format, where newer dates come before earlier dates.

My question is, how would I sort the array based on the date.

My apologies if this is a stupid question, but I've searched this site, and couldn't find any results.

Thank you in advance for any help.

Try:

dates.sort_by { |s| Date.strptime(s, '%m/%d/%Y') }

If you want it starting from the newest date:

dates.sort_by { |s| Date.strptime(s, '%m/%d/%Y') }.reverse

I don't know why your expected output contains only 9 out of 12 entries, and that you mention an array of size 10, but I believe your want to sort all 12. My preference would be to do it the way @BroiSatse has, but I will offer two ways that do not use methods from the class Date . I include them because it has some interesting aspects for those new to Ruby.

dates = [
  "6/23/2014", "8/5/2014", "8/19/2014", "6/26/2014", "8/19/2014", "10/19/2014",
  "7/8/2014", "6/3/2014", "7/30/2014", "7/3/2014", "6/3/2014", "6/26/2014"
]

I have changed dates slightly so that it includes a date having a two-digit month ( "10/19/2014" ).

Sort by arrays of integers

dates.sort_by { |d| d.split(?/).rotate(-1).map { |e| -e.to_i } }
  #=> ["10/19/2014", "8/19/2014", "8/19/2014", "8/5/2014", "7/30/2014", "7/8/2014",
  #    "7/3/2014", "6/26/2014", "6/26/2014", "6/23/2014", "6/3/2014", "6/3/2014"]

Here's what's happening.

enum = dates.sort_by
  #=> #<Enumerator: [
  #     "6/23/2014", "8/5/2014", "8/19/2014", "6/26/2014", "8/19/2014", "10/19/2014",
  #     "7/8/2014", "6/3/2014", "7/30/2014", "7/3/2014", "6/3/2014", "6/26/2014"
  #   ]:sort_by>

As you see, enum is an enumerator. The first value that it passes into its block is "6/23/2014" , which it assigns to the block variable:

d = "6/23/2014"

a = d.split(?/)
  #=> ["6", "23", "2014"]
b = a.rotate(-1)
  #=> ["2014", "6", "23"]
c = b.map { |e| -e.to_i }
  #=> [-2014, -6, -23]

Similarly, the third element of enum is converted to:

"8/19/2014".split(?/).rotate(-1).map { |e| -e.to_i }
  #=> [-2014, -8, -19]

sort_by usesArray#<=> to compare pairs of elements. After reading that doc you'll understand why:

[-2014, -6, -23] <=> [-2014, -8, -19]
  #=> 1

meaning that "8/19/2014" should precede "6/23/2014" in the sort.

Incidentally, it was necessary to convert months and days from strings to integers because those strings did not contain leading zeros for single-digit values. If we had left them as strings, "8" > "15" , which is not what we want. Since we are converting to integers anyway, it was easier to make them negative than to leave them positive and apply reverse to the sorted array.

Sort by strings in year-month-day order padded with leading zeros as necessary, then reverse

dates.sort_by do |d|
  d.sub(/(\d+)\/(\d+)\/(\d+)/) { "%4d%02d%02d" % [$3, $1, $2].map(&:to_i) }
end.reverse
  #=> ["10/19/2014", "8/19/2014", "8/19/2014", "8/5/2014", "7/30/2014", "7/8/2014",
  #    "7/3/2014", "6/26/2014", "6/26/2014", "6/23/2014", "6/3/2014", "6/3/2014"]

Note that

arr = dates.map do |d|
  d.sub(/(\d+)\/(\d+)\/(\d+)/) { "%4d%02d%02d" % [$3, $1, $2].map(&:to_i) }
end
  #=> ["20140623", "20140805", "20140819", "20140626", "20140819", "20141019",
  #    "20140708", "20140603", "20140730", "20140703", "20140603", "20140626"]
arr.sort
  #=> ["20140603", "20140603", "20140623", "20140626", "20140626", "20140703",
  #    "20140708", "20140730", "20140805", "20140819", "20140819", "20141019"]

See Kernel#sprintf for formatting directives. It appears that .map(&:to_i) is in fact unnecessary; that Ruby treates strings representing integers as integers when the d directive is used.

The regular expression /(\\d+)\\/(\\d+)\\/(\\d+)/ matches one or digits (the month), which are saved to capture group 1, followed by '/' , followed by one or digits (the day), which are saved to capture group 2, followed by '/' , followed by one or digits (the year), which are saved to capture group 3. One could alternatively write this:

/(\d{1,2})\/(\d{1,2})\/(\d{4})/

or with named capture groups:

r = /(?<month>\d{1,2})\/(?<day>\d{1,2})\/(?<year>\d{4})/

dates.sort_by do |d|
  d.sub(r) { "%4d%02d%02d" % [$~[:year], $~[:month], $~[:day]].map(&:to_i) }
end.reverse
  #=> ["10/19/2014", "8/19/2014", "8/19/2014", "8/5/2014", "7/30/2014", "7/8/2014",
  #    "7/3/2014", "6/26/2014", "6/26/2014", "6/23/2014", "6/3/2014", "6/3/2014"]

$~ is the most recent MatchData object . It equalsRegexp::last_match .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM