将整数映射到成分数

时间:2018-04-22 23:55:01

标签: ruby algorithm math

我有一个数字数组,表示某些实体需要完成某些处理的次数:

array = [20, 30, 10, 7, 8, 5]

和一个数字,表示实际完成处理的总次数:

amount_processed = 80

我想构建一个哈希,其键是数组中的数字,其值表示成功处理80的次数。例如:

hash = {}
index = 0
until amount_processed <= 0 || index == array.count
  mapped_amount = [array[index],amount_processed].min
  hash[array[index]] = mapped_amount
  amount_processed -= mapped_amount
  index += 1
end

这种情况下的输出是:

{20 => 20, 30 => 30, 10 => 10, 7 => 7, 8 => 8, 5 => 5}

如果是amount_processed = 65,我会得到:

{20 => 20, 30 => 30, 10 => 10, 7 => 5}

我想映射amount_processed,以便它总是首选一个映射,其中所有给定的键都用完了。例如,对于amount_processed = 65,输出应为:

{20 => 20, 30 => 30, 10 => 10, 5 => 5} # skipped 7 and 8 entirely

如果有不同的可能输出,则两者都有效,我无动于衷。即,如果amount_processed = 60,则以下2中的任何一个都是有效的

{20 => 20, 30 => 30, 10 => 10}
{30 => 30, 10 => 10, 7 => 7, 8 => 8, 5 => 5}

成功实现上述成果的代码如下所示

hash = {}
index = 0
size = array.size
until amount_processed <= 0
    if index == size * 2
        hash["notes"] = "attempting to go beyond 2nd iteration, i.e., there is still amount_processed left but nothing more in the array to map it to"
        return hash
    elsif index >= size
    # we've already looped through everything to find something that fully matches, and still have leftover amounts to be mapped, so now start over, and just go in order to fill in whatever's available
    pseudo_index = index - size # allows us to map to original array once index is on second iteration
    # was that particular one skipped or not
    if hash[array[pseudo_index]].present?
        # it wasn't skipped in earlier go around, so skip it NOW
    else
        mapped_amount = [array[pseudo_index],amount_processed].min
        hash[array[pseudo_index]] = mapped_amount
        amount_processed -= mapped_amount
      end
  else
    if amount_processed < array[index]
      # we don't want a partial map, so just don't, unless it's last resort
    else
      mapped_amount = [array[index],amount_processed].min
      hash[array[index]] = mapped_amount
      amount_processed -= mapped_amount
    end
  end
  index += 1
end
return hash

我的代码是否始终有效,因为任何array/amount_processed组合都可以创建一个哈希,该哈希首先匹配多个完全匹配的数组&#34; as然后创建不完整的匹配?

4 个答案:

答案 0 :(得分:1)

简单回答:在某些情况下,您的代码会失败

amount_processed = 62为例。一个正确的解决方案是{20,30,7,5},但您的代码将返回{20,30,10}

原因是原始代码部分将选择数组值,如果它小于amount_processed变量,则会导致选择不必要的数组值。

建议:重新思考子集和问题的逻辑,而不是重用原始的贪婪算法

答案 1 :(得分:0)

正如@iGian在他的回答中指出的那样,可以通过所有组合来找到解决方案,但有时可能会尝试猜测最终数组的大小,所以我创建了以下代码,试图做以一种非常天真的方式:

@amount_processed = 62
@iterations = 0

def found_solution given_array
  puts "Found a solution"
  puts given_array.inspect
  puts given_array.sum == @amount_processed ? "Exact solution" : "Aproximate solution"
  puts "After #{@iterations} iterations"
  exit 0
end

hash = {}
solution = []
array = [20, 30, 10, 7, 8, 5]

found_solution(array) if array.sum <= @amount_processed
found_solution([array.min]) if array.min >= @amount_processed

min_size_array = 0
array.size.times do |i|
  @iterations += 1
  min_size_array = i
  sum_of_maxs = array.max(min_size_array).sum
  found_solution(array.max(min_size_array)) if sum_of_maxs == @amount_processed
  break if array.max(min_size_array).sum > @amount_processed
end

max_value_array = array.max
max_size_array = min_size_array
(min_size_array..array.size).each do |i|
  @iterations += 1
  sum_of_mins = array.min(i).sum
  found_solution(array.min(i)) if sum_of_mins == @amount_processed
  break if sum_of_mins > @amount_processed
  max_size_array = i
end

max_value_in_array = array.max
# Run through all the combinations within the given sizes until a solution is found, including iterations for 
# non exact solutions, which can at most be as much as 'max_value_in_array' away from the amount_processed
(0..max_value_in_array).each do |difference_to_solution|
  (min_size_array..max_size_array).each do |size|
    array.combination(size).each do |current_combination|
      @iterations += 1
      if current_combination.sum == (@amount_processed - difference_to_solution)
        found_solution(current_combination) unless difference_to_solution > 0
        # There is a difference, so we need to find the min_value that is not in the current_combination
        # and add it to the approximate solution
        duplicate_array = array.dup
        current_combination.each do |x|
          duplicate_array.slice!(duplicate_array.index(x))
        end
        min_value_not_in_current_combination = duplicate_array.min
        found_solution(current_combination + [min_value_not_in_current_combination])
      end
    end
  end
end

答案 2 :(得分:0)

您的问题与Knapsack problem有一些相似之处,正如其他人所说,它可能没有最佳的算法解决方案。您的“适合”功能有一些额外的要求,似乎使它更具挑战性。以不同的顺序处理数组元素可能会产生更优化的解决方案,因此我认为您必须尝试所有这些并选择最符合您标准的解决方案。

我的解决方案试图通过所有排列来强制推出最佳解决方案。它利用Ruby的哈希标识(例如{ 1=>2, 2=>3 }等于{ 2=>3, 1=>2 })来删除重复的候选解决方案。我尝试将fit函数与您的要求对齐。

def solve(*args)
  best_fit(compute_solutions(*args))
end

def best_fit(solutions)
  # prefer unique solutions with no partial values
  preferred_solutions, alternative_solutions =
    solutions.uniq.partition { |solution| solution.all? { |k, v| k == v } }

  if preferred_solutions.empty?
    # prefer unique solutions with "fullest" partial values
    preferred_solutions = alternative_solutions
      .group_by { |solution| solution.detect { |k, v| v < k and break k } }
      .min_by(&:first)
      .last
  end

  # prefer shortest solution matching above criteria
  preferred_solutions
    .group_by(&:length)
    .min_by(&:first)
    .last
    .first
end

def compute_solutions(array, *args)
  return enum_for(__callee__, array, *args).to_a unless block_given?

  array.permutation do |permuted_array|
    yield compute_solution(permuted_array, *args)
  end
end

def compute_solution(array, amount_processed)
  return enum_for(__callee__, array, amount_processed).to_h unless block_given?

  array.each do |amount|
    break if amount_processed.zero?
    value = [amount, amount_processed].min
    yield [amount, value]
    amount_processed -= value
  end
end

p solve([20, 30, 10, 7, 8, 5], 80) == { 20=>20, 30=>30, 10=>10, 7=>7, 8=>8, 5=>5 } # => true
p solve([20, 30, 10, 7, 8, 5], 79) == { 20=>20, 30=>30, 10=>10, 7=>7, 8=>8, 5=>4 } # => true
p solve([20, 30, 10, 7, 8, 5], 65) == { 20=>20, 30=>30, 10=>10, 5=>5 } # => true
p solve([20, 30, 10, 7, 8, 5], 62) == { 20=>20, 30=>30, 7=>7, 5=>5 } # => true
p solve([20, 30, 10, 7, 8, 5], 60) == { 20=>20, 30=>30, 10=>10 } # => true

答案 3 :(得分:-1)

也许这就是你要找的逻辑? 如果没有匹配并且返回一个空数组

,它将通过所有组合
array = [20, 30, 10, 7, 8, 5]
amount_processed = 65

go = true
(array.size + 1).times do |n|
    combinations = array.combination(n).to_a
    combinations.each do |combination|
        sum = combination.inject(0){|sum,x| sum + x }
        if sum == amount_processed
            then
                p combination
                go = false
        end
        break if go == false
    end
    break if go == false
end