【问题标题】:Is this the most efficient algorithm to calculate the cross product of two sets of numbers?这是计算两组数字叉积的最有效算法吗?
【发布时间】:2014-12-12 11:32:19
【问题描述】:

这个问题已经被问到更一般的上下文。但是,在这种特定的情况下:

open System.Collections.Generic

#time
// s1 and s2 are both two ordered sets of numbers
// i.e. the both s1 and s2 do not contain duplicates
let inline calcSeq op (s1: 'a list) (s2: 'a list) =
    let m = new HashSet<'a>()

    for x1 in s1 do
        for x2 in s2 do
            m.Add(x1 |> op <| x2) |> ignore
    m
    |> Seq.toList

let inline multLists s1 s2  = calcSeq (*) s1 s2
let inline divLists s1 s2   = calcSeq (/) s1 s2
let inline sumLists s1 s2   = calcSeq (+) s1 s2
let inline subtrLists s1 s2 = calcSeq (-) s1 s2

作为两组数字的叉积结果,这是计算一组数字的最有效方法吗?

显然性能是 O(s1 |> Seq.lenght, s2 |> Seq.length)。所以表现是这样的:

> multLists [1..5] [1..10];;
Real: 00:00:00.002, CPU: 00:00:00.015, GC gen0: 0, gen1: 0, gen2: 0
val it : int list =
  [1; 2; 3; 4; 5; 6; 7; 8; 9; 10; 12; 14; 16; 18; 20; 15; 21; 24; 27; 30; 28;
   32; 36; 40; 25; 35; 45; 50]
> multLists [1..1000] [1..5000];;
Real: 00:00:02.052, CPU: 00:00:02.121, GC gen0: 100, gen1: 9, gen2: 1
val it : int list =
  [1; 2; 3; 4; 5; 6; 7; 8; 9; 10; 11; 12; 13; 14; 15; 16; 17; 18; 19; 20; 21;
   22; 23; 24; 25; 26; 27; 28; 29; 30; 31; 32; 33; 34; 35; 36; 37; 38; 39; 40;
   41; 42; 43; 44; 45; 46; 47; 48; 49; 50; 51; 52; 53; 54; 55; 56; 57; 58; 59;
   60; 61; 62; 63; 64; 65; 66; 67; 68; 69; 70; 71; 72; 73; 74; 75; 76; 77; 78;
   79; 80; 81; 82; 83; 84; 85; 86; 87; 88; 89; 90; 91; 92; 93; 94; 95; 96; 97;
   98; 99; 100; ...]

但也许我可以采取一些巧妙的技巧来加快速度?

请注意,例如,如果你 multLists [1..2] [1..3] 你得到 [1;2;3;4;6],跳过 5,同时将 [1..1000] 与[1..5000] 至少给出 [1..100] 的列表,而不会跳过任何内容。但是,以后还会有重复。

下午如果您愿意否决这个问题,请花点时间解释一下,我可能会学到一些东西。

【问题讨论】:

  • 这不是一个笛卡尔积。笛卡尔积将返回 (1,1)::(1,2)::(1,3)::...
  • @JohnPalmer。我明白了,你打算怎么称呼它?
  • 抱歉,错过了。在性能方面,我认为避免使用ResizeArray 会更快。另外,我认为您可以利用输入数据已排序的事实在性能上击败HashSet,但它会变得非常复杂。
  • 另外x1 |&gt; op &lt;| x2 直觉上听起来比普通的op x1 x2 更糟糕
  • 我也会删除ResizeArray - 只需遍历输入列表。我不认为您可以在不了解操作的情况下进行任何巧妙的优化(在这里,即使输入已排序这一事实也无济于事)。

标签: f# cartesian-product


【解决方案1】:

好吧,就像之前发布的那样,基本上这将有 O(n*m) 的性能,太糟糕了!只是把它挂在我的代码中会给我带来麻烦。

但是,如前所述,这是一个特定的用例场景。我试图实现的是对在两组数字之间执行计算的叉积提出一组可能的答案。所以,实际上,我不需要所有的答案,只需要一个合理的选项列表。

对于我的用例,当您每天开 3 或 4 次处方药,并且每次可以选择 1 到 5 片药片时,您将获得以下范围的可能性来获得每日总数:multiplyLists [ 3;4] [1..5] = [3; 6; 9; 12; 15; 4; 8个; 16; 20]。实际上它比这更复杂一些,但它归结为这个原则。因此,鉴于限制,我想阻止用户认为每天 5 片是可行的选择。

所以,解决方法很琐碎(只是半天没意识到;-():

let maximize n (set: list<_>) =
    let max = set.Length
    if n >= max then set
    else
        let set = set |> Seq.sort
        let nth = max / (n - 1)
        let i = ref 0
        [ for x in set do
            if !i % nth = 0 then
                yield x 
            i := !i + 1 ]


let inline calcSeq op (s1: 'a list) (s2: 'a list) =
    let m = new HashSet<'a>()
    let s1 = s1 |> maximize 100
    let s2 = s2 |> maximize 100
    for x1 in s1 do
        for x2 in s2 do
            m.Add(op x1 x2) |> ignore
    m
    |> Seq.toList

let inline multLists s1 s2  = calcSeq (*) s1 s2
let inline divLists s1 s2   = calcSeq (/) s1 s2
let inline sumLists s1 s2   = calcSeq (+) s1 s2
let inline subtrLists s1 s2 = calcSeq (-) s1 s2

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-12-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多