【问题标题】:How to generate a specific Cartesian Product given an index如何在给定索引的情况下生成特定的笛卡尔积
【发布时间】:2018-08-28 16:40:34
【问题描述】:

我的问题

我正在尝试从一个非常大的笛卡尔积中生成一小部分可能的组合。我的输入将是一个数组数组,但每个数组的大小是动态的。目前,我使用的是 Python,但我对任何需要使用的语言持开放态度。

我的目标

看到这个问题后:How to select specific item from cartesian product without calculating every other item,我认为这是一个了不起的算法,可以在给定索引的情况下生成一个集合。但是,这只适用于 3 个数组。我的最终目标是这样的,其中确定集合的函数是find_set

Input:
A = [ A_0, A_1, A_2, ..., A_n ]
B = [ B_0, B_1, B_2, ..., B_n ]
C = [ C_0, C_1, C_2, ..., C_n ]
D = [ D_0, D_1, D_2, ..., D_n ]
...
N = [ N_0, N_1, D_2, ..., N_n ]

List = [ A, B, C, D, ... N]

find_set(List, 0) -> [ A_0, B_0, C_0, ..., N_0 ]
find_set(List, 1) -> [ A_0, B_0, C_0, ..., N_1 ]
...

对于任何给定的索引,依此类推。

到目前为止我做了什么

我使用 Python 2.7 和 itertools.product 来生成所有组合,但这只会生成一个迭代器。在遇到内存消耗问题后,我尝试了这样的事情:

results = itertools.product(*List)

# generates 100,000 random indices between 0 and the size of the Cartesian Product
desired_indices = random.sample(xrange(0, calculated_size - 1), 100000) 

for item, index in results:
    if index in desired_indices:
          # do things

问题是,无论如何这都会导致 O(N) 操作,当我有 433,501,216 个可能的集合时,这将需要很长时间才能找到一个非常小的子集。感谢所有帮助和任何其他资源,我可以寻求更多关于该主题的知识。

【问题讨论】:

    标签: python set itertools cartesian-product


    【解决方案1】:

    我不会说 Python,但我为 Scala 编写了一个迭代器,它不需要存储中间结果,只需要一个索引。

    如果您需要有关语法的更多信息,请告诉我。

    基本思路如下:如果你有两个List,3个之一,两个元素之一,(a,b,c)和(1,2),你可以生成(a1,a2,然后b1, b2,最后是 c1,c2)。它完全由每个列表的长度控制,因此必须可以预先计算笛卡尔积的大小(2 * 3)或一般情况下的长度乘积,并取每个长度的模正确的顺序,对于 (0..size-1) 中的每个数字返回一组不同的元素。

    class CartesianIterator [T] (val ll: Seq[Seq[T]]) extends Iterator [Seq[T]] { // with IndexedSeq [Seq[T]] {
      var current = 0L
      override val size = ll.map (_.size).product
    
      def get (n: Long): List[T] = {
    
          def get (n: Long, lili: Seq[Seq[T]]): List[T] = lili.length match {
            case 0L => List ()
            case _ => {
              val inner = lili.head
              inner ((n % inner.size).toInt) :: get (n / inner.size, lili.tail)
            }
          }
    
          get (n, ll)
      }
    
      override def hasNext () : Boolean = current != size
      override def next (): Seq[T] = {
        current += 1
        get (current - 1)
      }
    
        // IndexedSeq: Selects an element by its index in the sequence.
        // val x = CartesianIterator (...)
        // val res = x(123) // = x.apply (123)
        def apply (idx: Long): Seq[T] = {
            current = idx-1L
            next ()
        }
    }
    
    def exhaustiveDemo () {
      val ci = new CartesianIterator (List(List ('a', 'b'), List (1, 2, 3, 4), List ("foo", "bar", "baz")))
      for (p <-ci) println (p)
    }
    
    def massiveDemo () {
        val r = util.Random
        // 8 bit per value, ...
        val li = (0 to 256).toList
        // 256^8 combinations, 0 to Long.MaxValue
        val ll = List (li, li, li, li, li, li, li, li)
        val ci = new CartesianIterator (ll)
        for (i <- 0 to 9;
            rnd = math.abs(r.nextLong ());
            tuple = ci.get (rnd)
        ) println (tuple.mkString (":") + " at idx: " + rnd)
    }
    
    exhaustiveDemo ()
    massiveDemo ()
    

    样本输出:

    List(a, 1, foo)
    List(b, 1, foo)
    List(a, 2, foo)
    //...
    List(b, 3, baz)
    List(a, 4, baz)
    List(b, 4, baz)
    
    92:167:65:79:242:74:167:67 at idx: 5009630557992325817
    252:176:16:94:68:30:43:44 at idx: 3270674835001624787
    113:63:109:2:2:184:2:82 at idx: 6072977656466246445
    95:68:232:237:171:233:183:114 at idx: 8494823201479688501
    181:241:90:213:40:128:134:57 at idx: 4259670111450561340
    29:113:165:134:150:89:247:72 at idx: 5402953717167499239
    87:30:93:211:245:210:1:83 at idx: 6146770892844404160
    25:205:116:230:196:105:62:37 at idx: 2757875964952116944
    194:68:71:160:63:57:204:41 at idx: 3094941632910767450
    166:133:37:249:17:6:215:92 at idx: 6874662895836376467
    

    【讨论】:

    • 如果我没看错,看起来你只是在生成笛卡尔积而不存储整个东西,并在必要时迭代到下一个元素,对吗?因此,如果我想访问索引 54,377 处的元素,我可以直接生成它吗?或者我是否需要遍历每一个直到达到该索引?如果是后者,你仍然会得到 O(n) 的性能
    • 是的,您只需将索引设置为所需的值并仅计算该位置的那个元素,而不是之前的 54.376。或者之前的 5400 万,数十亿或任何你喜欢的。 MassiveDemo 正是展示了这一点。如果你用一个常数种子随机初始化,你总是会得到相同的结果。不幸的是,集合基类的大小应该是最大的 int,所以使用 long 已经是个问题了。使用 BigInt 需要重写期望 Int 的库,并打破与其他工具/库的完美集成。在 Python 中可能更容易。
    • 对于每个元素,访问时间与集合中列表(或序列)的数量成正比。对于元素 54377,您可以调用 ci.get (54377, ll) 或更好的方法,提供一个仅采用索引 ci.get (54377) 的方法“get”,因为列表列表被称为递归 get 方法的入口点,它期望列表是参数。
    • 感谢您发布此内容,您的回答确实帮助我进行了更多研究并指导我自己的实施(:
    【解决方案2】:

    我实际上能够自己弄清楚。万一其他人遇到这种情况,这里有一个 Python 实现:

    import math
    
    class LazyCartesianProduct:
        def __init__(self, sets):
            self.sets = sets
            self.divs = []
            self.mods = []
            self.maxSize = 1
            self.precompute()
    
        def precompute(self):
            for i in self.sets:
                self.maxSize = self.maxSize * len(i)
            length = len(self.sets)
            factor = 1
            for i in range((length - 1), -1, -1):
                items = len(self.sets[i])
                self.divs.insert(0, factor)
                self.mods.insert(0, items)
                factor = factor * items
    
        def entryAt(self, n):
            length = len(self.sets)
            if n < 0 or n >= self.maxSize:
                raise IndexError
            combination = []
            for i in range(0, length):
                combination.append(self.sets[i][ int(math.floor(n / self.divs[i])) % self.mods[i]])
            return combination
    

    这实现了来自这个网站的算法:http://phrogz.net/lazy-cartesian-product

    【讨论】:

      猜你喜欢
      • 2021-05-06
      • 1970-01-01
      • 2020-06-01
      • 2018-12-03
      • 2016-10-08
      • 1970-01-01
      • 2011-12-26
      • 2021-09-05
      • 2016-05-07
      相关资源
      最近更新 更多