将不同年龄组合在一起的算法

时间:2011-07-14 00:12:23

标签: algorithm

假设我们有一系列年龄组和每个年龄组的人数数组

例如:

Ages = ("1-13", "14-20", "21-30", "31-40", "41-50", "51+")
People = (1, 10, 21, 3, 2, 1)

如果每组中人数少于5人,我希望有一个算法将这些年龄组与以下逻辑组合在一起。到目前为止,我所使用的算法如下:

  1. 从最后一个元素开始(例如,“51+”)你可以将它与下一个组合起来吗? (这里是“41-50”)如果是,请添加数字1 + 2并合并其标签。所以我们得到以下

    Ages = ("1-13", "14-20", "21-30", "31-40", "41+")
    People = (1, 10, 21, 3, 3)
    
  2. 再次拿到最后一个(这里是“41+”)。你能和下一组(31-40)合并吗?答案是肯定的,所以我们得到:

    Ages = ("1-13", "14-20", "21-30", "31+")
    People = (1, 10, 21, 6)
    
  3. 由于31+组现在有6名成员,我们无法将其合并到下一组。

  4. 我们不能将“21-30”折叠成下一个“14-20”

  5. “14-20”也有10个人(> 5个),所以我们不做任何事情

  6. 第一个(“1-13”),因为我们只有一个人,它是我们将它与下一个组“14-20”组合的最后一组,并得到以下

    Ages = ("1-20", "21-30", "31+")
    People = (11, 21, 6)
    
  7. 我有一个这种算法的实现,它使用许多标志来跟踪是否有任何数据被更改,并且它在两个数组上进行了多次传递以完成此任务。

    我的问题是,如果你知道做同样事情的任何有效方法吗?任何可以帮助的数据结构?任何可以帮助我做同样事情而不做太多记账的算法都会很棒。

    更新 一个根本的例子是(5,1,5)

    在第一遍中它变为(5,6)[将右边的那个折叠成中间的那个]

    然后我们有(5,6)。我们不能触及6,因为它大于我们的门槛:5。所以我们转到下一个(这是左边5的元素),因为它小于或等于5,因为它是左边的最后一个,我们将它与右边的那个组合在一起。所以我们终于得到了(11)

3 个答案:

答案 0 :(得分:2)

这是一个从左到右合并算法的OCaml解决方案:

let close_group acc cur_count cur_names =
  (List.rev cur_names, cur_count) :: acc

let merge_small_groups mini l =
  let acc, cur_count, cur_names =
    List.fold_left (
      fun (acc, cur_count, cur_names) (name, count) ->
        if cur_count <= mini || count <= mini then
          (acc, cur_count + count, name :: cur_names)
        else
          (close_group acc cur_count cur_names, count, [name])
    ) ([], 0, []) l
  in
  List.rev (close_group acc cur_count cur_names)

let input = [
  "1-13", 1;
  "14-20", 10;
  "21-30", 21;
  "31-40", 3;
  "41-50", 2;
  "51+", 1
]

let output = merge_small_groups 5 input
(* output = [(["1-13"; "14-20"], 11); (["21-30"; "31-40"; "41-50"; "51+"], 27)] *)

如您所见,从左到右合并的结果可能不是您想要的结果。

根据目标,合并总和最小的连续元素对并迭代直到所有计数都高于最小值为5可能更有意义。

答案 1 :(得分:1)

这是我的scala方法 我们从两个列表开始:

val people = List (1, 10, 21, 3, 2, 1)
val ages = List ("1-13", "14-20", "21-30", "31-40", "41-50", "51+")

并将它们组合成一种映射:

val agegroup = ages.zip (people)

定义合并两个字符串的方法,描述(开放式)间隔。第一个参数是“51+”中的+,如果有的话。

/**
   combine age-strings 
   a+  b-c => b+
   a-b c-d => c-b
*/
def merge (xs: String, ys: String) = {
  val xab = xs.split ("[+-]")
  val yab = ys.split ("-")
  if (xs.contains ("+")) yab(0) + "+" else 
  yab (0) +  "-" + xab (1)
}    

这是真正的工作:

/**
   reverse the list, combine groups < threshold. 
*/
def remap (map: List [(String, Int)], threshold : Int) = {

  def remap (mappings: List [(String, Int)]) : List [(String, Int)] = mappings match {
    case           Nil =>      Nil 
    case x ::      Nil => x :: Nil 
    case x :: y :: xs  => if (x._2 > threshold) x :: remap (y :: xs) else 
      remap ((merge (x._1, y._1), x._2 + y._2) :: xs) }

  val nearly = (remap (map.reverse)).reverse

  // check for first element 
  if (! nearly.isEmpty && nearly.length > 1 && nearly (0)._2 < threshold) {
    val a = nearly (0)
    val b = nearly (1) 
    val rest = nearly.tail.tail 
    (merge (b._1, a._1), a._2 + b._2) :: rest
  } else nearly
}

和调用

println (remap (agegroup, 5))

结果:

scala> println (remap (agegroup, 5))
List((1-20,11), (21-30,21), (31+,6))

结果是对,年龄组和成员数量的列表。

我想主要部分很容易理解:有3种基本情况:一个空列表,不能分组,一个列表,一个解决方案本身,以及多个元素。

如果第一个元素(我在开头反转列表,从结尾开始)大于5(6,无论如何),产生它,并继续其余 - 如果不是,将它与第二个组合,并采用这个组合元素,并以递归方式与其余元素调用它。

如果组合了2个元素,则调用字符串的merge-method。

在恢复地图后重新映射地图,结果再次恢复。现在必须检查第一个元素并最终合并。

我们已经完成了。

答案 2 :(得分:0)

我认为一个好的数据结构将是成对的链表,其中每对包含年龄跨度和计数。使用它,您可以轻松地遍历列表,并在O(1)中加入两对。