【问题标题】:Form a multidimensional cartesian product array形成多维笛卡尔积数组
【发布时间】:2021-07-16 10:24:07
【问题描述】:

我有一个为我生成笛卡尔积的函数

        var abcArray = new string[] { "a", "b", "c" };
        var xyzArray = new string[] { "1", "2", "3" };

        var combos = CartesianProductSmart(abcArray, xyzArray);
    private static string[][] CartesianProductSmart(string[] arr1, string[] arr2)
    {
        return arr1.SelectMany(s1 => arr2, (s1, s2) => new string[] { s1, s2 })
            .ToArray();
    }

我的输出如下:

a,1
a,2
a,3
b,1
b,2
b,3
c,1
c,2
c,3

我怎样才能把这个函数转换成更复杂的东西,比如形成多维数组:

array3 = [ {"A1", "B2", "C3"},
           {"A1", "B3", "C2"},
           {"A2", "B1", "C3"},
           {"A2", "B3", "C1"}, 
           {"A3", "B2", "C1"},
           {"A3", "B1", "C2"}
          ]

我想要的是只使用两个数组的每个元素并将其组成一个数组的笛卡尔积。 (我知道数组大小会因输入而异)。 到目前为止,我的想法是做笛卡尔积,将第一个元素分配为第一个笛卡尔积结果,然后遍历其余的笛卡尔积并通过检查是否没有重复添加到一个新数组中,然后只选择唯一成员,但这似乎效率很低。

【问题讨论】:

  • 对于智能和高效的方式,Eric Lippert 关于组合排列的博客文章必须很少。那应该像 3 条 4-5 篇博客文章链。这涵盖了大部分组合,并提供了很多很好的参考。它可能太闷了,但可能是一个很好的 raeding。
  • 您可以使用=> s1 + s2 代替=> new string[] { s1, s2 }(或使用=> s1.ToUpper() + s2

标签: c# linq combinatorics cartesian-product cartesian


【解决方案1】:

我的建议是,如果您使用复杂结构,则不应再使用标准结构,而应创建表示您建模的数据以及您可以对这些对象执行的操作的类。

显然您的程序具有Matrix 的概念(不确定您是否在英语数学中使用相同的术语)

一个 M 乘 N 的矩阵有 M 列和 N 行。

我不确定将您的abcarray 放在一个 3 x 1 的矩阵中是否明智,或者您是否想将它放在一个特殊的类中,您可以称之为 Vector。也许向量是 N 乘 1 矩阵的派生类?还是 1 乘 N?

无论如何,显然你可以对两个向量进行操作:

class Vector
{
    public Matrix<T> CartesianProduct(Vector<T> v)
    {
        return CartesianProduct(this, v);
    }
    public static Matrix<T> CartesianProduct(Vector<T> x, Vector<T> y) {...}

用法如下:

Vector<string> x = new Vector<string>(new string[] { "a", "b", "c" });
Vector<string> y = new Vector<string>(new string[] { "1", "2", "3" });
Matrix<string> m = x.CartesianProduct(y);

嗯,这对我来说已经足够整洁了,所以让我们继续这条路吧。

一个向量有一个Dimension。创建 Vector 对象时,您必须指定 Dimension。在 Vector 的生命周期内,您无法更改其 Dimension

一个向量有Coordinates,编号从0到Dimension-1。您可以获取和设置特定的坐标。为简单起见,我们使用索引来访问坐标。

您可以对向量执行的操作:加/减/负/乘/...这有点超出您的问题范围,所以我不会讨论这个。

class Vector<T> : IReadonlyCollection<T>
{
    private readonly T[] coordinates;

    public Vector(int dimension)
    {
        // TODO: check dimension > 0;
        this.coordinates = new T[dimension];
    }
    public Vector(IEnumerable<T> data)
    {
        // TODO: check data != null; check data contains at least one element
        this.Coordinates = data.ToArray();
    }

    // TODO: other constructors? ICloneable?
    // TODO: Add / Subtract / Multiply with a number?
    public T this[int index] 
    {
        get => this.coordinates[index];
        set => this.coordinates[index] = value;
    }
}

IReadOnlyCollection 的实现相当简单:

public int Count => this.coordinates.Length;
public Enumerator<T> GetEnumerator()
{
    return this.Coordinates.GetEnumerator();
}
IEnumerator Enumerable.GetEnumerator()
{
    return this.GetEnumerator();
}

对于初始化,以下扩展方法会很好。如果你不熟悉扩展方法,请阅读extension methods demystified

public static Vector<T> AsVector<T>(IEnumerable<T> source)
{
    return new Vector<T>(source.ToArray());
}

用法:

var abcArray = new string[] { "a", "b", "c" };
var abcVector = abcArray.AsVector();

我将矩阵设计为不可更改的二维数组。

class Matrix<T>
{
    private readonly T[,] elements;

    public Matrix(int columnCount, int rowCount)
    {
        this.elements = new T[columnCount, rowCount];
    }

    public Matrix(T[,] elements)
    {
        this.elements = elements;
    }

    public int RolumnCount => this.elements.GetLength[0];
    public int RowCount => this.elements.GetLength[1];

    // etc, invent several other useful operations on matrices.
    // addition, multiplication, linear transformation, ...
}

当然,如果你真的想将 Matrix 解释为二维数组:

public T[,] Elements => this.elements

最后是两个向量的笛卡尔积,返回一个矩阵。问题是,我们需要一个函数来“组合”一个 x 坐标和一个“y”坐标。对于一个数字向量,这可能是一个乘法,对于一个字符串向量,您要使用连接。

public static Matrix<T> CartesianProduct(Vector<T> vectorX, Vector<T> vectorY, 
    Func<T, T, T> combine)
{
    // TODO: input not null; correct dimension

    IEnumerable<T> combinations = vectorX.SelectMany(vectorY,
     (xCoordinate, yCoordinate) => combine(xCoordinate, yCoordinate);

    return new Matrix<T>(combinations);
}

Vector<string> abcArray = new Vector<string>(new string[] { "a", "b", "c" });
Vector<string> xyzArray = new Vector<string>(new string[] { "1", "2", "3" });
Matrix<string> m = Vector.CartesianProduct(abcArray, xyzArray, (x, y) => x + y);

【讨论】:

    【解决方案2】:

    使用扩展方法通过基于数组交换的 Heap 算法生成排列(来自 this Stackoverflow 答案),您可以计算 ["1", "2", "3"] 数组的排列和使用 LINQ 将它们与 ["a", "b", "c"] 数组合并。

    这里是排列的扩展方法:

    public static class IEnumerableExt {
        /// <summary>
        /// From StackOverflow answer https://stackoverflow.com/a/36634935/2557128
        /// EO: 2016-04-14
        /// Generator of all permutations of an array of anything.
        /// Base on Heap's Algorithm. See: https://en.wikipedia.org/wiki/Heap%27s_algorithm#cite_note-3
        /// Heap's algorithm to find all permutations. Non recursive, more efficient.
        /// </summary>
        /// <param name="items">Items to permute in each possible ways</param>
        /// <param name="funcExecuteAndTellIfShouldStop"></param>
        /// <returns>Return true if cancelled</returns>
        public static IEnumerable<T[]> Permutations<T>(this IEnumerable<T> itemsSrc) {
            T[] items;
            if (itemsSrc is T[] arrayOfItems)
                items = arrayOfItems;
            else
                items = itemsSrc.ToArray();
    
            /// <summary>
            /// Swap 2 elements of same type
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <param name="a"></param>
            /// <param name="b"></param>
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            static void Swap(ref T a, ref T b) {
                T temp = a;
                a = b;
                b = temp;
            }
    
            int countOfItem = items.Length;
            yield return items;
            if (countOfItem <= 1)
                yield break;
    
            var indexes = new int[countOfItem];
            for (int i = 1; i < countOfItem;) {
                if (indexes[i] < i) {
                    if ((i & 1) == 1) // test if i odd: if (i % 2 == 1)  ... more efficient ??? At least the same.
                        Swap(ref items[i], ref items[indexes[i]]);
                    else
                        Swap(ref items[i], ref items[0]);
    
                    yield return items;
    
                    indexes[i]++;
                    i = 1;
                }
                else
                    indexes[i++] = 0;
            }
        }
    }
    

    现在你可以:

    var ans1 = xyzArray.Permutations().Select(p => abcArray.Zip(p, (x,a) => x+a));
    

    如果您更喜欢数组数组,可以将其添加到 LINQ:

    var ans2 = xyzArray.Permutations().Select(p => abcArray.Zip(p, (x,a) => x+a).ToArray()).ToArray();
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-11-28
      • 2017-10-20
      • 2011-12-18
      • 2016-02-24
      • 2016-10-08
      • 2016-07-10
      • 2019-09-30
      • 1970-01-01
      相关资源
      最近更新 更多