Ответ 1
arr.sort_by{|a| a['created_at']}.inject([]){ |r, e| r.reverse << e }
Забавная проблема!
Учитывая, что у меня есть массив хэшей, как я могу сортировать их (используя ruby) в стиле подиума (используя их значение created_at), как на изображении ниже?
[
{ created_at: "DATETIME", src: "..." },
{ created_at: "DATETIME", src: "..." },
{ created_at: "DATETIME", src: "..." },
{ created_at: "DATETIME", src: "..." }
]
arr.sort_by{|a| a['created_at']}.inject([]){ |r, e| r.reverse << e }
Забавная проблема!
Я уверен, что вы могли бы сжать это дальше, но что-то вроде этого сделало бы трюк:
# 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
Если вы не прочь использовать полностью мысленное решение, мне это очень нравится:
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}]
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}]
Альтернативное решение с использованием 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
Если вам нужно решение без двух видов:
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
Проще говоря, это всего лишь реализация алгоритма сортировки вставки, только вместо того, чтобы сопоставлять линейную линию, мы сопоставляем с ним.
Еще один способ
arr.sort_by { |h| h[:created_at] }.sort_by.with_index { |_,i| i.odd? ? -i : i }
# 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
Это было решение, с которым я столкнулся с использованием 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">
Забавная проблема.
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
Сортировка предметов, затем рост подиума от середины.
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
имеющий массив, отсортированный по атрибуту 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)
Итак, в конце концов, все заканчивается тем, где мы хотим сфокусироваться,
Однострочник:
((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] }
Сортируйте массив как обычно, а затем запустите следующий код:
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
Он основан на изменении знака (-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}
toggle = true
array.sort_by{|a| a['created_at']}.inject([]) do |r, e|
toggle ? r.push(e) : r.unshift(e)
toggle = !toggle
r
end