11

Given that I have array of hashes, how can I sort them (using ruby) into a podium style (using their created_at value) like in the image below?

[
  { created_at: "DATETIME", src: "..." },
  { created_at: "DATETIME", src: "..." },
  { created_at: "DATETIME", src: "..." },
  { created_at: "DATETIME", src: "..." }
]

enter image description here

4

16 回答 16

8

arr.sort_by{|a| a['created_at']}.inject([]){ |r, e| r.reverse << e }

有趣的问题!

于 2013-11-11T02:40:44.237 回答
6

我相信你可以进一步压缩它,但这样的事情会奏效:

# Your initial array
item_array = [{...}]
count = 0

# Sort it first, then stagger results to each side of the array
podium_sorted = item_array.sort_by{|a| a['created_at']}.inject([]) do |arr, item|
  count += 1
  count % 2 == 0 ? arr.unshift(item) : arr.push(item)
end
于 2013-10-30T17:06:23.333 回答
5

如果您不反对使用完全精神的解决方案,我非常喜欢这个:

zipped = (1..5).zip [:push, :unshift].cycle
# => [[1, :push], [2, :unshift], [3, :push], [4, :unshift], [5, :push]]

[].tap { |result| zipped.each { |val, op| result.send op, val } }
# => [4, 2, 1, 3, 5]

module Enumerable
  def to_podium
    [].tap { |r| (zip [:push, :unshift].cycle).each { |v, o| r.send o, v } }
  end
end

(1..10).to_podium
# => [10, 8, 6, 4, 2, 1, 3, 5, 7, 9]

并在行动中展示它:

test_input = (1..5).map { |i| { created_at: i, some_val: rand(100) } }.shuffle
# => [{:created_at=>3, :some_val=>69},
#     {:created_at=>5, :some_val=>15},
#     {:created_at=>2, :some_val=>89},
#     {:created_at=>4, :some_val=>77},
#     {:created_at=>1, :some_val=>54}]

test_input.sort_by { |e| e[:created_at] }.to_podium
# => [{:created_at=>4, :some_val=>77},
#     {:created_at=>2, :some_val=>89},
#     {:created_at=>1, :some_val=>54},
#     {:created_at=>3, :some_val=>69},
#     {:created_at=>5, :some_val=>15}]
于 2013-11-11T10:34:53.163 回答
3
def podium_sort(array)
  array.sort_by{|h| h[:created_at]}.each_with_index.inject([]) do |out, (item, index)|
    index.odd? ? out.unshift(item) : out.push(item)
  end
end

podium_sort((1..10).map { |value| {created_at: Time.now - rand(value..100).minutes } })
=> [{:created_at=>2013-10-30 18:03:54 -0400},
 {:created_at=>2013-10-30 17:58:54 -0400},
 {:created_at=>2013-10-30 17:44:54 -0400},
 {:created_at=>2013-10-30 17:18:54 -0400},
 {:created_at=>2013-10-30 16:54:54 -0400},
 {:created_at=>2013-10-30 16:48:54 -0400},
 {:created_at=>2013-10-30 16:57:54 -0400},
 {:created_at=>2013-10-30 17:37:54 -0400},
 {:created_at=>2013-10-30 17:44:54 -0400},
 {:created_at=>2013-10-30 18:00:54 -0400}]
于 2013-10-30T17:18:48.447 回答
2

还有一种方式

arr.sort_by { |h| h[:created_at] }.sort_by.with_index { |_,i| i.odd? ? -i : i }
于 2014-03-21T23:49:02.703 回答
2

如果您想要一个没有两种类型的解决方案:

arr = [ { :created_at => ... }, { :created_at => ... }, ... ]
size = arr.count
poss = Array.new(size) { |i| i%2 == 0 ? size-(i/2+1) : i/2 }
final = [-1]*size

然后将插入排序应用于非线性映射只是一件简单的事情poss。所以你会做这样的事情:

arr.each do |val|
  poss.each_with_index do |pos, pos_index|
    if final[pos] == -1
      final[pos] = val
      break
    elsif final[pos][:created_at] < val[:created_at]
      tmp_val = final[pos]
      final[pos] = val
      poss[pos_index+1..size].each do |new_pos|
        if final[new_pos] == -1
          final[new_pos] = tmp_val
          break
        elsif final[new_pos][:created_at] < tmp_val[:created_at]
          tmp2_val = final[new_pos]
          final[new_pos] = tmp_val
          tmp_val = tmp2_val
        end
      end
      break
    end
  end
end

非常简单地说,这只是插入排序算法的一个实现,只是我们不是映射到线性行,而是映射到 poss。

于 2013-11-11T22:41:25.183 回答
2

使用替代解决方案Enumerable#partition

items     = [5,1,2,4,3].sort # use any sort method that is relevant
odd, even = items.each_with_index.partition{ |item,index| index.odd? }
podium    = even.reverse.push(*odd).map(&:first) # &:first to remove index 

可能效率稍低(必须构建中间数组),但更紧凑。

此外,一些人在这里公开的解决方案也可以更紧凑地重写:

items.sort.each_with_index.inject([]) do |podium,(item,index)|
  index.odd? ? podium.unshift(item) : podium.push(item)
end

这可能是Enumerable您选择的任何可枚举类的一个很好的扩展:

module Enumerable
  def podium(&block)
    items = block_given? ? sort : sort_by(&block)
    items.each_with_index.inject([]) do |podium,(item,index)|
      index.odd? ? podium.unshift(item) : podium.push(item)
    end
  end
end
于 2013-11-09T13:28:45.780 回答
1
# Initializing variable to store the output
podium_sorted = []


# Assuming sorting has to be in descending order of created_at 
#(If you want it in ascending the just remove `reverse` from the below line)
sorted_array = input_array.sort_by { |record| record[:created_at] }.reverse
sorted_array.each_with_index do |record, index|
  index.even? ? podium_sorted << record : podium_sorted = [record] + podium_sorted
end
于 2013-10-30T17:16:54.843 回答
1

对项目进行排序,然后从中间向外扩展讲台。

def podium_sort(items)
  sorted = items.sort_by{|h|h[:created_at]}
  sorted.each_slice(2).reduce([]) { |podium, pair|
    [pair[1]] + podium + [pair[0]]
  }.compact
end

更多变体,使用each_slice(2)

排序项目,建立讲台的左右两侧,然后组合。

def podium_sort(items)
  left = []
  right = []
  sorted = items.sort_by{|h|h[:created_at]}
  sorted.each_slice(2){|a,b| left.unshift(b); right << a}
  (left.compact + right)
end

对项目进行排序,将成对的列转置为行,然后合并。

def podium_sort(items)
  sorted = items.sort_by{|h|h[:created_at]}
  sorted += [nil] if items.length.odd?
  right, left = sorted.each_slice(2).to_a.transpose
  (left.compact.reverse + right)
end
于 2013-11-07T00:34:33.557 回答
1

让数组按 created_at 属性排序,我们可以提供一个类似的方法

def podium_sort(array)
  result = []
  l = array.length / 2 + 1
  l.times do |i|
    result.push(array[2*i])
    result.unshift(array[2*i+1])
  end
  result.compact
end

编辑

我针对以下两个解决方案对此解决方案进行了基准测试:

基于检查数组的每个元素的实现

def pod_if(ar)
  count = 0
  ar.sort_by{|a| a[:created_at]}.inject([]) { |arr, item|
    count += 1
    count % 2 == 0 ? arr.unshift(item) : arr.push(item); 
  }
end

以及具有连续反转的实现(@bonzofenix)。(最优雅,但相当昂贵)

def pod_reverse(arr)
  arr.sort_by{|a| a['created_at']}.inject([]){ |r, e| r.reverse << e }
end

对于 100 个哈希的数组 a,基准是:

Benchmark.bm do |x|
  x.report('zig_zag') { 1000000.times { podium_sort a } }
  x.report('if_based') { 1000000.times { pod_if a } }
  x.report('reverse') { 1000000.times { pod_reverse a } }
end

和结果

               user        system    total      real
zig_zag        89.090000   0.490000   89.580000 (121.833205)
if_based       97.250000   0.230000   97.480000 (123.692612)
reverse       207.050000   0.610000  207.660000 (267.401497)

所以最后一切都集中在我们想要关注的地方,

  • “连续反转”实现(恕我直言)非常优雅,但速度非常慢。
  • 另一方面,其他两种实现似乎具有相似的速度(基于数组长度的一种,略好一些)。
于 2013-11-10T18:32:06.803 回答
1

这是我使用 Ruby 的 OpenStruct 和 Mixin 提出的解决方案。

FWIW 我在初始哈希中添加了一个名为 given_order 的键,以便在打印输出上获得视觉提示。显然这对于​​最后的问题是不必要的。

require 'ostruct'

# compose a lightweight class and make it comparable
class SortableSeed < OpenStruct
  include Comparable
  def <=>(other)
    created_at <=> other.created_at
  end
end

# initial seed. src
seed = [
   { created_at: Time.now + 180, src: "...", given_order: "1" },
   { created_at: Time.now + 60,  src: "...", given_order: "2" },
   { created_at: Time.now + 240, src: "...", given_order: "3" },
   { created_at: Time.now,       src: "...", given_order: "4" },
   { created_at: Time.now + 320, src: "...", given_order: "5" }
]

# show inital arrangement
puts "Initial Seed (random order)\n"
puts seed
puts "\n"

# Create our structs from seed
structs = seed.map {|struct| SortableSeed.new(struct)}

# sort and out print them
puts "Podium Sort (by created_at)\n"
structs.sort.map {|struct| puts struct.inspect}

输出:

# Initial Seed (random order)
{:created_at=>2013-11-05 17:30:22 -0600, :src=>"...", :given_order=>"1"}
{:created_at=>2013-11-05 17:28:22 -0600, :src=>"...", :given_order=>"2"}
{:created_at=>2013-11-05 17:31:22 -0600, :src=>"...", :given_order=>"3"}
{:created_at=>2013-11-05 17:27:22 -0600, :src=>"...", :given_order=>"4"}
{:created_at=>2013-11-05 17:32:42 -0600, :src=>"...", :given_order=>"5"}

# Podium Sort (by created_at)
#<SortableSeed created_at=2013-11-05 17:27:22 -0600, src="...", given_order="4">
#<SortableSeed created_at=2013-11-05 17:28:22 -0600, src="...", given_order="2">
#<SortableSeed created_at=2013-11-05 17:30:22 -0600, src="...", given_order="1">
#<SortableSeed created_at=2013-11-05 17:31:22 -0600, src="...", given_order="3">
#<SortableSeed created_at=2013-11-05 17:32:42 -0600, src="...", given_order="5">

有趣的问题。

于 2013-11-05T23:32:07.110 回答
1

单线:

((0...A.length).select(&:odd?).reverse + (0...A.length).select(&:even?)).collect { |p| A.sort_by { |i| i[:created_at] }[p] }

或者,将其分解为更易读的形式:

podium = ((0...A.length).select(&:odd?).reverse + (0...A.length).select(&:even?)
sorted_array = A.sort_by { |i| i[:created_at] }
podium.collect { |p| sorted_array[p] }
于 2013-11-11T20:55:17.753 回答
1
def podium_sort arr
  arr.sort_by {|h| h[:created_at]}.map.with_index {|e,i|
    i.odd? ? [-i,e] : [i,e]}.sort.map(&:last)
end

arr = [
  {:created_at=>2013-11-05 22:20:59 -0800},
  {:created_at=>2013-11-05 22:22:07 -0800},
  {:created_at=>2013-11-05 22:21:31 -0800},
  {:created_at=>2013-11-05 22:22:04 -0800},
  {:created_at=>2013-11-05 22:21:06 -0800},
  {:created_at=>2013-11-05 22:21:10 -0800},
  {:created_at=>2013-11-05 22:20:44 -0800},
  {:created_at=>2013-11-05 22:20:52 -0800},
  {:created_at=>2013-11-05 22:22:00 -0800},
  {:created_at=>2013-11-05 22:21:50 -0800},
  {:created_at=>2013-11-05 22:21:15 -0800}
]

podium_sort(arr) #=> [
  {:created_at=>2013-11-05 22:22:04 -0800},
  {:created_at=>2013-11-05 22:21:50 -0800},
  {:created_at=>2013-11-05 22:21:15 -0800},
  {:created_at=>2013-11-05 22:21:06 -0800},
  {:created_at=>2013-11-05 22:20:52 -0800},
  {:created_at=>2013-11-05 22:20:44 -0800},
  {:created_at=>2013-11-05 22:20:59 -0800},
  {:created_at=>2013-11-05 22:21:10 -0800},
  {:created_at=>2013-11-05 22:21:31 -0800},
  {:created_at=>2013-11-05 22:22:00 -0800},
  {:created_at=>2013-11-05 22:22:07 -0800}
]

编辑:还有一个:

def sel(a,t) a.select {|e| t = !t} end

def podium_sort(a)
  sel(a.sort!,true).reverse + sel(a,false)
end
于 2013-11-06T06:31:37.010 回答
0

正常对数组进行排序,然后运行以下代码:

result = []
a.each.with_index do |x, i|
  i.even? ? result.push(x) : result.unshift(x)
end

另一种方法:

a.push(nil) if a.size.odd?
a = a.each_slice(2).to_a.transpose
p (a.first.reverse + a.last).compact
于 2013-10-31T16:35:24.627 回答
0

它基于 (-1)**k 的符号变化。但是有2种排序:(

arr = [....]
k = - 1;
arr.sort_by{|item| item[:created_at]}.sort_by{|item| k *= -1; k*item[:created_at].to_time.to_i}
于 2013-11-09T00:11:45.453 回答
0
toggle = true
array.sort_by{|a| a['created_at']}.inject([]) do |r, e| 
    toggle  ? r.push(e) : r.unshift(e)
    toggle = !toggle
    r
end
于 2013-11-13T07:42:54.047 回答