假设我们有一系列年龄组和每个年龄组的人数数组
例如:
Ages = ("1-13", "14-20", "21-30", "31-40", "41-50", "51+")
People = (1, 10, 21, 3, 2, 1)
如果每组中人数少于5人,我希望有一个算法将这些年龄组与以下逻辑组合在一起。到目前为止,我所使用的算法如下:
从最后一个元素开始(例如,“51+”)你可以将它与下一个组合起来吗? (这里是“41-50”)如果是,请添加数字1 + 2并合并其标签。所以我们得到以下
Ages = ("1-13", "14-20", "21-30", "31-40", "41+")
People = (1, 10, 21, 3, 3)
再次拿到最后一个(这里是“41+”)。你能和下一组(31-40)合并吗?答案是肯定的,所以我们得到:
Ages = ("1-13", "14-20", "21-30", "31+")
People = (1, 10, 21, 6)
由于31+组现在有6名成员,我们无法将其合并到下一组。
我们不能将“21-30”折叠成下一个“14-20”
“14-20”也有10个人(> 5个),所以我们不做任何事情
第一个(“1-13”),因为我们只有一个人,它是我们将它与下一个组“14-20”组合的最后一组,并得到以下
Ages = ("1-20", "21-30", "31+")
People = (11, 21, 6)
我有一个这种算法的实现,它使用许多标志来跟踪是否有任何数据被更改,并且它在两个数组上进行了多次传递以完成此任务。
我的问题是,如果你知道做同样事情的任何有效方法吗?任何可以帮助的数据结构?任何可以帮助我做同样事情而不做太多记账的算法都会很棒。
更新 一个根本的例子是(5,1,5)
在第一遍中它变为(5,6)[将右边的那个折叠成中间的那个]
然后我们有(5,6)。我们不能触及6,因为它大于我们的门槛:5。所以我们转到下一个(这是左边5的元素),因为它小于或等于5,因为它是左边的最后一个,我们将它与右边的那个组合在一起。所以我们终于得到了(11)答案 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)中加入两对。