学习红宝石:如何用红宝石解决这个问题?

时间:2019-09-10 00:35:58

标签: ruby-on-rails ruby performance testing

问题陈述

一所大学只有一个旋转门。它既可以用作出口也可以用作入口。不幸的是,有时很多人想穿过旋转栅门,他们的方向可能会有所不同。第i个人在时间[i]到达旋转栅门,并希望在direction [i] = 1时退出大学,或者在direciton [i] = 0时进入大学。人们排成2个队列,一个队列退出,一个进入。它们按到达转闸的时间排序,如果时间相等,则按其索引排序。 如果有人想同时进入大学而又想离开大学,则有以下三种情况:

• If in the previous second the turnstile was not used (maybe it was used before, but not at the previous second), then the person who wants to leave goes first.
• If in the previous second the turnstile was used as an exit, then the person who
wants to leave goes first
• If in the previous second the turnstile was used as an entrance, then the person 
who wants to enter goes first

通过旋转栅门需要1秒

为每个人找到他们通过旋转闸门的时间

该函数必须返回由n个整数组成的数组,当第i个人通过旋转闸门时,index [i]处的值相同。

该函数具有以下参数:

• time: an array of n integers where the value at index i is the time when the 
ith person will came to the turnstile
• direction: an array of n  integers where the value at index i is the direction 
of the ith  person

约束

• 1 <= n <= 105
• 0 <= time[i] <= 109 for 0 <= i <= n – 1
• time[i] <= time[i+1] for 0 <= i <= n - 2
• 0 <= direction [i] <= 1 for 0 <= i <= n – 1

示例:

n = 4

时间= [0,0,1,5]

方向= [0,1,1,0]

输出= [2,0,1,5]

示例2

n = 5

时间= [0,1,1,3,3]

方向= [0,1,0,0,1]

输出= [0,2,1,4,3]


这是我的实际尝试

def getTimes(time, direction)
  persons = time.size - 1
  exits = []
  (0..persons).each do |person|
    if time[person] == time[person + 1]
      exits[person + 1] = time[person] if direction[person + 1] == 1 and direction[person] == 0
    else
      exits[person] = time[person] if direction[person] != direction[person + 1]
    end
  end

  p exits
end

我的回复 [nil,0,1,5]

3 个答案:

答案 0 :(得分:2)

代码

def pass_times(time, direction)
  dir = direction.map { |d| d.zero? ? :ENTER : :EXIT }
  enter, exit = time.each_index.map do |i|
    { person: i, direction: dir[i], arrival_time: time[i] }
  end.partition { |h| h[:direction] == :ENTER }.
      map { |arr| arr.sort_by { |h| h[:arrival_time] } }  
  time.each_index.with_object([]) do |_i,arr|
    last_pass = last_pass_time(arr)
    next_enter_time = enter.empty? ? Float::INFINITY :
      [enter.first[:arrival_time], last_pass].max 
    next_exit_time  = exit.empty?  ? Float::INFINITY :
      [exit.first[:arrival_time], last_pass].max
    h = case
    when last_pass_time(arr) < [next_enter_time, next_exit_time].min
      g = next_exit_time <= next_enter_time ? exit.shift : enter.shift
      add_pass_time(g, g[:arrival_time] + 1) 
    when next_enter_time < next_exit_time
      add_pass_time(enter.shift, last_pass + 1)
    when next_exit_time < next_enter_time
      add_pass_time(exit.shift, last_pass + 1)
    else
      add_pass_time(arr.last[:direction] == :ENTER ?
        enter.shift : exit.shift, last_pass + 1)
    end
    arr << h 
  end.sort_by { |h| h[:person] }.map { |h| h[:pass_time] }
end

def last_pass_time(arr)
  arr.empty? ? -1 : arr.last[:pass_time]
end

def add_pass_time(g, pass_time)
  g.merge(:pass_time => pass_time)
end

示例

time = [0,1,1,3,3]
direction = [0,1,0,0,1]

pass_times(time, direction)
  #=> [1, 3, 2, 5, 4] (compares with [0,2,1,4,3] from the question)

time = [0,0,1,5]
direction = [0,1,1,0]

pass_times(time, direction)
  #=> [3, 1, 2, 6] (compares with [2,0,1,5] from the question)

n = 10
time = n.times.map { rand 13 }
  #=> [3, 5, 1, 12, 7, 3, 9, 3, 9, 0] 
direction = n.times.map { rand 2 }
  #=> [0, 0, 0, 1, 0, 0, 0, 1, 1, 1] 

pass_times(time, direction)
  #=> [5, 7, 2, 13, 8, 6, 11, 4, 10, 1] 

说明

以下是上面第一个示例的步骤。

time = [0,1,1,3,3]
direction = [0,1,0,0,1]

首先,为了提高可读性并避免为了进入和退出而混淆零和一个的问题,我选择使用数组dir代替direction

dir = direction.map { |d| d.zero? ? :ENTER : :EXIT }
  #=> [:ENTER, :EXIT, :ENTER, :ENTER, :EXIT] 

接下来,创建一个哈希数组,其中包含每个接近转弯人的基本信息。

a = time.each_index.map do |i|
  { person: i, direction: dir[i], arrival_time: time[i] }
end
  #=> [{:person=>0, :direction=>:ENTER, :arrival_time=>0},
  #    {:person=>1, :direction=>:EXIT,  :arrival_time=>1},
  #    {:person=>2, :direction=>:ENTER, :arrival_time=>1},
  #    {:person=>3, :direction=>:ENTER, :arrival_time=>3},
  #    {:person=>4, :direction=>:EXIT,  :arrival_time=>3}] 

现在将这些哈希划分为到达和离开。

b = a.partition { |h| h[:direction] == :ENTER }
  #=> [[{:person=>0, :direction=>:ENTER, :arrival_time=>0},
  #     {:person=>2, :direction=>:ENTER, :arrival_time=>1},
  #     {:person=>3, :direction=>:ENTER, :arrival_time=>3}],
  #    [{:person=>1, :direction=>:EXIT,  :arrival_time=>1},
  #     {:person=>4, :direction=>:EXIT,  :arrival_time=>3}]]

现在将到达和离开排序到队列中。

enter, exit = b.map { |arr| arr.sort_by { |h| h[:arrival_time] } }  
enter
  #=> [{:person=>0, :direction=>:ENTER, :arrival_time=>0},
  #    {:person=>2, :direction=>:ENTER, :arrival_time=>1},
  #    {:person=>3, :direction=>:ENTER, :arrival_time=>3}] 
exit 
  #=> [{:person=>1, :direction=>:EXIT, :arrival_time=>1},
  #    {:person=>4, :direction=>:EXIT, :arrival_time=>3}] 

我们现在处理两个队列,将结果保存到数组c中。请注意,time.each_index只会处理队列中的每个人。我本可以写例如dir.size.times

c = time.each_index.with_object([]) do |_i,arr|
  last_pass = last_pass_time(arr)
  next_enter_time = enter.empty? ? Float::INFINITY :
    [enter.first[:arrival_time], last_pass].max 
  next_exit_time  = exit.empty?  ? Float::INFINITY :
    [exit.first[:arrival_time], last_pass].max
  # Note next_enter_time and next_exit_time cannot both be infinite
  h = case
  when last_pass_time(arr) < [next_enter_time, next_exit_time].min
    # if the turnstile was not used in the last second, FIFO with exit
    # having priority in ties
    g = next_exit_time <= next_enter_time ? exit.shift : enter.shift
    add_pass_time(g, g[:arrival_time] + 1) 
  when next_enter_time < next_exit_time
    # next entering person arrives before or when last person passes
    # through turnstile and next exiting person has not yet joined the queue. 
    add_pass_time(enter.shift, last_pass + 1)
  when next_exit_time < next_enter_time
    # next exiting person arrives before or when last person passes
    # through turnstile and next entering person has not yet joined the queue. 
    add_pass_time(exit.shift, last_pass + 1)
  else
    # next entering and exiting persons both arrive arrive before or when last
    # person passes through turnstile. Person who passes next depends if last
    # person passing through the turnstile was entering or exiting.
    add_pass_time(arr.last[:direction] == :ENTER ?
      enter.shift : exit.shift, last_pass +1)
  end
  arr << h 
end
  #=> [{:person=>0, :direction=>:ENTER, :arrival_time=>0, :pass_time=>1},
  #    {:person=>2, :direction=>:ENTER, :arrival_time=>1, :pass_time=>2},
  #    {:person=>1, :direction=>:EXIT,  :arrival_time=>1, :pass_time=>3},
  #    {:person=>4, :direction=>:EXIT,  :arrival_time=>3, :pass_time=>4},
  #    {:person=>3, :direction=>:ENTER, :arrival_time=>3, :pass_time=>5}] 

这表明人0是第一个在时间0到达的人,因此该人立即经过了旋转栅门,并在时间(:pass_time)1出现。人2和1都在时间1到达,当人0从旋转门中出来时。第二个人接下来要经过旋转门(在时间2出现),因为该个人像个人0一样正在进入,因此优先于希望退出的个人1。

人员1接下来经过时间3的旋转门,因为没有人在时间2等待。人员4和3都在时间3到达,因为人员1从旋转门中出来。这次人4具有优先权,因为该人正像人1即将离开一样。人4在时间4从旋转栅门出现,此时人3(最后一个经过的人)进入栅门并在时间5出现。

仍然需要创建一个数组arr,以使arr[i]返回人物i的通过时间。

arr = c.sort_by { |h| h[:person] }.map { |h| h[:pass_time] }
  #=> [1, 3, 2, 5, 4]

示例2导致以下旋转通道通过顺序。

[{:person=>1, :direction=>:EXIT,  :arrival_time=>0, :pass_time=>1},
 {:person=>2, :direction=>:EXIT,  :arrival_time=>1, :pass_time=>2},
 {:person=>0, :direction=>:ENTER, :arrival_time=>0, :pass_time=>3}, 
 {:person=>3, :direction=>:ENTER, :arrival_time=>5, :pass_time=>6}] 

示例3导致以下旋转门通过顺序。

[{:person=>0, :direction=>:ENTER, :arrival_time=>0, :pass_time=>1},
 {:person=>2, :direction=>:ENTER, :arrival_time=>1, :pass_time=>2}, 
 {:person=>1, :direction=>:EXIT,  :arrival_time=>1, :pass_time=>3}, 
 {:person=>4, :direction=>:EXIT,  :arrival_time=>3, :pass_time=>4}, 
 {:person=>3, :direction=>:ENTER, :arrival_time=>3, :pass_time=>5}] 

示例4导致以下旋转通道通过顺序。

[{:person=>9, :direction=>:EXIT,  :arrival_time=>0,  :pass_time=>1},
 {:person=>2, :direction=>:ENTER, :arrival_time=>1,  :pass_time=>2},
 {:person=>7, :direction=>:EXIT,  :arrival_time=>3,  :pass_time=>4}, 
 {:person=>0, :direction=>:ENTER, :arrival_time=>3,  :pass_time=>5}, 
 {:person=>5, :direction=>:ENTER, :arrival_time=>3,  :pass_time=>6}, 
 {:person=>1, :direction=>:ENTER, :arrival_time=>5,  :pass_time=>7}, 
 {:person=>4, :direction=>:ENTER, :arrival_time=>7,  :pass_time=>8}, 
 {:person=>8, :direction=>:EXIT,  :arrival_time=>9,  :pass_time=>10}, 
 {:person=>6, :direction=>:ENTER, :arrival_time=>9,  :pass_time=>11}, 
 {:person=>3, :direction=>:EXIT,  :arrival_time=>12, :pass_time=>13}] 

我将它留给读者来验证这些顺序。

答案 1 :(得分:1)

stackoverflow通常不是代码编写服务。但是,事实证明这是一个有趣的问题。

所以,我为您编写了一些代码。在这里:

list, sepby(id)

     +-------------------------+
     | id    code   cost   tag |
     |-------------------------|
  1. |  1   15342     18     1 |
  2. |  1   16786     32     1 |
  3. |  1   23432     22     1 |
  4. |  1   34234     23     1 |
     |-------------------------|
  5. |  2   15342     12     1 |
  6. |  2   15366     12     1 |
  7. |  2   22223     12     1 |
     |-------------------------|
  8. |  4   15342     64     1 |
  9. |  4   23453    345     1 |
     +-------------------------+

如果您想尝试一下,这里有一个RSpec测试:

def calculate_turnstile_times(time, direction)
  current_time    = 0
  dir = direction.map { |d| d.zero? ? :enter : :exit }
  enter_queue, exit_queue = time.each_index.map do |i|
                              {person: i, time: time[i], direction: dir[i]}
                            end.partition do |h| 
                              h[:direction] == :enter
                            end.map do |arr| 
                              arr.sort_by { |h| h[:time] }
                            end  

  enterer         = enter_queue.shift
  exiter          = exit_queue.shift

  time.each_with_object([]) do |_t, to_return|
    person_to_go        = nil
    enterer           ||= enter_queue.shift
    exiter            ||= exit_queue.shift
    current_time        = [[enterer,exiter].map{|p|p.try(:[],:time)}.compact.min,current_time].max

    enterer_arrived     = enterer && enterer[:time] <= current_time
    exiter_arrived      = exiter && exiter[:time] <= current_time

    person_to_go    = :enterer if enterer_arrived && !exiter_arrived
    person_to_go  ||= :exiter  if exiter_arrived  && !enterer_arrived
    person_to_go  ||= :exiter  if exiter_arrived  && to_return.empty?
    person_to_go  ||= :exiter  if exiter_arrived  && ((to_return.last.try(:[],:time)||-2) != (current_time-1))
    person_to_go  ||= :enterer if enterer_arrived && to_return.last.try(:[],:direction) == :enter
    person_to_go  ||= :exiter  if exiter_arrived

    case person_to_go
    when :exiter
      person_to_go = exiter 
      exiter = nil
    when :enterer 
      person_to_go = enterer
      enterer = nil
    end

    person_to_go[:time] = current_time
    to_return << person_to_go
    current_time += 1

  end.sort_by{|p| p[:person]}.map{|p| p[:time]}
end

哪个给我:

require 'rails_helper'

RSpec.describe "Turnstiles" do
  it "using calculate_turnstile_times" do 
    %i(calculate_turnstile_times).each do |method_sym|
      test_cases.each do |test_case|
        expect(send(method_sym,test_case[0],test_case[1])).to eq(test_case[2])
      end
    end
  end
end

def test_cases
  [
    [[10],[1],[10]],
    [[0,0,0],[0,0,1],[1,2,0]],
    [[0,0,1,5],[0,1,1,0],[2,0,1,5]],
    [[0,0],[0,1],[1,0]],
    [[0,0],[1,1],[0,1]],
    [[0,0],[1,0],[0,1]],
    [[4,4],[0,1],[5,4]],
    [[0,0,0],[0,1,1],[2,0,1]],
    [[0,0,0],[0,0,0],[0,1,2]],
    [[0,0,0,0],[0,1,1,1],[3,0,1,2]],
    [[0,0,0,5],[0,1,1,1],[2,0,1,5]],
    [[0,1,1,3,3],[0,1,0,0,1],[0,2,1,4,3]],
    [[0,1,1,3,3],[0,1,1,0,1],[0,1,2,4,3]],
    [[0,1,1,6,7],[0,1,1,0,1],[0,1,2,6,7]],
    [[0,1,1,3,6],[0,1,1,0,1],[0,1,2,3,6]],
    [[0,0,1,5],[0,1,1,0],[2,0,1,5]],
    [[0,1,1,3,3],[0,1,0,0,1],[0,2,1,4,3]]
  ]
end

def calculate_turnstile_times(time, direction)
  current_time    = 0
  dir = direction.map { |d| d.zero? ? :enter : :exit }
  enter_queue, exit_queue = time.each_index.map do |i|
                              {person: i, time: time[i], direction: dir[i]}
                            end.partition do |h| 
                              h[:direction] == :enter
                            end.map do |arr| 
                              arr.sort_by { |h| h[:time] }
                            end  

  enterer         = enter_queue.shift
  exiter          = exit_queue.shift

  time.each_with_object([]) do |_t, to_return|
    person_to_go    = nil
    enterer           ||= enter_queue.shift
    exiter            ||= exit_queue.shift
    current_time        = [[enterer,exiter].map{|p|p.try(:[],:time)}.compact.min,current_time].max

    enterer_arrived     = enterer && enterer[:time] <= current_time
    exiter_arrived      = exiter && exiter[:time] <= current_time

    person_to_go    = :enterer if enterer_arrived && !exiter_arrived
    person_to_go  ||= :exiter  if exiter_arrived  && !enterer_arrived
    person_to_go  ||= :exiter  if exiter_arrived  && to_return.empty?
    person_to_go  ||= :exiter  if exiter_arrived  && ((to_return.last.try(:[],:time)||-2) != (current_time-1))
    person_to_go  ||= :enterer if enterer_arrived && to_return.last.try(:[],:direction) == :enter
    person_to_go  ||= :exiter  if exiter_arrived

    case person_to_go
    when :exiter
      person_to_go = exiter 
      exiter = nil
    when :enterer 
      person_to_go = enterer
      enterer = nil
    end

    person_to_go[:time] = current_time
    to_return << person_to_go
    current_time += 1

  end.sort_by{|p| p[:person]}.map{|p| p[:time]}
end

答案 2 :(得分:1)

这里是一个专注于数据建模以提高可读性和可追溯性的实现。

class Turnstile
  DIRECTIONS = { IN: 0, OUT: 1 }

  Person = Struct.new(:index, :arrival_time, :direction, :enter_turnstile_time) do
    def has_not_exited?
      !enter_turnstile_time
    end
  end

  State = Struct.new(:time, :entry_queue, :exit_queue, :person_in_turnstile) do
    def build_next
      State.new(time + 1, entry_queue.dup, exit_queue.dup, person_in_turnstile)
    end
  end

  def initialize(time, direction)
    @people = time.map.with_index { |t, ix| Person.new(ix, t, direction[ix], nil) }
    @states = [State.new(-1, Array.new, Array.new, nil)]
  end

  def process
    while @people.any?(&:has_not_exited?)
      @states << process_next_state(@states)
    end

    @people.map(&:enter_turnstile_time)
  end

  def process_next_state(states)
    current_state = states.last.build_next

    log_str = "Time: #{current_state.time}"

    @people.each do |person|
      next unless person.arrival_time == current_state.time

      case person.direction
      when DIRECTIONS[:IN]
        log_str += ", Person #{person.index} queued for entry"
        current_state.entry_queue << person
      when DIRECTIONS[:OUT]
        log_str += ", Person #{person.index} queued for exit"
        current_state.exit_queue << person
      end
    end

    if current_state.person_in_turnstile
      log_str += ", Person #{current_state.person_in_turnstile.index} exited turnstile"

      turnstile_direction_priority = current_state.person_in_turnstile.direction
      current_state.person_in_turnstile = nil
    end

    turnstile_direction_priority ||= DIRECTIONS[:OUT]

    prioritized_queues =
      case turnstile_direction_priority
      when DIRECTIONS[:IN]
        [current_state.entry_queue, current_state.exit_queue]
      when DIRECTIONS[:OUT]
        [current_state.exit_queue, current_state.entry_queue]
      end

    if current_state.person_in_turnstile = prioritized_queues.first.pop || prioritized_queues.last.pop
      current_state.person_in_turnstile.enter_turnstile_time = current_state.time
      log_str += ", Person #{current_state.person_in_turnstile.index} entered turnstile"
    end

    puts log_str
    current_state
  end
end

结果:

2.5.5 :049 > Turnstile.new([0,1,1,3,3], [0,1,0,0,1]).process
Time: 0, Person 0 queued for entry, Person 0 entered turnstile
Time: 1, Person 1 queued for exit, Person 2 queued for entry, Person 0 exited turnstile, Person 2 entered turnstile
Time: 2, Person 2 exited turnstile, Person 1 entered turnstile
Time: 3, Person 3 queued for entry, Person 4 queued for exit, Person 1 exited turnstile, Person 4 entered turnstile
Time: 4, Person 4 exited turnstile, Person 3 entered turnstile
 => [0, 2, 1, 4, 3]