Return 列表列表的叉积

Return the cross product of a list of lists

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

示例:

输出:

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

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]);
        }
    }
}

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

如您所知,通常的解决方案是递归。然而,出于无聊,我曾经写了一个 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")));
}

你不需要递归。您需要做的就是建立一套中间解决方案。这是 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 的数字会是:

000
001
002
010
011
...
222

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

每个数字的"base"可能不同,是对应嵌套列表的大小。如果嵌套列表很大,"digit" 可以是一个非常大的数字。

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

这里是 Java 使用数组的数组实现,即 String[][]。如果需要,您可以轻松更改为 List<List<String>>List<String[]>

@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 来将 "digit" 重置为 0。

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

编辑:我在 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 循环或列表理解进一步清理它,但这留作 reader.

的练习

好吧,这是一个病态的例子,希望你知道 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 函数。