【问题标题】:Return the cross product of a list of lists返回列表列表的叉积
【发布时间】:2016-04-01 18:36:13
【问题描述】:

给定一个大小为 n 的列表,编写一个程序,返回每个列表中包含的所有可能的元素组合。

例子:

  • 列表 A = "x, z"
  • 列表 B = "a, b, c"
  • 列表 C = "o, p"

输出:

  • x a o
  • x 一次
  • x b o
  • x b p
  • .....
  • z c p

顺序无关紧要,但困难的部分是:您不能使用递归。 我的解决方案:

void combos(const char *string)
{
    int i, j, k;
    int len = strlen(string);

    for (i = 0; i < len - 2; i++)
    {
        for (j = i + 1; j < len - 1; j++)
        {
            for (k = j + 1; k < len; k++)
                printf("%c%c%c\n", string[i], string[j], string[k]);
        }
    }
}

如您所见,它只有在我事先知道列表数量的情况下才有效。我很好奇递归是否是解决它的唯一方法。

【问题讨论】:

  • 您是明确要求所有标记语言的解决方案,还是只对一种语言感兴趣?
  • 嘿,你忘了php标签:)
  • 我不关心语言,我主要关心它背后的逻辑和通用算法。对不起,我是新来的。如果题外话,我会删除这个问题。
  • 没有错。在这种情况下,请使用标签language-agnostic,而不是列出您能想到的所有语言。
  • @PaulBoddington 感谢您的提示。我相应地编辑了我的帖子。但没关系,它已经被否决了。

标签: algorithm data-structures language-agnostic


【解决方案1】:

把它想象成你如何增加一个数字,例如一个基数为 3 的数字会去:

000
001
002
010
011
...
222

现在将每个数字视为每个嵌套列表的索引。您将拥有与嵌套列表一样多的数字,即外部列表的大小。

每个数字的“基数”可能不同,是对应嵌套列表的大小。如果嵌套列表很大,则“数字”可能是一个非常大的数字。

因此,您首先创建一个“数字”列表或索引值,并将它们初始化为0。然后打印这些索引处元素的值。然后增加最后一个索引值,根据需要滚动,就像正常数字一样,当第一个索引值滚动时停止。

这是一个使用数组数组的 Java 实现,即String[][]。如果需要,您可以轻松更改为 List&lt;List&lt;String&gt;&gt;List&lt;String[]&gt;

@SafeVarargs
public static void printCombos(String[] ... lists) {
    if (lists.length == 0)
        throw new IllegalArgumentException("No lists given");
    for (String[] list : lists)
        if (list.length == 0)
            throw new IllegalArgumentException("List is empty");

    int[] idx = new int[lists.length];
    for (;;) {
        // Print combo
        for (int i = 0; i < lists.length; i++) {
            if (i != 0)
                System.out.print(' ');
            System.out.print(lists[i][idx[i]]);
        }
        System.out.println();

        // Advance to next combination
        for (int i = lists.length - 1; ++idx[i] == lists[i].length; ) {
            idx[i] = 0;
            if (--i < 0)
                return; // We're done
        }
    }
}

public static void main(String[] args) {
    String[][] data = { { "x", "z" }, { "a", "b", "c" }, { "o", "p" } };
    printCombos(data);
}

输出

x a o
x a p
x b o
x b p
x c o
x c p
z a o
z a p
z b o
z b p
z c o
z c p

如果您使用列表而不是数组,则代码将使用 get(int),这可能并不总是对性能有好处,例如对于LinkedList

如果是这种情况,请将int[] idx 替换为Iterator[],并使用对应列表的迭代器初始化每个数组条目。然后将通过从相关列表中检索新的Iterator 来将“数字”重置为 0。

在这种情况下,它们甚至不必是列表,而是可以是任何类型的集合,或者更具体地说是 Iterable 对象。

【讨论】:

    【解决方案2】:

    你不需要递归。您需要做的就是建立一套中间解决方案。这是 Python 中的非递归解决方案:

    # This does NOT use recursion!
    def all_comb(list_of_lists):
        # We start with a list of just the empty set.
        answer = [[]]
        for list in list_of_lists:
            # new_answer will be the list of combinations including this one.
            new_answer = []
            # Build up the new answer.
            for thing in list:
                for prev_list in answer:
                    new_answer.append(prev_list + [thing])
            # Replace the old answer with the new one.
            answer = new_answer
        # We now have all combinations of all lists.
        return answer
    
    # Demonstration that it works.    
    for comb in all_comb([["x", "y"], ["a", "b", "c"], ["o", "p"]]):
        print(" ".join(comb))
    

    【讨论】:

      【解决方案3】:

      编辑:我在 python 中回答这个问题,因为虽然它当前标记为 ,但 python 是一个很好的、可执行的伪伪代码。

      如果您可以以 尾递归 的形式编写函数,即以类似于def f(x): return f(g(x)) 的形式,则很容易将其转换为迭代形式。不幸的是,您通常不会得到尾递归调用,因此您需要了解一些技巧。

      首先,假设我们有一个如下所示的函数:

      def my_map(func, my_list):
          if not my_list:
              return []
      
          return [func(my_list[0])] + change_my_list(my_list[1:])
      

      好的,所以它是递归的,但不是尾递归:它真的是

      def my_map(func, my_list):
          if not my_list:
              return []
      
          result = [func(my_list[0])] + change_my_list(my_list[1:])
          return result
      

      相反,我们需要稍微调整函数,添加传统上所谓的累加器:

      def my_map(func, my_list, acc = [])
          if not my_list: return acc
          acc = acc + func(my_list[0])
          return my_map(func, my_list[1:], acc + func(my_list[0]))
      

      现在,我们有了一个真正的尾递归函数:我们已经从 def f(x): return g(f(x)) 变为 def f(x): return f(g(x))

      现在,将该函数转换为非递归形式非常简单:

      def my_map(func, my_list, acc=[]):
          while True: #added
              if not my_list: return acc
              #return my_map(func, my_list[1:], acc + func(my_list[0])) #deleted
              func, my_list, acc = func, my_list[1:], acc + func(my_list[0]) #added
      

      现在,我们稍微整理一下:

      def my_map(func, my_list):
          acc = []
          while my_list:
              acc.append(func(my_list[0])
              my_list = my_list[1:]
      
          return acc
      

      请注意,您可以使用 for 循环或列表推导进一步清理它,但这留给读者作为练习。

      好的,这是一个病态的例子,希望你知道python有一个内置的map函数,但过程是一样的:转换成尾递归形式,用参数重新分配替换递归调用,然后收拾一下。

      所以,如果你有:

      def make_products(list_of_lists):
          if not list_of_lists: return []
          first_list = list_of_lists[0]
          rest = list_of_lists[1:]
          return product_of(first_list, make_products(rest))
      

      您可以将其转换为尾递归形式

      def make_products(list_of_lists, acc=[]):
          if not list_of_lists: return acc
          first_list = list_of_lists[0]
          rest = list_of_lists[1:]
          acc = product_of(acc, first_list)
          return make_products(rest, acc)
      

      那么,这简化为:

      def make_products(list_of_lists):
          acc=[]
          while list_of_lists: 
              first_list = list_of_lists[0]
              rest = list_of_lists[1:]
      
              acc = product_of(acc, first_list)
              list_of_lists = rest
      
          return acc
      

      同样,这可以进一步清理,进入for 循环:

      def make_products(list_of_lists):
          acc=[]
      
          for lst in list_of_lists: 
              acc = product_of(acc, lst)
      
          return acc
      

      如果您查看过内置函数,您可能会注意到这有点熟悉:它本质上是 reduce 函数:

      def reduce(function, iterable, initializer):
          acc = initializer
          for x in iterable:
              acc = function(acc, x)
          return acc
      

      所以,最终的形式是这样的

      def make_products(list_of_lists):
          return reduce(product_of, list_of_lists, []) # the last argument is actually optional here
      

      然后您只需要担心编写product_of 函数。

      【讨论】:

        【解决方案4】:

        如您所知,通常的解决方案是递归。但是,出于无聊,我曾经编写了一个 java 方法 multiNext 来做到这一点而无需递归。 multiNext 使用数组来跟踪等效嵌套循环系统中的索引负载。

        public static boolean multiNext(int[] current, int[] slotLengths) {
            for (int r = current.length - 1; r >= 0; r--) {
                if (current[r] < slotLengths[r] - 1) {
                    current[r]++;
                    return true;
                } else {
                    current[r] = 0;
                }
            }
            return false;
        }
        
        public static void cross(List<List<String>> lists) {
            int size = lists.size();
            int[] current = new int[size];
            int[] slotLengths = new int[size];
            for (int i = 0; i < size; i++)
                slotLengths[i] = lists.get(i).size();
            do {
                List<String> temp = new ArrayList<>();
                for (int i = 0; i < size; i++)
                    temp.add(lists.get(i).get(current[i]));
                System.out.println(temp);
            } while (multiNext(current, slotLengths));
        }
        
        public static void main(String[] args) {
            cross(Arrays.asList(Arrays.asList("x", "z"), Arrays.asList("a", "b", "c"), Arrays.asList("o", "p")));
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-02-05
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-11-26
          • 1970-01-01
          相关资源
          最近更新 更多