获取列表列表的笛卡尔积,长度不等,并且对每个项目应用一个函数('a->'b list)

时间:2018-09-30 15:01:39

标签: algorithm recursion f#

我已经为此苦了一段时间了。虽然我有一些行之有效的命令式方法,但我还是决定重新设计此部分:

  • 获取列表列表
  • 列出每个子列表的第一项,然后再列出第一项,但是从最后一个子列表开始,第二项,然后是第三项,直到最后一个列表用尽,对N-做相同的操作1个子列表,基本上给出了所有这些列表的一种产物

    换句话说:[abc][FG][98]的计算方式应类似于(将函数f应用于每个项目,逗号用于可读性):[aF9,aF8,aG9,aG8,bF9,bF8,bG9,bG8 ,cF9,cF8,cG9,cG8]

  • 平整化结果并将其应用于结果的每个项目,结果本身将返回列表

  • 然后将这些列表展平以返回单链接列表
  • 当子列表为空(称为E)时,仅评估子列表0 .. E-1(在我的示例中不是)

这是一个工作示例,清楚地表明了其递归性质,但显然仅深入到了三个列表:

let rec nestedApply f inp =
    match inp with
    | [] -> []
    | [x] -> x |> List.collect f
    | [x;y] -> 
        x
        |> List.collect (fun a -> y |> List.collect (fun b -> [a;b]))
        |> List.collect f
    | [x;y;z] -> 
        x
        |> List.collect (fun a -> y |> List.collect (fun b -> z |> List.collect (fun c -> [a;b;c])))
        |> List.collect f
    | head::tail ->
        // ??? I gave the following several attempts but couldn't get it right
        nestedApply f tail
        |> List.collect (fun a -> a::head)
        |> List.collect f

我更喜欢不会炸毁堆栈的解决方案。最终,我需要将其用于延迟评估,因此我可能会求助于序列,但是对于列表,我认为该算法将是最容易考虑的。

示例:nestedApply (fun a -> [a]) [[1 .. 5];[6;7];[11;12]]

示例输出:

[1; 6; 11; 1; 6; 12; 1; 7; 11; 1; 7; 12; 2; 6; 11; 2; 6; 12; 2; 7; 11; 2; 7;
 12; 3; 6; 11; 3; 6; 12; 3; 7; 11; 3; 7; 12; 4; 6; 11; 4; 6; 12; 4; 7; 11; 4;
 7; 12; 5; 6; 11; 5; 6; 12; 5; 7; 11; 5; 7; 12]

顺便说一句,尽管这不是笛卡尔乘积,但它看起来像是一种相当“正常”的算法,因此与之最接近的典型的知名算法是什么?

2 个答案:

答案 0 :(得分:3)

(操作说明:这个高级答案导致我在另一个答案中实现了,谢谢Vivek!)

  • 您拥有的数据集称为N-ary树。

  • 在这里,每个节点/列表元素的子节点数可能在0 <= children <= N之间。

算法步骤:

  • 浏览lists的第一个列表。
  • depth first search应用于列表中的每个元素。
  • 使子元素返回自己为list
  • 在父级创建一个新的空lists并将父元素添加到每个返回的子列表中,并将其添加到lists
  • 返回lists

伪代码:

function possibleLists(curr_list){
  my_new_lists = [];
  for each element in curr_list:
     child_lists = possibleLists(element)
     for each child_list in child_lists:
         child_list.add(element)
         my_new_lists.add(child_list)    
     if child_lists.size() == 0: // needed if there were no further deep levels than the level of curr_list elements
         my_new_lists.add(new List().add(child_list)) // you can return current instance to have this kind of chaining.      

  return my_new_lists;
}

注意:如果要实现尾部递归,则必须将访问的元素的路径作为参数list传递给参数中的下一个递归调用,以附加到该元素中子元素。

P.S-我不是F#编码员,所以可以最大程度地帮助您使用伪代码。

答案 1 :(得分:2)

感谢@ vivek_23在N进制树上提供了指针,我读了一些关于树遍历的文章,等等,这并不完全是关于(如果我错了,请纠正我),但是带我去一个简单的,我相信是优雅的解决方案:

let rec nestedApply f acc inp =
    match inp with
    | [] -> f acc
    | head::tail -> 
        [
            for x in head do
                yield! nestedApply f (x::acc) tail
        ]

在这种情况下,apply函数f作用于每次迭代具有相同长度的小子列表,但是对于我的特殊情况,这并不重要(而且,如果apply-function不需要关心子集的顺序)。要获得与原始问题完全相同的行为,请按以下方式使用它:

> nestedApply List.rev [] [[1 .. 5];[6;7];[11;12]];;
val it : int list =
  [1; 6; 11; 1; 6; 12; 1; 7; 11; 1; 7; 12; 2; 6; 11; 2; 6; 12; 2; 7; 11; 2; 7;
   12; 3; 6; 11; 3; 6; 12; 3; 7; 11; 3; 7; 12; 4; 6; 11; 4; 6; 12; 4; 7; 11; 4;
   7; 12; 5; 6; 11; 5; 6; 12; 5; 7; 11; 5; 7; 12]

稍微整洁的解决方案隐藏了累加器:

let nestedApply f inp =
    let rec innerLoop acc inp =
        match inp with
        | [] -> f acc
        | head::tail -> 
            [
                for x in head do
                    yield! innerLoop (x::acc) tail
            ]

    innerLoop [] inp