从golang限制键范围内的地图生成的切片中随机选择元素。有O(1)快捷方式吗?

时间:2018-10-02 15:05:54

标签: go optimization hashmap

在我的模拟多粒子进化的程序中,我有一个映射,该映射采用一个关键值pop(种群大小),并返回一个包含该种群的站点的切片:myMap[pop][]int。这些切片通常很大。

在每个演化步骤中,我选择一个随机的种群大小RandomPop。然后,我想随机选择一个人口至少为RandomPop的站点。 sitechosen用于更新我的人口结构,并且我利用第二张地图来有效地更新myMap键。我当前的(慢速)实现看起来像

func Evolve( ..., myMap map[int][]int ,...){

    RandomPop = rand.Intn(rangeofpopulation)+1

    for i:=RandPop,; i<rangeofpopulation;i++{
        preallocatedslice=append(preallocatedslice,myMap[i]...)
    }

    randomindex:= rand.Intn(len(preallocatedslice))
    sitechosen= preallocatedslice[randomindex]

    UpdateFunction(site)

    //reset preallocated slice 
    preallocatedslice=preallocatedslice[0:0]

}

当将值从映射表复制到preallocatedslice时,此代码(显然)遇到了巨大的瓶颈,其中runtime.memmove占用了我87%的CPU使用率。我想知道是否存在O(1)方法来随机选择myMap所指示的切片联合中包含的项,其键值介于0RandomPop之间?我对允许您在任何人都知道的自定义哈希表中使用的程序包持开放态度。并发建议不一定很安全

尝试了其他方法:我以前让我的地图记录了所有站点的值至少为pop,但是这占用了超过10GB的内存,而且很愚蠢。我尝试将指向相关切片的指针存储起来以构成查找切片,但是禁止这样做。我可以总结每个切片的长度,并以此为基础生成一个随机数,然后按长度遍历myMap中的切片,但这比仅保留我的总体cdf进行二进制搜索要慢得多。在上面。二进制搜索速度很快,但即使手动完成更新cdf也为O(n)。我真的很希望滥用哈希表来加快随机选择的速度,并在可能的情况下进行更新

我有一个模糊的想法,就是在构筑某种地图的嵌套结构,这些结构指向它们的内容,并且指向的地图的键要小于它们或其他东西的键。

1 个答案:

答案 0 :(得分:0)

我正在查看您的代码,但有一个问题。 为什么必须将值从地图复制到切片?我的意思是,我认为我正在遵循背后的逻辑...但是我想知道是否有一种方法可以跳过这一步。

所以我们有:

func Evolve( ..., myMap map[int][]int ,...){

    RandomPop = rand.Intn(rangeofpopulation)+1

    for i:=RandPop,; i<rangeofpopulation;i++{
        // slice of preselected `sites`. one of this will be 'siteChosen'
        // we expect to have `n sites` on `preAllocatedSlice`
        // where `n` is the amount of iterations, 
        // ie; n = rangeofpopulation - RandPop
        preallocatedslice=append(preallocatedslice,myMap[i]...) 
    }

    // Once we have a list of sites, we select `one`
    // under a normal distribution every site ha a chance of 1/n to be selected.
    randomindex:= rand.Intn(len(preallocatedslice))
    sitechosen= preallocatedslice[randomindex]

    UpdateFunction(site)
    ...

}

但是如果我们将其更改为:

func Evolve( ..., myMap map[int][]int ,...){

    if len(myMap) == 0 {
        // Nothing to do, print a log! 
        return
    }

    // This variable will hold our site chosen!
    var siteChosen []int

    // Our random population size is a value from 1 to rangeOfPopulation 
    randPopSize := rand.Intn(rangeOfPopulation) + 1

    for i := randPopSize; i < rangeOfPopulation; i++ {
        // We are going to pretend that the current candidate is the siteChosen 
        siteChosen = myMap[i]

        // Now, instead of copying `myMap[i]` to preAllocatedSlice
        // We will test if the current candidate is actually the 'siteChosen` here:

        // We know that the chances for an specific site to be the chosen is 1/n,
        // where n = rangeOfPopulation - randPopSize
        n := float64(rangeOfPopulation - randPopSize)
        // we roll the dice...
        isTheChosenOne := rand.Float64() > 1/n

        if isTheChosenOne {
            // If the candidate is the Chosen site, 
            // then we don't need to iterate over all the other elements.
            break
        }

    }

    // here we know that `siteChosen` is a.- a selected candidate, or 
    // b.- the last element assigned in the loop 
    // (in the case that `isTheChosenOne` was always false [which is a probable scenario])
    UpdateFunction(siteChosen)
    ...
}

如果您想在循环外计算n1/n,也可以。 因此,我们的想法是在循环中测试候选者是否为siteChosen,并避免将候选者复制到此预选池中。