获取一个列表,其中列表1中的每个元素都与列表2中的某个元素配对。

huangapple go评论45阅读模式
英文:

How would I get a list of pairs where every element from list1 is paired with some element from list2?

问题

我有一个问题,我试图通过迭代而不是递归来解决,但尽管看起来很容易,但解决方案似乎一直逃脱我。

假设我有两个列表

l1 = [a, b, c]
l2 = [1, 2, 3]

我想要一个列表,其中l1中的每个元素都与l2中的某个元素配对。如下所示:

[
[(a, 1), (b, 1), (c, 1)],
[(a, 1), (b, 1), (c, 2)],
[(a, 1), (b, 1), (c, 3)],
[(a, 1), (b, 2), (c, 1)],
[(a, 1), (b, 2), (c, 2)],
[(a, 1), (b, 2), (c, 3)],
[(a, 1), (b, 3), (c, 1)],
[(a, 1), (b, 3), (c, 2)],
[(a, 1), (b, 3), (c, 3)],
...
]

请注意,这与简单地获得交叉产品(笛卡尔积)有点不同。

像这样做不太适用:

for i in l1:
  for j in l2:
    ...

这里不太适用,因为一旦你有了(a,1)这样的一对,你必须跳到b,而不是继续到(a,2)。

从外观上看,似乎应该不难构造循环以获得这个结果,但我不太能立刻想出来。我为你提供了一个用Python编写的递归解决方案供参考。

l1 = ['a', 'b', 'c']
l2 = [1, 2, 3]
l3 = []

def makepair(res, cur, l1, l2):
    if l1 == []:
        res.append(cur)
    else:
        for i in l2:
            temp = cur[:]
            temp.append((l1[0], i))
            makepair(res, temp, l1[1:], l2)

makepair(l3, [], l1, l2)
for p in l3:
    print(p)

上述代码基本上打印了我解释的示例。有人能帮我以迭代的方式编写这个吗?我不关心使用哪种编程语言。

英文:

So I have this problem in my head that I'm trying to solve iteratively instead of recursively, but the solution seems to elude me despite it seemingly being so easy.

Let's say I have two lists

l1 = [a,b,c]
l2 = [1,2,3]

I want a list, where every element from l1 is paired with some element from l2. So it'll be like below:

[
[(a,1), (b,1), (c,1)],
[(a,1), (b,1), (c,2)],
[(a,1), (b,1), (c,3)],
[(a,1), (b,2), (c,1)],
[(a,1), (b,2), (c,2)],
[(a,1), (b,2), (c,3)],
[(a,1), (b,3), (c,1)],
[(a,1), (b,3), (c,2)],
[(a,1), (b,3), (c,3)],
...
]

Note that this is a bit different than simply getting a cross product (cartesian product).

Doing something like

for i in l1:
  for j in l2:
    ...

doesn't quite work here because once you have the (a,1) pair for example, you have to jump to b instead of continuing to (a,2).

From the looks of it, it seems like it shouldn't be too difficult to formulate loops to make this result, but it's not coming to me immediately. I did make a recursive solution in python for your reference though.

l1 = ['a','b','c']
l2 = [1,2,3]
l3 = []

def makepair(res,cur,l1,l2):
    if(l1==[]):
        res.append(cur)
    else:
        for i in l2:
            temp = cur[:]
            temp.append((l1[0],i))
            makepair(res,temp,l1[1:],l2)

makepair(l3,[],l1,l2)
for p in l3:
    print(p)

The above code basically prints the example I explained. Can someone help me write this iteratively? I don't care about which language.

答案1

得分: 2

请注意,这与仅仅获得一个叉积(笛卡尔积)有一些不同。

实际上,它确切地是list2list2list2的笛卡尔积,使用list1作为索引。

请注意,叉积笛卡尔积没有太多关联。在Python中,您可以使用标准库通过itertools.product获得笛卡尔积。要获得叉积,使用numpy库

使用itertools.product

from itertools import product

list1 = ['a','b','c']
list2 = [1,2,3]

print( [list(zip(list1, p)) for p in product(list2, repeat=len(list1))] )
# [[('a', 1), ('b', 1), ('c', 1)],
#  [('a', 1), ('b', 1), ('c', 2)],
#  [('a', 1), ('b', 1), ('c', 3)],
#  [('a', 1), ('b', 2), ('c', 1)],
#  ...,
#  [('a', 3), ('b', 3), ('c', 3)]]

使用for循环

当然,如果list1中的元素数量较小且恒定,Python的itertools.product等同于嵌套的简单循环:

def indexed_cartesian_product(l1, l2):
    a, b, c = l1
    for x in l2:
        for y in l2:
            for z in l2:
                yield ((a, x), (b, y), (c, z))

print(list(indexed_cartesian_product('abc', [1,2,3]))
# [(('a', 1), ('b', 1), ('c', 1)),
#  (('a', 1), ('b', 1), ('c', 2)),
#  (('a', 1), ('b', 1), ('c', 3)),
#  (('a', 1), ('b', 2), ('c', 1)),
#  ...,
#  (('a', 3), ('b', 3), ('c', 3))]
英文:

> Note that this is a bit different than simply getting a cross product (cartesian product).

Actually, it is exactly a Cartesian product, of list2 with list2 with list2, using list1 as indices.

Note that cross product and Cartesian product are pretty unrelated. In python, you can get the Cartesian product with the standard library, using itertools.product. For the cross product, use the numpy library.

Using itertools.product

from itertools import product

list1 = ['a','b','c']
list2 = [1,2,3]

print( [list(zip(list1, p)) for p in product(list2, repeat=len(list1))] )
# [[('a', 1), ('b', 1), ('c', 1)],
#  [('a', 1), ('b', 1), ('c', 2)],
#  [('a', 1), ('b', 1), ('c', 3)],
#  [('a', 1), ('b', 2), ('c', 1)],
#  ...,
#  [('a', 3), ('b', 3), ('c', 3)]]

Using for-loops

Of course, if the number of elements in list1 is small and constant, python's itertools.product is equivalent to nested simple loops:

def indexed_cartesian_product(l1, l2):
    a, b, c = l1
    for x in l2:
        for y in l2:
            for z in l2:
                yield ((a, x), (b, y), (c, z))

print(list(indexed_cartesian_product('abc', [1,2,3])))
# [(('a', 1), ('b', 1), ('c', 1)),
#  (('a', 1), ('b', 1), ('c', 2)),
#  (('a', 1), ('b', 1), ('c', 3)),
#  (('a', 1), ('b', 2), ('c', 1)),
#  ...,
#  (('a', 3), ('b', 3), ('c', 3))]

答案2

得分: 1

从一个空行开始然后逐列构建Python实现):

l1 = ['a','b','c']
l2 = [1,2,3]

l3 = [[]]
for column in l1:
    l3 = [
        row + [(column, value)]
        for row in l3
        for value in l2
    ]

for p in l3:
    print(p)

同样的方式,但更像其他编程语言的风格:

l3 = [[]]
for column in l1:
    new_l3 = []
    for row in l3:
        for value in l2:
            new_row = row + [(column, value)]
            new_l3.append(new_row)
    l3 = new_l3

使用itertools的额外解决方案(生成元组对的迭代器,而不是一组一组对的列表,但如果确实需要可以很容易调整):

from itertools import repeat, product

l1 = ['a','b','c']
l2 = [1,2,3]

result = product(*map(zip, map(repeat, l1), repeat(l2)))

for p in result:
    print(p)
英文:

Starting with just an empty row, then building up from that one column at a time (Python implementation):

l1 = ['a','b','c']
l2 = [1,2,3]

l3 = [[]]
for column in l1:
    l3 = [
        row + [(column, value)]
        for row in l3
        for value in l2
    ]

for p in l3:
    print(p)

Same but written more in the style of other languages:

l3 = [[]]
for column in l1:
    new_l3 = []
    for row in l3:
        for value in l2:
            new_row = row + [(column, value)]
            new_l3.append(new_row)
    l3 = new_l3

Bonus solution using itertools (producing an iterator of tuples of pairs instead of a list of list of pairs, but that's easy to adjust if really necessary.

from itertools import repeat, product

l1 = ['a','b','c']
l2 = [1,2,3]

result = product(*map(zip, map(repeat, l1), repeat(l2)))

for p in result:
    print(p)

答案3

得分: 1

I wrote up a solution that basically translates the recursion into a stack. After all, all recursions run on a stack. (Link if you want to play with it yourself)

l1 = ['a','b','c']
l2 = [1,2,3]
l3 = []

stack = [[]]
while(len(stack)>0):
    check = stack.pop()
    if (len(check) == len(l1)):
        l3.append(check)
    else:
        for e in l2:
            stack.append(check+[(l1[len(check)],e)])
print(l3)

If we really wanted to save memory, I think this could be done without building the stack like this though. But considering the stack would be at most as big as the resulting list, therefore needing that much space anyway, I guess it's not a priority in my problem statement. But if we change up the problem a little bit and say we are checking if a "specific" combination exists, then the space can be wasteful. After all, building the entire combination will create a size of (len_l2)^(len_l1). Because this is exponential, it can get nasty really fast, so I think it's worth considering clever solutions that won't build the stack like this. (Note, the stack at each point should be smaller than (len_l2)^(len_l1), but I don't know how much smaller. Not sure how much smaller it would be on average, and also not sure how much smaller it is at its worst point. But I think it'll still be exponential)

An example scenario for finding a "specific" case might be if both l1 and l2 are made of numbers, and you are getting the sum of (each element in l1)*(some element in l2), and you must check whether there is some combination where the sum matches a specific number. In such a case, solutions using stuff like itertools.product start to become less useful because if you have to iterate through so much data, ideally you want to break the moment you find your specific case instead of building all possible options first and then iterating through them to see if the case exists.

I also made a pretty messy solution that doesn't build a stack. I did this in C++ instead of python because my original goal was to do this idea in C++ anyways. (Just initially used

英文:

I wrote up a solution that basically translates the recursion into a stack. Afterall, all recursions run on a stack. (Link if you want to play with it yourself)

l1 = ['a','b','c']
l2 = [1,2,3]
l3 = []

stack = [[]]
while(len(stack)>0):
    check = stack.pop()
    if (len(check) == len(l1)):
        l3.append(check)
    else:
        for e in l2:
            stack.append(check+[(l1[len(check)],e)])
print(l3)

If we really wanted to save memory, I think this could be done without building the stack like this though. But considering the stack would be at most as big as the resulting list, therefore needing that much space anyway, I guess it's not a priority in my problem statement. But if we change up the problem a little bit and say we are checking if a "specific" combination exists, then the space can be wasteful. Afterall, building the entire combination will create a size of (len_l2)^(len_l1). Because this is exponential, it can get nasty really fast, so I think it's worth considering clever solutions that won't build the stack like this. (Note, the stack at each point should be smaller than (len_l2)^(len_l1), but I don't know how much smaller. Not sure how much smaller it would be on average, and also not sure how much smaller it is at its worst point. But I think it'll still be exponential)

An example scenario for finding a "specific" case might be if both l1 and l2 are made of numbers, and you are getting the sum of (each element in l1)*(some element in l2), and you must check whether there is some combination where the sum matches a specific number. In such a case, solutions using stuff like itertools.product start to become less useful because if you have to iterate through so much data, ideally you want to break the moment you find your specific case instead of building all possible options first and then iterating through them to see if the case exists.

I also made a pretty messy solution that doesn't build a stack. I did this in C++ instead of python because my original goal was to do this idea in C++ anyways. (Just initially used python for better readability)

#include <iostream>
#include <bits/stdc++.h>

using namespace std;

//cur will be in the form of [0,1,2], and would need to be transformed to [(a,1),(b,2),(c,3)]
void add_to_l3(vector<vector<pair<char,int>>> &l3, vector<char> &l1, vector<int> &l2, vector<int> &cur) {
    vector<pair<char,int>> paired_cur(l1.size()); 
    for(int x=0; x<l1.size(); x++){
        paired_cur[x] = {l1[x], l2[cur[x]]};
    }
    l3.push_back(paired_cur);
}

int main()
{
    vector<char> l1 = {'a','b','c'};
    vector<int> l2 = {1, 2, 3};
    vector<vector<pair<char,int>>> l3;
    vector<int> cur(l1.size(), 0);
    //cur[i] stores the index of the number at l2 that would be paired with l1[i]
    int i = l1.size()-1;
    //Start with the base case of [(a,1),(b,1),(c,1)] and keep the pointer at the back of the array pointing at (c,1)
    //We iterate through l2's spot at the pointer such as [(a,1),(b,1),(c,2)] until we can't any more (which is at [(a,1),(b,1),(c,3)])
    //Afterwards, we decrement the pointer and notify that we decremented the pointer. We would now be pointing at (b,1)
    //If we can increment the l2's spot there, increment it, and then reset all the l2's spot for every element in the right of that to the base case. Start the pointer at the back of the array again.
    //If we couldn't increment the L2's spot, keep decrementing the pointer until we find the location that could increment l2's spot, and then reset all l2's spot for elements to the right of it.
    //Do this until we run out of spaces to decrement the pointer.
    add_to_l3(l3,l1,l2,cur);
    bool back=false;
    //This boolean checks if we decremented the pointer
    while(i>=0) {
        if(cur[i] < l2.size()-1) {
            cur[i]++;
            if(back==true) {
                for(i=i+1; i<l1.size(); i++) {
                    cur[i]=0;
                }
                back=false;
                i=l1.size()-1;
            }
            //cout << i << endl;
            add_to_l3(l3,l1,l2,cur);
        }
        else {
            i--;
            back=true;
        }
    }
    for (auto &e : l3) {
        for (auto &p : e) {
            cout << '(' << p.first << ',' << p.second << ')';
        }
        cout << endl;
    }
    return 0;
}

The idea behind this is that we start with

[(a,1),(b,1),(c,1)]

and start by pointing at (c,1).

Increment the l2 part of the pair until we can't like so:

[(a,1),(b,1),(c,2)] and [(a,1),(b,1),(c,3)]

Then we backtrack the pointer to point at (b,1) and increment the l2 part, or if that is also maxed out (because it is (b,3)), we keep backtracking until we reach a location where we can increment the l2 part. Once we increment the l2 part, we reset all the l2 part to the right of it to the base case.

So when we are pointing at (b,1), that gets changed to (b,2). And then (c,3) gets reset to (c,1). Therefore, we have [(a,1),(b,2),(c,1)]

It's messy but this allows us to be holding onto just one set of combination at a time, while also having full control of observing each combination at the moment we reach it.

Please let me know if you have any feedback of making this simpler

huangapple
  • 本文由 发表于 2023年2月8日 19:55:52
  • 转载请务必保留本文链接:https://go.coder-hub.com/75385452.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定