8

アイテムを含むストアがあります。各アイテムは、コンポーネント (アトマル) またはさまざまなコンポーネントで構成される製品 (ただし、2 つ以上の同じコンポーネントで構成されることはありません) のいずれかです。

今、商品を店から持ち出したい場合、さまざまなシナリオがあります。

  • ストアには必要な数の製品が含まれています。
  • ストアには、私が製品を組み立てることができるコンポーネントが含まれています。
  • ストアには、必要な製品とコンポーネントを共有する製品が含まれています。それらを分解して、必要なアイテムを組み立てることができます。
  • 上記の任意の組み合わせ。

以下に、これまでのコードを示します ( getAssemblyPath)。可能であれば、必要なアイテムを組み立てる方法を見つけますが、組み立てパスを最適化しません。

次の 2 つの方法でパスを最適化します。

  • まず、アセンブリ/分解アクションの回数が最も少ないパスを選択します。
  • 第二に、そのような経路が複数ある場合は、分解されたコンポーネントが店舗に残るのが最も少ない経路を選択します。

さて、ここで私はこの最適化を行う方法を完全に失っています(これが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.

これは機能しますが、製品 B には必要なコンポーネント alpha と charlie の両方が含まれているため、不必要に製品 A を逆アセンブルします。

--

編集:

Blckknght の非常に賢明な質問に答える:

「組立・分解回数を少なくしたい」とは、品数を少なくしたいということですか、それとも品種数を少なくしたいということですか

「組み立て/分解アクション」とは、含まれるコンポーネントの数に関係なく、1 つの製品を組み立てる、または分解するアクションです。異なるかどうかに関係なく、タッチされたアイテムの数が最も少ないものを探しています。

つまり、製品 A を 10 個分解し、さらに製品 B を 5 個分解するよりも、製品 A を 20 個分解する方がよいでしょうか?

後者は最適に近いです。

さらに、多くのコンポーネントを残すのは避けたいと言っていますが、現在のコードでは、要求された製品で使用されていない逆アセンブルされたコンポーネントはすべて失われています。それは意図的なものですか (つまり、他のコンポーネントを破棄したいのですか)、それともバグですか?

このメソッドgetAssemblyPathは、アイテムを取得する方法のパスのみを決定します。実店舗には触れません。に割り当てられることはありませんself.__items。これは、店から必要なアイテムを必要な量だけ取り出すために、店長が将来 (当面) に何をしなければならないかを注文する機能と考えてください。

--

編集2:

この問題に取り組むための最初の明白な (または少なくとも私には明白な) 方法は、分解するたびに必要なコンポーネントをより多く取得するにつれて、必要な製品と最大量のコンポーネントを共有する製品を最初に検索することです。しかし残念ながら、これは必ずしも最適なパスをもたらすとは限りません。たとえば、次のようにします。

コンポーネントα、β、γ、δ、ε、およびζからなる製品A。

コンポーネントα、β、η、δ、ε、およびθからなる製品B。

コンポーネントα、β、γ、ι、κ、およびλからなる製品C。

コンポーネントμ、ν、ξ、δ、ε、およびζからなる製品D。

A を 0 個、B を 100 個、C を 100 個、D を 100 個保管しています。A を 10 個必要とします。まず、A とほとんどのコンポーネントを共有する製品を探すと、B が見つかります。10 個を分解します。 B は、α、β、δ、および ε のそれぞれ 10 を取得します。しかし、次に、C の 10 個 (γ を取得するため) と D の 10 個 (ζ を取得するため) を逆アセンブルする必要があります。これらは 40 回のアクション (30 回の分解と 10 回の組み立て) になります。しかし、最適な方法は、C の 10 個と D の 10 個を分解することです (アクション 30 回、分解 20 回、組み立て 10 回)。

--

編集3:

報奨金を獲得するために Python コードを投稿する必要はありません。アルゴリズムを私に説明して、それが実際に最適なパス、または最適なパスが複数ある場合はその 1 つを生成することを示してください。

4

3 に答える 3

3

これが私がこの問題を解決する方法です。このためのコードを書きたかったのですが、時間がないと思います。

再帰的に最適解を見つけることができます。パーツ ストアの状態と現在の要求を表すデータ構造を作成します。次に、必要なパーツごとに、一連の再帰呼び出しを行い、注文を満たすさまざまな方法を試します。重要なのは、注文を満たす方法を試すことで、作業の一部を完了できるということです。そのため、再帰呼び出しは、同じ問題の少し単純なバージョンになります。

あなたの例に基づいた具体的な例を次に示します。コンポーネント c1、c3、および c4 で構成される製品 3 (p3) の注文を満たす必要があります。私たちの注文は p3 の 20 個で、p3 の在庫が 10 個あるので、p3 の最初の 10 個の注文を簡単に満たします。ここで、注文は p3 の 10 個ですが、c1 の 10 個、c3 の 10 個、および c4 の 10 個の注文と見なすことができます。最初の再帰呼び出しでは、p1 を逆アセンブルし、単一の c1 の注文を満たし、余分な c2 をストアに配置します。したがって、この再帰呼び出しは c1 の 9 個、c3 の 10 個、および c4 の 10 個に対するものであり、ストアの在庫状況が更新されます。2 番目の再帰呼び出しでは、p2 を逆アセンブルし、c1 と c4 の注文を満たし、余分な c2 をストアに入れます。したがって、この再帰呼び出しは c1 の 9 個、c3 の 10 個、および c4 の 9 個に対するものであり、ストアの在庫状況が更新されています。

呼び出しごとに問題が軽減されるため、再帰的な一連の呼び出しは終了します。再帰呼び出しはコスト メトリックを返す必要があります。コスト メトリックは、呼び出しが解を見つけることができなかったことを通知するか、見つかった解のコストを通知します。関数は、コストが最も低いソリューションを選択することで、最適なソリューションを選択します。

よくわかりませんが、呼び出しをメモ化することでこれを高速化できるかもしれません。Python には、3.x シリーズで新しく追加された非常に気の利いたビルトインがありfunctools.lru_cache()ます。質問に「Python 3.2」というタグを付けたので、これを利用できます。

メモ化とは何ですか? Python でどのように使用できますか?

メモ化は、関数が同じ引数で既に呼び出されていることを認識し、以前と同じソリューションを返すだけで機能します。したがって、これは引数を回答にマッピングするキャッシュです。引数に必須ではないデータ (ストアにあるコンポーネント c2 の数など) が含まれている場合、メモ化が機能する可能性は低くなります。しかし、製品 p1 と p9 があり、p9 にコンポーネント c1 と c9 が含まれていると仮定すると、p1 の 1 つまたは p9 の 1 つを逆アセンブルすることは、目的のために同等である必要があります。これらの逆アセンブル コストは同じであり、どちらも必要なコンポーネントを生成します。 (c1) と、必要のないもの (c2 または c9)。したがって、再帰呼び出しの引数を正しく取得できれば、p9 を試してみたときにメモ化によって即座に回答が返され、多くの時間を節約できます。

うーん、そういえば使えないかもしれないfunctools.lru_cache()けど、自分たちでメモっておけばよかった。ソリューションのキャッシュを作成できます。タプルを値にマッピングする辞書であり、キャッシュしたい引数だけを持つタプルを構築します。次に、関数で最初に行うことは、ソリューションのキャッシュをチェックすることです。この呼び出しがキャッシュされたソリューションと同等である場合は、それを返します。

編集:これまでに書いたコードは次のとおりです。デバッグが終わっていないので、まだ正しい答えが得られていない可能性があります (長い時間がかかり、実行を終了していないため、確信が持てません)。このバージョンは辞書を渡しているため、メモ化に関する私のアイデアではうまく機能しませんが、単純なバージョンを機能させたいと思ったので、スピードアップについて心配しました。

また、このコードは製品を分解してコンポーネントとしてストアに追加するため、最終的なソリューションは最初に「製品 A を 10 個分解する」のように言い、次に「コンポーネント アルファを 20 個取る」などと言うでしょう。つまり、すでに店頭にあった部品と、商品を分解して店頭に置いた部品との区別がつかず、部品点数が多いといえます。

今は時間がないので、しばらくは作業しません。申し訳ありません。

#!/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)
于 2013-03-06T20:03:25.463 に答える
2
  1. N 製品の最適パス <=> 単一製品の最適パス。

実際、製品 X の N 個を最適に組み立てる必要がある場合、(現在の在庫を使用して) 1 つの製品を最適に組み立てた後、残りの在庫を使用して (N-1) 個の製品 X を最適に組み立てることが問題になります。

⇒したがって、製品 X を 1 つずつ最適に組み立てるアルゴリズムを提供すれば十分です。

  1. 製品にコンポーネント x1,..xn が必要であると仮定します (ここでは、在庫コンポーネントとして利用できないコンポーネントのみを含めます)。

コンポーネント xk ごとに、このコンポーネントを含むすべての製品を検索します。コンポーネントごとに製品のリストを取得します - 製品 A1(1),..,A1(i1) はコンポーネント x1 を持ち、製品 A(1),.., A(i2) はコンポーネント x2 を持ちます。製品は、複数のリスト A1、A2、..、An リストに含めることができます)。

リストのいずれかが空の場合、解決策はありません。

そのセットの製品が各リストに含まれるように、最小限の製品セットが必要です。最も単純ですが、計算効率の悪い解決策は力ずくです。すべてのセットを試して、最小限のものを選びます。

  • A1,..,An の結合を取り、それを A と呼びます (結合には固有の積のみを含めます)。

を。A から 1 つの製品を取り出し、それがすべての A1,..,An に含まれている場合 - 1 つの分解 (この製品) のみが必要です。b. A の 2 つの積のすべての組み合わせを試して、いずれかの組み合わせ (a1,a2) が、a1 または a2 のいずれかがリスト A1,..,An のそれぞれに含まれるという条件を満たす場合、それが解です。

...

確かに、深さ n に解があります。リスト A1、..、An のそれぞれから 1 つのコンポーネントです。以前に解決策が見つからなかった場合は、これが最善の解決策です。

今、私は可能だと思うブルートフォースチェックよりも優れた戦略について考える必要があるだけです-私はそれについて考える必要がありますが、このブルートフォースアプローチは確かに厳密に最適なソリューションを見つけます.


編集:

より正確な解決策は、リストを長さでソートすることです。次に、ソリューションである K 製品のセットをチェックする場合、最初の K リストの各リストからの 1 つのアイテムのすべての可能な組み合わせのみをチェックする必要があります。ソリューションがない場合は、問題を解決する深さ K の最小セットはありません。そのタイプのチェックも計算的にはそれほど悪くありません-おそらくうまくいくでしょう????

于 2013-03-06T22:27:34.460 に答える
0

I think the key here is to establish the potential costs of each purchase case, so that the proper combination of purchase cases optimally minimize a cost function. (Then its simply reduced to a knapsack problem)

What follows is probably not optimal but here is an example of what I mean:

1.Any product that is the end product "costs" it's actual cost (in currency).

2.Any component or product that can be assembled into the end product (given other separate products/components) but does not require being dissembled costs it's real price (in currency) plus a small tax( tbd).

3.Any component or product that can facilitate assembly of the end product but requires being dissembled costs it's price in currency plus a small tax for the assembly into the end product and another small tax for each dis-assembly needed. (maybe the same value as the assembly tax?).

Note: these "taxes" will apply to all sub-products that occupy the same case.

... and so on for other possible cases

Then, find all possible combinations of components and products available at the storefront that are capable of being assembled into the end product. Place these "assembly lists" into a cost sorted list determined by your chosen cost function. After that, start creating as many of the first (lowest cost) "assembly list" as you can (by checking if all items in assembly list are still available at the store - i.e. you have already used them for a previous assembly). Once you cannot create any more of this case, pop it from the list. Repeat until all the end products you need are "built".

Note: Every time you "assemble" an end product you will need to decriment a global counter for each product in the current "assembly list".

Hope this get's the discussion moving in the right direction. Good luck!

于 2013-02-28T04:40:58.850 に答える