jjzjj

python - 优化产品组装/拆卸

coder 2023-08-21 原文

我有一个商店,里面有元素。每个项目要么是一个组件(它是原子的),要么是由各种组件(但绝不是 2 个或更多相同组件)组成的产品。

现在,当我想从商店取货时,有多种情况:

  • 商店包含必要数量的产品。
  • 商店包含我可以组装产品的组件。
  • 该商店包含与所需产品共享组件的产品。我可以拆卸它们并组装所需的元素。
  • 以上任意组合。

  • 到目前为止,您可以在下面看到我的代码( getAssemblyPath )。如果可能,它确实找到了组装所需元素的方法,但它没有优化组装路径。

    我想通过两种方式优化路径:
  • 首先,选择组装/拆卸操作次数最少的路径。
  • 其次,如果有多种这样的路径,选择在商店中留下最少拆卸组件的路径。

  • 现在,我完全不知道如何完成这个优化(我什至不确定这是 SO 还是数学的问题)。

    我该如何更改 getAssemblyPath以便它满足我的优化要求?

    到目前为止我的代码:
    #! /usr/bin/python
    
    class Component:
        def __init__ (self, name): self.__name = name
    
        def __repr__ (self): return 'Component {}'.format (self.__name)
    
    class Product:
        def __init__ (self, name, components):
            self.__name = name
            self.__components = components
    
        @property
        def components (self): return self.__components
    
        def __repr__ (self): return 'Product {}'.format (self.__name)
    
    class Store:
        def __init__ (self): self.__items = {}
    
        def __iadd__ (self, item):
            item, count = item
            if not item in self.__items: self.__items [item] = 0
            self.__items [item] += count
            return self
    
        @property
        def items (self): return (item for item in self.__items.items () )
    
        @property
        def products (self): return ( (item, count) for item, count in self.__items.items () if isinstance (item, Product) )
    
        @property
        def components (self): return ( (item, count) for item, count in self.__items.items () if isinstance (item, Component) )
    
        def getAssemblyPath (self, product, count):
            if product in self.__items:
                take = min (count, self.__items [product] )
                print ('Take {} of {}'.format (take, product) )
                count -= take
                if not count: return
            components = dict ( (comp, count) for comp in product.components)
            for comp, count in self.components:
                if comp not in components: continue
                take = min (count, components [comp] )
                print ('Take {} of {}'.format (take, comp) )
                components [comp] -= take
                if not components [comp]: del components [comp]
                if not components: return
            for prod, count in self.products:
                if prod == product: continue
                shared = set (prod.components) & set (components.keys () )
                dis = min (max (components [comp] for comp in shared), count)
                print ('Disassemble {} of {}.'.format (dis, prod) )
                for comp in shared:
                    print ('Take {} of {}.'.format (dis, comp) )
                    components [comp] -= take
                    if not components [comp]: del components [comp]
                    if not components: return
            print ('Missing components:')
            for comp, count in components.items ():
                print ('{} of {}.'.format (count, comp) )
    
    c1 = Component ('alpha')
    c2 = Component ('bravo')
    c3 = Component ('charlie')
    c4 = Component ('delta')
    
    p1 = Product ('A', [c1, c2] )
    p2 = Product ('B', [c1, c2, c3] )
    p3 = Product ('C', [c1, c3, c4] )
    
    store = Store ()
    store += (c2, 100)
    store += (c4, 100)
    store += (p1, 100)
    store += (p2, 100)
    store += (p3, 10)
    store.getAssemblyPath (p3, 20)
    

    这输出:
    Take 10 of Product C
    Take 10 of Component delta
    Disassemble 10 of Product A.
    Take 10 of Component alpha.
    Disassemble 10 of Product B.
    Take 10 of Component charlie.
    

    这是可行的,但它确实不必要地分解了产品 A,因为产品 B 包含所需的组件 alpha 和 charlie。

    ——

    编辑:

    回答 Blckknght 非常明智的问题:

    When you say you want "the least number of assembly/disassembly actions", do you mean the smallest number of items, or the smallest number of different products?



    “组装/拆卸 Action ”是组装或拆卸一个产品的 Action ,无论涉及多少个组件。我正在寻找最少数量的触摸项目,无论它们是否不同。

    That is, is is better to dissassemble 20 of Product A than to dissassemble 10 of Product A and an additional 5 of Product B?



    后者更接近最优。

    Further, you say you want to avoid leaving many components behind, but in your current code all disassembled components that are not used by the requested Product are lost. Is that deliberate (that is, do you want to be throwing away the other components), or is it a bug?



    方法getAssemblyPath只决定如何获取元素的路径。它不接触实际的商店。它不会分配给 self.__items .把它想象成一个功能,它向商店发出订单,保存他在(近期)将来必须做的事情,以便从他的商店中取出所需数量的所需元素。

    ——

    编辑2:

    解决这个问题的第一个明显(或至少对我来说是显而易见的)方法是首先搜索那些与所需产品共享最大数量的组件的产品,因为每次拆卸都会获得更多所需的组件。但不幸的是,这不一定会产生最佳路径。举个例子:

    产品 A 由组分 α、β、γ、δ、ε 和 ζ 组成。

    产品 B 由组分 α、β、η、δ、ε 和 θ 组成。

    产品 C 由组分 α、β、γ、ι、κ 和 λ 组成。

    乘积 D 由分量 μ、ν、ξ、δ、ε 和 ζ 组成。

    我们有 0 个 A、100 个 B、100 个 C 和 100 个 D。我们需要 10 个 A。现在如果我们首先寻找与 A 共享大部分组件的产品,我们会找到 B。我们拆解 10 个B 得到 α、β、δ 和 ε 各 10 个。但是我们需要拆解 10 个 C(得到 γ)和 10 个 D(得到 ζ)。这些将是 40 个 Action (30 个拆卸和 10 个组装)。
    但最好的方法是拆10个C和10个D(30个 Action ,20个拆解,10个组装)。

    ——

    编辑 3:

    你不需要发布 python 代码来赢得赏金。只需向我解释该算法并表明它确实产生了最佳路径,或者如果存在多个最佳路径,则是其中之一。

    最佳答案

    这是我将如何解决这个问题。我想为此编写代码,但我认为我没有时间。

    您可以递归地找到最佳解决方案。制作一个表示零件存储状态和当前请求的数据结构。现在,对于您需要的每个部分,进行一系列递归调用,尝试使用各种方法来填充订单。关键是通过尝试填充订单的方法,您完成了部分工作,因此递归调用现在是同一问题的稍微简单的版本。

    这是一个基于您的示例的特定示例。我们需要填写由组件 c1、c3 和 c4 组成的产品 3 (p3) 的订单。我们的订单是 20 个 p3,我们有 10 个 p3 的库存,所以我们随便填写了 p3 的前 10 个订单。现在我们的订单是 p3 的 10,但我们可以将其视为 c1 的 10、c3 的 10 和 c4 的 10 的订单。对于第一次递归调用,我们拆解一个 p1,并为单个 c1 填写订单并在商店中放置一个额外的 c2;所以这个递归调用是针对 c1 中的 9 个、c3 中的 10 个和 c4 中的 10 个,并且在商店中更新了可用性。对于第二次递归调用,我们拆解了一个 p2,并填写了一个 c1 和一个 c4 的订单,并将一个额外的 c2 放入商店;所以这个递归调用是针对 c1 中的 9 个、c3 中的 10 个和 c4 中的 9 个,并在商店中更新了可用性。

    由于每次调用都会减少问题,因此递归调用系列将终止。递归调用应该返回一个成本指标,它要么表明调用未能找到解决方案,要么表明找到的解决方案成本是多少;该函数通过选择成本最低的解决方案来选择最佳解决方案。

    我不确定,但您可以通过记住通话来加快速度。 Python 在 3.x 系列中有一个非常漂亮的内置新功能,functools.lru_cache() ;由于您将问题标记为“Python 3.2”,因此您可以使用它。

    What is memoization and how can I use it in Python?

    memoization 的工作原理是识别出该函数已经使用相同的参数被调用,并且只返回与以前相同的解决方案。所以它是一个缓存映射参数到答案。如果参数包含非必要数据(例如存储中有多少组件 c2),则内存不太可能起作用。但是如果我们假设我们有产品 p1 和 p9,并且 p9 包含组件 c1 和 c9,那么为了我们的目的,拆卸 p1 之一或 p9 之一应该是等效的:它们具有相同的拆卸成本,并且它们都生产我们需要的组件(c1) 和一个我们不需要的 (c2 或 c9)。因此,如果我们正确使用递归调用参数,那么当我们开始尝试 p9 时,备忘录可以立即返回答案,并且可以节省大量时间。

    嗯,现在想想,我们可能不能用functools.lru_cache()但我们可以自己记住。我们可以制作解决方案的缓存:将元组映射到值的字典,并构建仅包含我们想要缓存的参数的元组。那么在我们的函数中,我们做的第一件事就是检查解的缓存,如果这个调用相当于一个缓存的解,就返回它。

    编辑:这是我到目前为止编写的代码。我还没有完成调试,所以它可能还没有产生正确的答案(我不确定,因为它需要很长时间而且我还没有让它完成运行)。这个版本正在传递字典,这对我关于内存的想法不太适用,但我想让一个简单的版本工作,然后担心加快速度。

    此外,此代码将产品拆开并将它们作为组件添加到商店中,因此最终解决方案将首先说“拆开 10 个产品 A”之类的内容,然后它会说“拆开 20 个组件 alpha”或其他任何内容。换句话说,组件数量可以被认为是高的,因为它不区分已经在商店中的组件和通过拆卸产品放在那里的组件。

    我现在没时间,暂时不会处理它,抱歉。

    #!/usr/bin/python3
    
    class Component:
        def __init__ (self, name): self.__name = name
    
        #def __repr__ (self): return 'Component {}'.format (self.__name)
        def __repr__ (self): return 'C_{}'.format (self.__name)
    
    class Product:
        def __init__ (self, name, components):
            self.__name = name
            self.__components = components
    
        @property
        def components (self): return self.__components
    
        #def __repr__ (self): return 'Product {}'.format (self.__name)
        def __repr__ (self): return 'P_{}'.format (self.__name)
    
    class Store:
        def __init__ (self): self.__items = {}
    
        def __iadd__ (self, item):
            item, count = item
            if not item in self.__items: self.__items [item] = 0
            self.__items [item] += count
            return self
    
        @property
        def items (self): return (item for item in self.__items.items () )
    
        @property
        def products (self): return ( (item, count) for item, count in self.__items.items () if isinstance (item, Product) )
    
        @property
        def components (self): return ( (item, count) for item, count in self.__items.items () if isinstance (item, Component) )
    
        def get_assembly_path (self, product, count):
            store = self.__items.copy()
            if product in store:
                take = min (count, store [product] )
                s_trivial = ('Take {} of {}'.format (take, product) )
                count -= take
                if not count:
                    print(s_trivial)
                    return
                dict_decr(store, product, take)
                product not in store
    
            order = {item:count for item in product.components}
            cost, solution = solver(order, store)
            if cost is None:
                print("No solution.")
                return
    
            print("Solution:")
            print(s_trivial)
            for item, count in solution.items():
                if isinstance(item, Component):
                    print ('Take {} of {}'.format (count, item) )
                else:
                    assert isinstance(item, Product)
                    print ('Disassemble {} of {}'.format (count, item) )
    
        def getAssemblyPath (self, product, count):
            if product in self.__items:
                take = min (count, self.__items [product] )
                print ('Take {} of {}'.format (take, product) )
                count -= take
                if not count: return
            components = dict ( (comp, count) for comp in product.components)
            for comp, count in self.components:
                if comp not in components: continue
                take = min (count, components [comp] )
                print ('Take {} of {}'.format (take, comp) )
                components [comp] -= take
                if not components [comp]: del components [comp]
                if not components: return
            for prod, count in self.products:
                if prod == product: continue
                shared = set (prod.components) & set (components.keys () )
                dis = min (max (components [comp] for comp in shared), count)
                print ('Disassemble {} of {}.'.format (dis, prod) )
                for comp in shared:
                    print ('Take {} of {}.'.format (dis, comp) )
                    components [comp] -= take
                    if not components [comp]: del components [comp]
                    if not components: return
            print ('Missing components:')
            for comp, count in components.items ():
                print ('{} of {}.'.format (count, comp) )
    
    def str_d(d):
        lst = list(d.items())
        lst.sort(key=str)
        return "{" + ", ".join("{}:{}".format(k, v) for (k, v) in lst) + "}"
    
    def dict_incr(d, key, n):
        if key not in d:
            d[key] = n
        else:
            d[key] += n
    
    def dict_decr(d, key, n):
        assert d[key] >= n
        d[key] -= n
        if d[key] == 0:
            del(d[key])
    
    def solver(order, store):
        """
        order is a dict mapping component:count
        store is a dict mapping item:count
    
        returns a tuple: (cost, solution)
            cost is a cost metric estimating the expense of the solution
            solution is a dict that maps item:count (how to fill the order)
    
        """
        print("DEBUG: solver: {} {}".format(str_d(order), str_d(store)))
        if not order:
            solution = {}
            cost = 0
            return (cost, solution)
    
        solutions = []
        for item in store:
            if not isinstance(item, Component):
                continue
            print("...considering: {}".format(item))
            if not item in order:
                continue
            else:
                o = order.copy()
                s = store.copy()
                dict_decr(o, item, 1)
                dict_decr(s, item, 1)
                if not o:
                    # we have found a solution!  Return it
                    solution = {}
                    solution[item] = 1
                    cost = 1
                    print("BASIS: solver: {} {} / {} {}".format(str_d(order), str_d(store), cost, str_d(solution)))
                    return (cost, solution)
                else:
                    cost, solution = solver(o, s)
                    if cost is None:
                        continue  # this was a dead end
                    dict_incr(solution, item, 1)
                    cost += 1
                    solutions.append((cost, solution))
    
        for item in store:
            if not isinstance(item, Product):
                continue
            print("...Product components: {} {}".format(item, item.components))
            assert isinstance(item, Product)
            if any(c in order for c in item.components):
                print("...disassembling: {}".format(item))
                o = order.copy()
                s = store.copy()
                dict_decr(s, item, 1)
                for c in item.components:
                    dict_incr(s, c, 1)
                cost, solution = solver(o, s)
                if cost is None:
                    continue  # this was a dead end
                cost += 1 # cost of disassembly
                solutions.append((cost, solution))
            else:
                print("DEBUG: ignoring {}".format(item))
    
        if not solutions:
            print("DEBUG: *dead end*")
            return (None, None)
        print("DEBUG: finding min of: {}".format(solutions))
        return min(solutions)
    
    
    
    c1 = Component ('alpha')
    c2 = Component ('bravo')
    c3 = Component ('charlie')
    c4 = Component ('delta')
    
    p1 = Product ('A', [c1, c2] )
    p2 = Product ('B', [c1, c2, c3] )
    p3 = Product ('C', [c1, c3, c4] )
    
    store = Store ()
    store += (c2, 100)
    store += (c4, 100)
    store += (p1, 100)
    store += (p2, 100)
    store += (p3, 10)
    #store.getAssemblyPath (p3, 20)
    store.get_assembly_path(p3, 20)
    

    关于python - 优化产品组装/拆卸,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15071659/

    有关python - 优化产品组装/拆卸的更多相关文章

    1. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

      关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

    2. Python 相当于 Perl/Ruby ||= - 2

      这个问题在这里已经有了答案:关闭10年前。PossibleDuplicate:Pythonconditionalassignmentoperator对于这样一个简单的问题表示歉意,但是谷歌搜索||=并不是很有帮助;)Python中是否有与Ruby和Perl中的||=语句等效的语句?例如:foo="hey"foo||="what"#assignfooifit'sundefined#fooisstill"hey"bar||="yeah"#baris"yeah"另外,类似这样的东西的通用术语是什么?条件分配是我的第一个猜测,但Wikipediapage跟我想的不太一样。

    3. java - 什么相当于 ruby​​ 的 rack 或 python 的 Java wsgi? - 2

      什么是ruby​​的rack或python的Java的wsgi?还有一个路由库。 最佳答案 来自Python标准PEP333:Bycontrast,althoughJavahasjustasmanywebapplicationframeworksavailable,Java's"servlet"APImakesitpossibleforapplicationswrittenwithanyJavawebapplicationframeworktoruninanywebserverthatsupportstheservletAPI.ht

    4. 华为OD机试用Python实现 -【明明的随机数】 2023Q1A - 2

      华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o

    5. python - 如何读取 MIDI 文件、更改其乐器并将其写回? - 2

      我想解析一个已经存在的.mid文件,改变它的乐器,例如从“acousticgrandpiano”到“violin”,然后将它保存回去或作为另一个.mid文件。根据我在文档中看到的内容,该乐器通过program_change或patch_change指令进行了更改,但我找不到任何在已经存在的MIDI文件中执行此操作的库.他们似乎都只支持从头开始创建的MIDI文件。 最佳答案 MIDIpackage会为您完成此操作,但具体方法取决于midi文件的原始内容。一个MIDI文件由一个或多个音轨组成,每个音轨是十六个channel中任何一个上的

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

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

    7. 神州数码无线产品(AC+AP)配置 - 2

      注意:本文主要掌握DCN自研无线产品的基本配置方法和注意事项,能够进行一般的项目实施、调试与运维AP基本配置命令AP登录用户名和密码均为:adminAP默认IP地址为:192.168.1.10AP默认情况下DHCP开启AP静态地址配置:setmanagementstatic-ip192.168.10.1AP开启/关闭DHCP功能:setmanagementdhcp-statusup/downAP设置默认网关:setstatic-ip-routegeteway192.168.10.254查看AP基本信息:getsystemgetmanagementgetmanaged-apgetrouteAP配

    8. 阿里云RDS——产品系列概述 - 2

      基础版云数据库RDS的产品系列包括基础版、高可用版、集群版、三节点企业版,本文介绍基础版实例的相关信息。RDS基础版实例也称为单机版实例,只有单个数据库节点,计算与存储分离,性价比超高。说明RDS基础版实例只有一个数据库节点,没有备节点作为热备份,因此当该节点意外宕机或者执行重启实例、变更配置、版本升级等任务时,会出现较长时间的不可用。如果业务对数据库的可用性要求较高,不建议使用基础版实例,可选择其他系列(如高可用版),部分基础版实例也支持升级为高可用版。基础版与高可用版的对比拓扑图如下所示。优势 性能由于不提供备节点,主节点不会因为实时的数据库复制而产生额外的性能开销,因此基础版的性能相对于

    9. python ffmpeg 使用 pyav 转换 一组图像 到 视频 - 2

      2022/8/4更新支持加入水印水印必须包含透明图像,并且水印图像大小要等于原图像的大小pythonconvert_image_to_video.py-f30-mwatermark.pngim_dirout.mkv2022/6/21更新让命令行参数更加易用新的命令行使用方法pythonconvert_image_to_video.py-f30im_dirout.mkvFFMPEG命令行转换一组JPG图像到视频时,是将这组图像视为MJPG流。我需要转换一组PNG图像到视频,FFMPEG就不认了。pyav内置了ffmpeg库,不需要系统带有ffmpeg工具因此我使用ffmpeg的python包装p

    10. Python 刷Leetcode题库,顺带学英语单词(31) - 2

      ValidPalindromeGivenastring,determineifitisapalindrome,consideringonlyalphanumericcharactersandignoringcases. [#125]Example:"Aman,aplan,acanal:Panama"isapalindrome."raceacar"isnotapalindrome.Haveyouconsiderthatthestringmightbeempty?Thisisagoodquestiontoaskduringaninterview.Forthepurposeofthisproblem

    随机推荐