在内存和执行时间有限的情况下迭代大数组

Iterating over big arrays with limited memory and time of execution

我在使用 Ruby 通过一些测试时遇到问题,这些测试使数组太大并且 return 出错。

Solution.rb: failed to allocate memory (NoMemoryError)

两次都没有通过

问题是关于安排会议的。该方法按顺序接收两个参数:一个矩阵,包含投资者可以在公司见面的所有第一天,以及一个矩阵,包含所有最后几天。

例如:

firstDay = [1,5,10]
lastDay = [4,10,10]

这表明第一位投资者将能够在 1..4 日之间找到自己,第二位投资者将能够在 5..10 日之间找到自己,最后一位投资者将在 10..10 日之间找到自己。[=24] =]

我需要 return 公司将服务的最大数量的投资者。在这种情况下,所有这些都可以参加,第 1 天第一个,第 5 天第二个,第 10 天最后一个。

到目前为止,代码运行正常,但在至少1000个投资者的一些隐藏测试中,出现了我之前提到的错误。

Ruby 中是否有处理此问题的最佳实践?

我当前的代码是:

def countMeetings(firstDay, lastDay)
    GC::Profiler.enable
    GC::Profiler.clear

    first = firstDay.sort.first
    last = lastDay.sort.last
    available = []
    
    #Construct the available days for meetings
    firstDay.each_with_index do |d, i|
        available.push((firstDay[i]..lastDay[i]).to_a)
    end
    available = available.flatten.uniq.sort
    investors = {}
    attended_day = []
    attended_investor = []

    #Construct a list of investor based in their first and last days
    firstDay.each_index do |i|
        investors[i+1] = (firstDay[i]..lastDay[i]).to_a
    end

    for day in available
        investors.each do |key, value|
            next if attended_investor.include?(key)
            if value.include?(day)
                next if attended_day.include?(day)
                attended_day.push(day)
                attended_investor.push(key)
            end
        end
    end

    attended_investor.size
end

据我所知使用 Lazy,我逃脱了 MemoryError,但我开始收到运行时错误:

Your code was not executed on time. Allowed time: 10s

我的代码是这样的:

def countMeetings(firstDay, lastDay)

    loop_size = firstDay.size
    first = firstDay.sort.first
    last = lastDay.sort.last

    daily_attendance = {}
   
    (first..last).each do |day|
        for ind in 0...loop_size
            (firstDay[ind]..lastDay[ind]).lazy.each do |investor_day|
                next if daily_attendance.has_value?(ind)
                if investor_day == day
                    daily_attendance[day] = ind
                end
            end
        end
    end

    daily_attendance.size
end

而且它经历了很少有投资者的案例。我想过用multi-thread,代码变成了下面这样:

 def countMeetings(firstDay, lastDay)

    loop_size = firstDay.size
    first = firstDay.sort.first
    last = lastDay.sort.last
    threads = []
    daily_attendance = {}
   
    (first..last).lazy.each_slice(25000) do |slice|
        slice.each do |day|
            threads << Thread.new do
                for ind in 0...loop_size
                    (firstDay[ind]..lastDay[ind]).lazy.each do |investor_day|
                        next if daily_attendance.has_value?(ind)
                        if investor_day == day
                            daily_attendance[day] = ind
                        end
                    end
                end
            end
        end
    end
    threads.each{|t| t.join}
    daily_attendance.size
end

不幸的是,它又回到了MemoryError

这可以在不消耗比天数范围更多的内存的情况下完成。关键是 避免数组 并尽可能保持 Enumerators

首先,如果范围列表非常大,则传入 Ranges. This both simplifies the method, and it allows it to be Lazy 的 Enumerable,而不是需要转换为范围的笨拙的数组对。它可以从文件中读取,从数据库或 API 中获取,或者由另一个惰性枚举器生成。这使您无需大数组。

这是一个使用范围数组的示例。

p count_meetings([(1..4), (5..10), (10..10)])

或者演示如何将您的 firstDaylastDay 数组转换为范围惰性可枚举...

firstDays = [1,5,10]
lastDays = [4,10,10]
p count_meetings(
  firstDays.lazy.zip(lastDays).map { |first,last|
    (first..last)
  }
)

firstDays.lazy让后面的一切变得懒惰。 .zip(lastDays) 成对遍历两个数组:[1,4]、[5,10] 和 [10,10]。然后我们把它们变成范围。因为它很懒惰,所以只会根据需要映射它们。这避免了创建另一个大数组。


现在已经解决了,我们需要做的就是遍历每个范围并增加当天的出勤率。

def count_meetings(attendee_ranges)
  # Make a Hash whose default values are 0.
  daily_attendance = Hash.new(0)

  # For each attendee
  attendee_ranges.each { |range|
    # For each day they will attend, add one to the attendance for that day.
    range.each { |day| daily_attendance[day] += 1 }
  }
  
  # Get the day/attendance pair with the maximum value, and only return the value.
  daily_attendance.max[1]
end

内存增长受日期范围的限制。如果最早的与会者在第 1 天,最后的与会者在第 1000 天,daily_attendance 就是 1000 个条目,这对于会议来说是很长的时间。

既然你已经构建了整个哈希,为什么要浪费它呢?写一个 returns 全出勤率的函数,另一个提取最大出勤率的函数

def count_meeting_attendance(attendee_ranges)
  daily_attendance = Hash.new(0)

  attendee_ranges.each { |range|
    range.each { |day| daily_attendance[day] += 1 }
  }
  
  return daily_attendance
end

def max_meeting_attendance(*args)
  count_meeting_attendance(*args).max[1]
end

因为这是一个练习,你被那些靠不住的论点所困,我们可以做同样的事情,懒惰地将 firstDayslastDays 压缩在一起,并将它们变成范围。

def count_meeting_attendance(firstDays, lastDays)
  attendee_ranges = firstDays.lazy.zip(lastDays).map { |first,last|
    (first..last)
  }

  daily_attendance = Hash.new(0)
  attendee_ranges.each { |range|
    range.each { |day| daily_attendance[day] += 1 }
  }
  
  return daily_attendance
end