Сортировка стилей подиума в Ruby

Учитывая, что у меня есть массив хэшей, как я могу сортировать их (используя ruby) в стиле подиума (используя их значение created_at), как на изображении ниже?

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

enter image description here

Ответ 1

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

Забавная проблема!

Ответ 2

Я уверен, что вы могли бы сжать это дальше, но что-то вроде этого сделало бы трюк:

# 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

Ответ 3

Если вы не прочь использовать полностью мысленное решение, мне это очень нравится:

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}]

Ответ 4

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}]

Ответ 5

Альтернативное решение с использованием 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

Ответ 6

Если вам нужно решение без двух видов:

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

Проще говоря, это всего лишь реализация алгоритма сортировки вставки, только вместо того, чтобы сопоставлять линейную линию, мы сопоставляем с ним.

Ответ 7

Еще один способ

arr.sort_by { |h| h[:created_at] }.sort_by.with_index { |_,i| i.odd? ? -i : i }

Ответ 8

# 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

Ответ 9

Это было решение, с которым я столкнулся с использованием 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">

Забавная проблема.

Ответ 10

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

Ответ 11

Сортировка предметов, затем рост подиума от середины.

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

Ответ 12

имеющий массив, отсортированный по атрибуту 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

Для массива a из 100 хэшей эталонный показатель:

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)

Итак, в конце концов, все заканчивается тем, где мы хотим сфокусироваться,

  • Реализация "последовательных обратных" (IMHO) очень элегантная, но она значительно медленнее.
  • С другой стороны, две другие реализации, похоже, находятся на одинаковой скорости (одна на основе длины массива, немного лучше).

Ответ 13

Однострочник:

((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] }

Ответ 14

Сортируйте массив как обычно, а затем запустите следующий код:

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

Ответ 15

Он основан на изменении знака (-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}

Ответ 16

toggle = true
array.sort_by{|a| a['created_at']}.inject([]) do |r, e| 
    toggle  ? r.push(e) : r.unshift(e)
    toggle = !toggle
    r
end