Цикл двух массивов одновременно - PullRequest
2 голосов
/ 16 октября 2019

У меня есть массив с заказами, каждый с датой. Как:

[
#<Order id: 1, date: '2019-10-07'>,
#<Order id: 2, date: '2019-10-08'>,
#<Order id: 3, date: '2019-10-10'>,
#<Order id: 4, date: '2019-10-10'>,
#<Order id: 5, date: '2019-10-12'>
]

Я хочу отобразить это так:

2019-10-05:
2019-10-06:
2019-10-07: id 1
2019-10-08: id 2
2019-10-09:
2019-10-10: id 3, id 4
2019-10-11:
2019-10-12: id 5
2019-10-13: 

Каков наилучший способ сделать это?

Я могу придумать следующие варианты:

  1. date_range.each do ... и проверьте, есть ли соответствующие заказы на эту дату.
  2. Сначала отсортируйте массив заказов, затем выполните orders.each do ... и проверьте, не пропущены ли какие-либо даты.

Есть ли какой-то 3-й путь, который проходит через оба массива одновременно? Как начать с дат, когда есть соответствующий заказ, продолжить с заказов до новой даты?

Ответы [ 5 ]

1 голос
/ 16 октября 2019

Подобно тому, что Майкл Кол и arieljuod описывают в своих ответах. Сначала сгруппируйте даты по дате, затем прокрутите даты и выберите группы, которые имеют отношение.

# mock
orders = [{id: 1, date: '2019-10-07'}, {id: 2, date: '2019-10-08'}, {id: 3, date: '2019-10-10'}, {id: 4, date: '2019-10-10'}, {id: 5, date: '2019-10-12'}]
orders.map!(&OpenStruct.method(:new))

# solution
orders = orders.group_by(&:date)
orders.default = []

date_range = Date.new(2019, 10, 5)..Date.new(2019, 10, 13)
date_range.map(&:iso8601).each do |date|
  ids = orders[date].map { |order| "id: #{order.id}" }.join(', ')
  puts "#{date}: #{ids}"
end
# 2019-10-05:
# 2019-10-06:
# 2019-10-07: id: 1
# 2019-10-08: id: 2
# 2019-10-09:
# 2019-10-10: id: 3, id: 4
# 2019-10-11:
# 2019-10-12: id: 5
# 2019-10-13:
#=> ["2019-10-05", "2019-10-06", "2019-10-07", "2019-10-08", "2019-10-09", "2019-10-10", "2019-10-11", "2019-10-12", "2019
0 голосов
/ 17 октября 2019

Данные

Нам дан массив экземпляров класса Order:

require 'date'

class Order
  attr_reader :id, :date
  def initialize(id,date)
    @id = id
    @date = date
  end
end

arr = ['2019-10-07', '2019-10-08', '2019-10-10', '2019-10-10', '2019-10-12'].
  map.each.with_index(1) { |s,i| Order.new(i, Date.iso8601(s)) }
  #=> [#<Order:0x00005a49d68ad8b8 @id=1,
  #      @date=#<Date: 2019-10-07 ((2458764j,0s,0n),+0s,2299161j)>>,
  #    #<Order:0x00005a49d68ad6d8 @id=2,
  #      @date=#<Date: 2019-10-08 ((2458765j,0s,0n),+0s,2299161j)>>,
  #    #<Order:0x00005a49d68ad3b8 @id=3,
  #      @date=#<Date: 2019-10-10 ((2458767j,0s,0n),+0s,2299161j)>>,
  #    #<Order:0x00005a49d68ad138 @id=4,
  #      @date=#<Date: 2019-10-10 ((2458767j,0s,0n),+0s,2299161j)>>,
  #    #<Order:0x00005a49d68aceb8 @id=5,
  #      @date=#<Date: 2019-10-12 ((2458769j,0s,0n),+0s,2299161j)>>] 

и даты начала и окончания:

start_date = '2019-10-05'
end_date   = '2019-10-13'

Предположение

Я предполагаю, что:

Date.iso8601(start_date) <= arr.first.date &&
  arr.first.date <= arr.last.date &&
  arr.last.date <= Date.iso8601(end_date)
  #=> true

Нет необходимости сортировать элементы arr по дате.

Код

h = (start_date..end_date).each_with_object({}) { |d,h| h[d] = d + ':' }
arr.each do |inst|
  date = inst.date.strftime('%Y-%m-%d')
  h[date] += "#{h[date][-1] == ':' ? '' : ','} id #{inst.id}"
end
h.values
  #=> ["2019-10-05:",
  #    "2019-10-06:",
  #    "2019-10-07: id 1",
  #    "2019-10-08: id 2",
  #    "2019-10-09:",
  #    "2019-10-10: id 3, id 4",
  #    "2019-10-11:",
  #    "2019-10-12: id 5",
  #    "2019-10-13:"] 

Пояснение

Первый шаг - создание хеша h:

h = (start_date..end_date).each_with_object({}) { |d,h| h[d] = d + ':' }
  #=> {"2019-10-05"=>"2019-10-05:", "2019-10-06"=>"2019-10-06:",
  #    "2019-10-07"=>"2019-10-07:", "2019-10-08"=>"2019-10-08:",
  #    "2019-10-09"=>"2019-10-09:", "2019-10-10"=>"2019-10-10:",
  #    "2019-10-11"=>"2019-10-11:", "2019-10-12"=>"2019-10-12:",
  #    "2019-10-13"=>"2019-10-13:"} 

Теперь мы пройдемся по элементам inst (экземпляры Order) из arr, и для каждого из них будет изменено значение ключа в h, равное inst.date, преобразованному в строку:

arr.each do |inst|
  date = inst.date.strftime('%Y-%m-%d')
  h[date] += "#{h[date][-1] == ':' ? '' : ','} id #{inst.id}"
end

В результате:

h #=> {"2019-10-05"=>"2019-10-05:",
  #    "2019-10-06"=>"2019-10-06:",
  #    "2019-10-07"=>"2019-10-07: id 1",
  #    "2019-10-08"=>"2019-10-08: id 2",
  #    "2019-10-09"=>"2019-10-09:",
  #    "2019-10-10"=>"2019-10-10: id 3, id 4",
  #    "2019-10-11"=>"2019-10-11:",
  #    "2019-10-12"=>"2019-10-12: id 5",
  #    "2019-10-13"=>"2019-10-13:"} 

Осталось только извлечь значения хеша h:

h.values
  #=> ["2019-10-05:",
  #    "2019-10-06:",
  #    "2019-10-07: id 1",
  #    "2019-10-08: id 2",
  #    "2019-10-09:",
  #    "2019-10-10: id 3, id 4",
  #    "2019-10-11:",
  #    "2019-10-12: id 5",
  #    "2019-10-13:"] 
0 голосов
/ 16 октября 2019

Я бы сделал смесь обоих:

# rearange your array of hashes into a hash with [date, ids] pairs
orders_by_date = {}
orders.each do |id, date|
  orders_by_date[date] ||= []
  orders_by_date[date] << id
end

# iterate over the range and check if the previous hash has the key
date_range.each do |date|
  date_s = date.strftime('%Y-%m-%d')
  date_ids = orders_by_date.fetch(date_s, []).map { |x| "id: #{x}" }.join(', ')
  puts "#{date_s}: #{date_ids}"
end
0 голосов
/ 16 октября 2019

Попробуйте group_by. Вы можете найти документацию по https://apidock.com/ruby/Enumerable/group_by

grouped_orders = orders.group_by{|ords| ords[:date]}
(start_date..end_date).each do |order_date|
  puts order_date
  grouped_orders.fetch(order_date).map{|m| puts m.id}
end
0 голосов
/ 16 октября 2019

Я бы начал с чего-то вроде этого:

  1. Группировка массива заказов по дате: lookup = orders.group_by(:date)
  2. Итерации по диапазону дат, использование даты в качестве ключа в lookup, так что, по крайней мере, вам не нужно повторно проходить массив orders.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...