为IF编写可变数量的参数

时间:2015-01-19 05:04:13

标签: julia

我正在尝试编写一个可以解决此问题的任何常规版本的函数:

  

如果我们列出10以下的所有自然数是3或5的倍数,我们得到3,5,6和9.这些倍数的总和是23.

     

查找低于1000的3或5的所有倍数的总和。

我解决这个问题的方法是:

multiples = Array(Int, 0)
[ (i % 3 == 0 || i % 5 == 0) && push!(multiples, i) for i in 1:1000 ]
sum(multiples)

我想编写一个函数,它将采用多个数组(在本例中为[3,5])和最终数字(在本例中为1000)。关键是阵列可以由任意多个数组成,而不仅仅是两个(例如,[3,5,6])。然后,该函数应为每个i % N == 0运行N

如何最有效地完成这项工作?这可能涉及元编程吗? (代码不必是列表推导格式。)

谢谢!

2 个答案:

答案 0 :(得分:3)

首先突然出现在我的脑海中,使用模数除法和功能样式:

v1(N,M) = sum(filter(k->any(j->k%j==0,M), 1:N))

但我想调查一些替代方案,因为这有两个问题:

  1. 两个级别的匿名函数,Julia并没有很好地优化(还)。
  2. 从范围中创建一个临时数组,然后求和。
  3. 所以这是最明显的选择,单线的 C风格版

    function v2(N,M)
        sum_so_far = 0
        for k in 1:N
            for j in M
                if k%j==0
                    sum_so_far += k
                    break
                end
            end
        end
        return sum_so_far
    end
    

    但后来我又考虑了一下,并记得在某处读取模数除法是一个缓慢的操作。我想看看IntSet的执行情况 - 一个专门用于整数的集合。所以这是另一个单行, IntSet s,没有使用任何模块划分,以及功能风格!

    v3(N,M) = sum(union(map(j->IntSet(j:j:N), M)...))
    

    将地图扩展为for循环并重复将union!应用于单个IntSet并不是更好,因此我不会在此处包含此内容。打破这个:

    • IntSet(j:j:N)是j和N之间的所有j的倍数
    • j->IntSet(j:j:N)是一个匿名函数,返回IntSet
    • map(j->IntSet(j:j:N), M)将该函数应用于M中的每个j,并返回Vector{IntSet}
    • ..." splats"向量输入union
    • 的参数
    • union创建一个IntSet,它是其参数的并集 - 在本例中,是M的所有数字的倍数
    • 然后我们总结它完成

    我用

    对这些进行了基准测试
    N,M = 10000000, [3,4,5]
    

    给你

    • 单行:2.857292874 seconds (826006352 bytes allocated, 10.49% gc time)
    • C风格:0.190581908 seconds (176 bytes allocated)
    • IntSet no modulo:0.121820101 seconds (16781040 bytes allocated)

    所以你绝对可以用更高级别的对象击败C风格的代码 - 模数我觉得很贵!关于无模数的巧妙之处在于它非常容易并行化:

    addprocs(3)
    @everywhere worker(j,N) = IntSet(j:j:N)
    v4(N,M) = sum(union(pmap(j->worker(j,N),M)...))
    @show v4(1000, [3,5])
    @time v3(1000000000,[3,4,5]);  # bigger N than before
    @time v4(1000000000,[3,4,5]);
    

    给出了

    elapsed time: 12.279323079 seconds (2147831540 bytes allocated, 0.94% gc time)
    elapsed time: 10.322364457 seconds (1019935752 bytes allocated, 0.71% gc time)
    

    这不是更好,但我认为是这样。

答案 1 :(得分:1)

好的,这是我的最新答案。

根据@IainDunning答案中的基准,要击败的方法是他的v2。我的下面的方法似乎很多更快,但我不够聪明,不能将它推广到长度大于2的输入向量。一位优秀的数学家应该能够改进我的答案。< / p>

快速直觉:对于length(M)=2的情况,问题会减少到M[1]N的所有倍数之和加到{{1}的所有倍数之和最多M[2],其中为了避免重复计算,我们需要将N的所有倍数之和减去M[1]*M[2]。可以为N实现类似的算法,但重复计算问题会很快变得非常复杂很多。我怀疑这种通用算法肯定会存在(这是组合学领域一直存在的问题),但我不知道这一点。

以下是我的方法(M > 2)与f1的测试代码:

v2

时间是:

function f1(N, M)
  if length(M) > 2
    error("I'm not clever enough for this case")
  end
  runningSum = 0
  for c = 1:length(M)
    runningSum += sum(M[c]:M[c]:N)
  end
  for c1 = 1:length(M)
    for c2 = c1+1:length(M)
      temp1 = M[c1]*M[c2]
      runningSum -= sum(temp1:temp1:N)
    end
  end
  return(runningSum)
end    

function v2(N, M)
  sum_so_far = 0
  for k in 1:N
    for j in M
      if k%j==0
        sum_so_far += k
        break
      end
    end
  end
  return sum_so_far
end

f1(1000, [3,5])
v2(1000, [3,5])

N = 10000000
M = [3,5]

@time f1(N, M)
@time v2(N, M)

抱歉,这是一个有趣的问题,但我担心我必须重新开始工作:-)如果我有机会,我会稍后再回来查看...