草庐IT

algorithm - 从N个元素的 slice 生成K个元素的算法

coder 2024-07-06 原文

我正在尝试从Go中的this Stackoverflow question移植算法。我正在尝试使用的算法如下:给定任意长度的字符串 slice 和“深度”,找到原始 slice 中长度为深度的元素的所有组合。例如,如果给定一个包含A,B,C,D,E和F且深度为3的 slice ,则结果应为:

[A, B, C]
[A, B, D]
[A, B, E]
[A, B, F]
[A, C, D]
[A, C, E]
[A, C, F]
[A, D, E]
[A, D, F]
[A, E, F]
[B, C, D]
[B, C, E]
[B, C, F]
[B, D, E]
[B, D, F]
[B, E, F]
[C, D, E]
[C, D, F]
[C, E, F]
[D, E, F]

我已经尝试在上述Go语言中实现一些建议的解决方案,但是不幸的是,我的Go语言技能还没有达到标准。几周前我才开始用Go编程。

这是失败的代码,尝试移植this implementation in Java失败:
package main

import (
    "fmt"
)

func main() {
    combos := []string{"A","B","C","D","E","F"}
    combos = GetCombos(combos, 3)

    fmt.Println(combos)
}

func GetCombos(set []string, depth int) []string {
    var results []string
    element := make([]string, depth)
    return GetEnvCombos2(set, depth, 0, element, results)
}

func GetCombos2(set []string, depth int, start int, element, results []string) []string {
    if depth == 0 {
        var guess string
        for _, e := range element {
            guess += e
        }
        results = append(results, guess)
        return results
    }
    for i := start; i <= len(set) - depth; i++ {
        element[len(element) - depth] = set[i]
        results = append(results, GetEnvCombos2(set, depth - 1, i + 1, element, results)...)
    }

    return nil
}

我不知道Java中的实现是否是最有效的方法,但它似乎相当有效,并且(我认为)移植到Go相对容易。如果有一种完全不同但更有效的方法来执行此操作,我很乐意接受。

最佳答案

注意:

对任何组合问题的正确答案实际上是永远不要将所有可能的组合放入容器中,然后再进行处理。通常会有大量的组合,并且临时容器往往会用完所有可用内存以存储仅将被引用一次的项目。原始Java程序将处理步骤(在本例中为“打印组合”)埋在生成函数的内部,实际上,它也不是一个好的解决方案,因为它需要为每个不同的动作创建一个全新的生成器函数。

构造组合生成的一种方法是使用给定前一个组合的函数,该函数可查找下一个组合。这种功能通常称为“迭代器”。如果提供了最后一个组合,则该函数返回一个返回值,指示没有更多可用组​​合。通常,所提供的组合是“就地”修改的,因此返回值只是一个布尔值,指示组合是否为最后一个组合。 (通常认为最佳做法是将提供的组合重置为第一个组合,并报告以前是最后一个组合。)该策略不适用于递归算法(例如您要移植的组合)。

许多语言都包含一些允许递归生成可能值的功能。例如,在Go中,您可以将迭代器编写为“go例程”。尽管存在潜在的成本,但这可以产生非常精美的代码。

通过使用某种堆栈数据结构模拟调用堆栈,始终可以将递归函数重新实现为迭代函数。但是,结果难以理解,而且通常较慢(因为本机递归几乎总是比模拟递归快)。而且您可能能够找到一种非递归算法进行迭代(可能会更改迭代顺序)。

我不会在这里做任何这些事情。以下内容仅满足与原始代码相同的原型,并返回(可能是巨大的)结果片段,因为根本的问题仅是递归函数的设计问题。

内部递归生成器的原型是

func GetCombos2(set []string,
                depth int, start int,
                element []string,
                results []string) []string

(为清楚起见,我添加了element的类型。)尝试明确说明该函数的作用是很有用的,这可能是这样的:

给定项目列表set,仍然需要附加element项目的部分组合depth和列表results,返回results并附加可能的组合,并以element所指示的前缀开头,其继续仅包含其索引的项目大于等于start。组合以单调递增的索引顺序生成,并且要求前缀中的所有项目的索引都小于start

这有点麻烦,而且我不确定阅读它是否比代码更清楚。但这可能有助于理解正在发生的事情。在这里,我将只关注一小部分:

给定…results,…返回附加了…的results [用这些参数计算出的新组合]

这不是编写此递归的唯一可能方法。另一种方法是不要求results作为参数,而只是返回根据其他参数生成的组合列表。那会产生稍微简单的代码,但是由于生成并立即丢弃的部分结果的 slice 数量而可能会慢很多。使用诸如results之类的“累加器”参数是使递归更有效的常用技术。

在此讨论中重要的是理解递归函数的返回值是什么。如果您使用“累加器”策略(带有results参数),则返回值是到目前为止找到的结果的整个列表,并且仅在添加新结果时才附加到该结果。如果您使用非累加器策略,那么当您发现一个新结果时,会立即将其返回,将其留给调用方以连接它从多个调用中收到的各种列表。

因此,这两种策略如下所示:

蓄能器版本:
func GetCombos2(set []string, depth int, start int, element []string, results []string) []string {
    if depth == 0 {
        results = append(results, strings.Join(element, "")) 
    } else {
        for i := start; i <= len(set) - depth; i++ {
            element[len(element) - depth] = set[i]
            results = GetEnvCombos2(set, depth - 1, i + 1, element, results)
        }
    }
    return results
}

非累加器版本:
func GetCombos2(set []string, depth int, start int, element []string) []string {
    if depth == 0 {
        return []string { strings.Join(element, "") }
    } else {
        var results []string
        for i := start; i <= len(set) - depth; i++ {
            element[len(element) - depth] = set[i]
            results = append(results, GetCombos2(set, depth - 1, i + 1, element)...)
        }
        return results
    }
}

编辑:在写完之后,我意识到字符串数组elements的使用实际上是一种Java主义,无法很好地转换为Go。 (或者也许是C-ism不好地翻译为Java。)无论如何,如果我们只传递表示前缀的string,则函数会稍微快一些,并且易于阅读,因此我们不需要执行Join。 (Go字符串是不可变的,因此在将它们放入结果 slice 之前无需复制它们。)

这将代码减少为以下内容:

累加器版本(推荐,但是迭代器会更好):
func GetCombos(set []string, depth int) []string {
    return GetCombosHelper(set, depth, 0, "", []string{})
}

func GetCombosHelper(set []string, depth int, start int, prefix string, accum []string) []string {
    if depth == 0 {
        return append(accum, prefix)
    } else {
        for i := start; i <= len(set) - depth; i++ {
            accum = GetCombosHelper(set, depth - 1, i + 1, prefix + set[i], accum)
        }
        return accum
    }
}

非累加器版本:
func GetCombos(set []string, depth int) []string {
    return GetCombosHelper(set, depth, 0, "")
}

func GetCombosHelper(set []string, depth int, start int, prefix string) []string {
    if depth == 0 {
        return []string{prefix}
    } else {
        accum := []string{}
        for i := start; i <= len(set) - depth; i++ {
            accum = append(accum, GetCombosHelper(set, depth - 1, i + 1, prefix + set[i])...)
        }
        return accum
    }
}

在我的笔记本电脑上,给定一组62个深度为6的元素(大写和小写字母加数字),非累加器版本花费了29.7秒(经过),累加器版本花费了13.4秒。两者都使用了大约4.5 GB的内存,这对我来说似乎有点高,因为只有“61,474,519”个六字符组合,并且每个组合的内存消耗达到了将近80字节的峰值内存使用量。

关于algorithm - 从N个元素的 slice 生成K个元素的算法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54054062/

有关algorithm - 从N个元素的 slice 生成K个元素的算法的更多相关文章

  1. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  2. ruby - 在 jRuby 中使用 'fork' 生成进程的替代方案? - 2

    在MRIRuby中我可以这样做:deftransferinternal_server=self.init_serverpid=forkdointernal_server.runend#Maketheserverprocessrunindependently.Process.detach(pid)internal_client=self.init_client#Dootherstuffwithconnectingtointernal_server...internal_client.post('somedata')ensure#KillserverProcess.kill('KILL',

  3. ruby - 如何使用 Ruby aws/s3 Gem 生成安全 URL 以从 s3 下载文件 - 2

    我正在编写一个小脚本来定位aws存储桶中的特定文件,并创建一个临时验证的url以发送给同事。(理想情况下,这将创建类似于在控制台上右键单击存储桶中的文件并复制链接地址的结果)。我研究过回形针,它似乎不符合这个标准,但我可能只是不知道它的全部功能。我尝试了以下方法:defauthenticated_url(file_name,bucket)AWS::S3::S3Object.url_for(file_name,bucket,:secure=>true,:expires=>20*60)end产生这种类型的结果:...-1.amazonaws.com/file_path/file.zip.A

  4. ruby-on-rails - Ruby on Rails - 为文本区域和图片生成列 - 2

    我是Rails的新手,所以请原谅简单的问题。我正在为一家公司创建一个网站。那家公司想在网站上展示它的客户。我想让客户自己管理这个。我正在为“客户”生成一个表格,我想要的三列是:公司名称、公司描述和Logo。对于名称,我使用的是name:string但不确定如何在脚本/生成脚手架终端命令中最好地创建描述列(因为我打算将其设置为文本区域)和图片。我怀疑描述(我想成为一个文本区域)应该仍然是描述:字符串,然后以实际形式进行调整。不确定如何处理图片字段。那么……说来话长:我在脚手架命令中输入什么来生成描述和图片列? 最佳答案 对于“文本”数

  5. ruby-on-rails - 如何生成传递一些自定义参数的 `link_to` URL? - 2

    我正在使用RubyonRails3.0.9,我想生成一个传递一些自定义参数的link_toURL。也就是说,有一个articles_path(www.my_web_site_name.com/articles)我想生成如下内容:link_to'Samplelinktitle',...#HereIshouldimplementthecode#=>'http://www.my_web_site_name.com/articles?param1=value1¶m2=value2&...我如何编写link_to语句“alàRubyonRailsWay”以实现该目的?如果我想通过传递一些

  6. ruby - 在哈希的键数组中追加元素 - 2

    查看我的Ruby代码:h=Hash.new([])h[0]=:word1h[1]=h[1]输出是:Hash={0=>:word1,1=>[:word2,:word3],2=>[:word2,:word3]}我希望有Hash={0=>:word1,1=>[:word2],2=>[:word3]}为什么要附加第二个哈希元素(数组)?如何将新数组元素附加到第三个哈希元素? 最佳答案 如果您提供单个值作为Hash.new的参数(例如Hash.new([]),完全相同的对象将用作每个缺失键的默认值。这就是您所拥有的,那是你不想要的。您可以改用

  7. ruby-on-rails - 如何在 Rails 3 中创建自定义脚手架生成器? - 2

    有这些railscast。http://railscasts.com/episodes/218-making-generators-in-rails-3有了这个,你就会知道如何创建样式表和脚手架生成器。http://railscasts.com/episodes/216-generators-in-rails-3通过这个,您可以了解如何添加一些文件来修改脚手架View。我想把两者结合起来。我想创建一个生成器,它也可以创建脚手架View。有点像RyanBates漂亮的生成器或web_app_themegem(https://github.com/pilu/web-app-theme)。我

  8. 报告回顾丨模型进化狂飙,DetectGPT能否识别最新模型生成结果? - 2

    导读语言模型给我们的生产生活带来了极大便利,但同时不少人也利用他们从事作弊工作。如何规避这些难辨真伪的文字所产生的负面影响也成为一大难题。在3月9日智源Live第33期活动「DetectGPT:判断文本是否为机器生成的工具」中,主讲人Eric为我们讲解了DetectGPT工作背后的思路——一种基于概率曲率检测的用于检测模型生成文本的工具,它可以帮助我们更好地分辨文章的来源和可信度,对保护信息真实、防止欺诈等方面具有重要意义。本次报告主要围绕其功能,实现和效果等展开。(文末点击“阅读原文”,查看活动回放。)Ericmitchell斯坦福大学计算机系四年级博士生,由ChelseaFinn和Chri

  9. 区块链之加解密算法&数字证书 - 2

    目录一.加解密算法数字签名对称加密DES(DataEncryptionStandard)3DES(TripleDES)AES(AdvancedEncryptionStandard)RSA加密法DSA(DigitalSignatureAlgorithm)ECC(EllipticCurvesCryptography)非对称加密签名与加密过程非对称加密的应用对称加密与非对称加密的结合二.数字证书图解一.加解密算法加密简单而言就是通过一种算法将明文信息转换成密文信息,信息的的接收方能够通过密钥对密文信息进行解密获得明文信息的过程。根据加解密的密钥是否相同,算法可以分为对称加密、非对称加密、对称加密和非

  10. 「Python|Selenium|场景案例」如何定位iframe中的元素? - 2

    本文主要介绍在使用Selenium进行自动化测试或者任务时,对于使用了iframe的页面,如何定位iframe中的元素文章目录场景描述解决方案具体代码场景描述当我们在使用Selenium进行自动化测试的时候,可能会遇到一些界面或者窗体是使用HTML的iframe标签进行承载的。对于iframe中的标签,如果直接查找是无法找到的,会抛出没有找到元素的异常。比如近在咫尺的例子就是,CSDN的登录窗体就是使用的iframe,大家可以尝试通过F12开发者模式查看到的tag_name,class_name,id或者xpath来定位中的页面元素,会抛出NoSuchElementException异常。解决

随机推荐