Слияние двух массивов в.NET

Есть ли в.NET 2.0 встроенная функция, которая будет принимать два массива и объединять их в один массив?

Массивы имеют одинаковый тип. Я получаю эти массивы из широко используемой функции в моей базе кода и не могу изменить функцию, чтобы она возвращала данные в другом формате.

Я стараюсь не писать свою собственную функцию для достижения этой цели, если это возможно.

28 ответов

Решение

Если вы можете манипулировать одним из массивов, вы можете изменить его размер перед выполнением копирования:

T[] array1 = getOneArray();
T[] array2 = getAnotherArray();
int array1OriginalLength = array1.Length;
Array.Resize<T>(ref array1, array1OriginalLength + array2.Length);
Array.Copy(array2, 0, array1, array1OriginalLength, array2.Length);

В противном случае вы можете сделать новый массив

T[] array1 = getOneArray();
T[] array2 = getAnotherArray();
T[] newArray = new T[array1.Length + array2.Length];
Array.Copy(array1, newArray, array1.Length);
Array.Copy(array2, 0, newArray, array1.Length, array2.Length);

Подробнее о доступных методах Array на MSDN.

В C# 3.0 вы можете использовать метод Concat LINQ, чтобы сделать это легко:

int[] front = { 1, 2, 3, 4 };
int[] back = { 5, 6, 7, 8 };
int[] combined = front.Concat(back).ToArray();

В C# 2.0 у вас нет такого прямого пути, но Array.Copy, вероятно, является лучшим решением:

int[] front = { 1, 2, 3, 4 };
int[] back = { 5, 6, 7, 8 };

int[] combined = new int[front.Length + back.Length];
Array.Copy(front, combined, front.Length);
Array.Copy(back, 0, combined, front.Length, back.Length);

Это может быть легко использовано для реализации вашей собственной версии Concat,

Используйте LINQ:

var arr1 = new[] { 1, 2, 3, 4, 5 };
var arr2 = new[] { 6, 7, 8, 9, 0 };
var arr = arr1.Union(arr2).ToArray();

Если вы не хотите удалять дубликаты, попробуйте это

Используйте LINQ:

var arr1 = new[] { 1, 2, 3, 4, 5 };
var arr2 = new[] { 6, 7, 8, 9, 0 };
var arr = arr1.Concat(arr2).ToArray();

Во-первых, обязательно задайте себе вопрос "Должен ли я действительно использовать массив здесь"?

Если вы не создаете что-то, где скорость имеет первостепенное значение, типизированный список, как List<int> это, вероятно, путь. Единственный раз, когда я использую массивы, это байтовые массивы при отправке данных по сети. Кроме этого, я никогда не прикасаюсь к ним.

Проще было бы просто использовать LINQ:

var array = new string[] { "test" }.ToList();
var array1 = new string[] { "test" }.ToList();
array.AddRange(array1);
var result = array.ToArray();

Сначала конвертируйте массивы в списки и объединяйте их... После этого просто конвертируйте список обратно в массив:)

Это то, что я придумал. Работает для переменного числа массивов.

public static T[] ConcatArrays<T>(params T[][] args)
    {
        if (args == null)
            throw new ArgumentNullException();

        var offset = 0;
        var newLength = args.Sum(arr => arr.Length); 
        var newArray = new T[newLength];

        foreach (var arr in args)
        {
            Buffer.BlockCopy(arr, 0, newArray, offset, arr.Length);
            offset += arr.Length;
        }

        return newArray;
    }

...

var header = new byte[] { 0, 1, 2};
var data = new byte[] { 3, 4, 5, 6 };
var checksum = new byte[] {7, 0};
var newArray = ConcatArrays(header, data, checksum);
//output byte[9] { 0, 1, 2, 3, 4, 5, 6, 7, 0 }

Я думаю, что вы можете использовать Array.Copy для этого. Он принимает исходный индекс и целевой индекс, так что вы должны иметь возможность добавлять один массив к другому. Если вам нужно выполнить более сложную задачу, чем просто добавить одно к другому, это может оказаться не подходящим инструментом для вас.

Каждый уже высказал свое мнение, но я думаю, что это более читабельно, чем подход "использовать как метод расширения":

var arr1 = new[] { 1, 2, 3, 4, 5 };
var arr2 = new[] { 6, 7, 8, 9, 0 };
var arr = Queryable.Concat(arr1, arr2).ToArray();

Однако его можно использовать только при объединении двух массивов.

Предполагая, что массив назначения имеет достаточно места, Array.Copy() буду работать. Вы также можете попробовать использовать List<T> И его .AddRange() метод.

Начиная с C# 12, вы можете использовать выражения коллекции и оператор распространения. .., например:

      int[] a = [1, 2, 3];
int[] b = [4, 5, 6];
int[] c = [..a, ..b];

Примечание. Эта функция доступна только в режиме предварительной версии, но ее официальный выпуск запланирован на ноябрь 2023 года.

Лично я предпочитаю свои собственные языковые расширения, которые я добавляю или удаляю по желанию для быстрого создания прототипов.

Ниже приведен пример для строк.

//resides in IEnumerableStringExtensions.cs
public static class IEnumerableStringExtensions
{
   public static IEnumerable<string> Append(this string[] arrayInitial, string[] arrayToAppend)
   {
       string[] ret = new string[arrayInitial.Length + arrayToAppend.Length];
       arrayInitial.CopyTo(ret, 0);
       arrayToAppend.CopyTo(ret, arrayInitial.Length);

       return ret;
   }
}

Это намного быстрее, чем LINQ и Concat. Еще быстрее, использует кастом IEnumerable Тип-оболочка, которая хранит ссылки / указатели переданных массивов и позволяет перебирать всю коллекцию, как если бы это был обычный массив. (Полезно в HPC, обработка графики, рендеринг графики...)

Ваш код:

var someStringArray = new[]{"a", "b", "c"};
var someStringArray2 = new[]{"d", "e", "f"};
someStringArray.Append(someStringArray2 ); //contains a,b,c,d,e,f

Полный код и дженериковую версию см. По адресу: https://gist.github.com/lsauer/7919764

Примечание: это возвращает нерасширенный объект IEnumerable. Возвращать расширенный объект немного медленнее.

Я скомпилировал такие расширения с 2002 года, и многие из них были полезны людям, работающим с CodeProject и Stackru. Я выпущу их в ближайшее время и выложу ссылку здесь.

Просто чтобы это было отмечено как опция: если массивы, с которыми вы работаете, имеют примитивный тип - Boolean (bool), Char, SByte, Byte, Int16 (short), UInt16, Int32 (int), UInt32, Int64 (long), UInt64, IntPtr, UIntPtr, Single или Double - тогда вы можете (или должны?) Попробовать использовать Buffer.BlockCopy. Согласно странице MSDN для класса Buffer:

Этот класс обеспечивает лучшую производительность для управления примитивными типами, чем аналогичные методы в классе System.Array.

Используя пример C# 2.0 из ответа @OwenP в качестве отправной точки, он будет работать следующим образом:

int[] front = { 1, 2, 3, 4 };
int[] back = { 5, 6, 7, 8 };

int[] combined = new int[front.Length + back.Length];
Buffer.BlockCopy(front, 0, combined, 0, front.Length);
Buffer.BlockCopy(back, 0, combined, front.Length, back.Length);

Между синтаксисом почти нет различий Buffer.BlockCopy и Array.Copy что @OwenP использовал, но это должно быть быстрее (хотя бы немного).

Мне нужно решение, чтобы объединить неизвестное количество массивов.

Удивлен, никто другой не предоставил решение, используя SelectMany с params,

 private static T[] Combine<T>(params IEnumerable<T>[] items) =>
                    items.SelectMany(i => i).Distinct().ToArray();

Если вы не хотите, чтобы отдельные элементы просто удалили отдельные.

 public string[] Reds = new [] { "Red", "Crimson", "TrafficLightRed" };
 public string[] Greens = new [] { "Green", "LimeGreen" };
 public string[] Blues = new [] { "Blue", "SkyBlue", "Navy" };

 public string[] Colors = Combine(Reds, Greens, Blues);

Примечание. Определенно нет никаких гарантий при заказе.

В случае, если кто-то ищет, как объединить два байтовых массива изображения:

        private void LoadImage()
        {
            string src = string.empty;
            byte[] mergedImageData = new byte[0];

            mergedImageData = MergeTwoImageByteArrays(watermarkByteArray, backgroundImageByteArray);
            src = "data:image/png;base64," + Convert.ToBase64String(mergedImageData);
            MyImage.ImageUrl = src;
        }

        private byte[] MergeTwoImageByteArrays(byte[] imageBytes, byte[] imageBaseBytes)
        {
            byte[] mergedImageData = new byte[0];
            using (var msBase = new MemoryStream(imageBaseBytes))
            {
                System.Drawing.Image imgBase = System.Drawing.Image.FromStream(msBase);
                Graphics gBase = Graphics.FromImage(imgBase);
                using (var msInfo = new MemoryStream(imageBytes))
                {
                    System.Drawing.Image imgInfo = System.Drawing.Image.FromStream(msInfo);
                    Graphics gInfo = Graphics.FromImage(imgInfo);
                    gBase.DrawImage(imgInfo, new Point(0, 0));
                    //imgBase.Save(Server.MapPath("_____testImg.png"), ImageFormat.Png);
                    MemoryStream mergedImageStream = new MemoryStream();
                    imgBase.Save(mergedImageStream, ImageFormat.Png);
                    mergedImageData = mergedImageStream.ToArray();
                    mergedImageStream.Close();
                }
            }
            return mergedImageData;
        }

Если у вас есть исходные массивы в самом массиве, вы можете использовать SelectMany:

var arrays = new[]{new[]{1, 2, 3}, new[]{4, 5, 6}};
var combined = arrays.SelectMany(a => a).ToArray();
foreach (var v in combined) Console.WriteLine(v);   

дает

1
2
3
4
5
6

Вероятно, это не самый быстрый метод, но может подойти в зависимости от сценария использования.

Вот простой пример использования Array.CopyTo. Я думаю, что он отвечает на ваш вопрос и дает пример использования CopyTo - я всегда озадачен, когда мне нужно использовать эту функцию, потому что помощь немного неясна - индекс - это позиция в целевом массиве, где происходит вставка.

int[] xSrc1 = new int[3] { 0, 1, 2 };
int[] xSrc2 = new int[5] { 3, 4, 5, 6 , 7 };

int[] xAll = new int[xSrc1.Length + xSrc2.Length];
xSrc1.CopyTo(xAll, 0);
xSrc2.CopyTo(xAll, xSrc1.Length);

Я думаю, вы не можете сделать это намного проще.

Начиная с .NET 5, теперь у нас есть AllocateUnitializedArray , который, возможно, может добавить дополнительное (небольшое) улучшение производительности для предлагаемых решений:

      public static T[] ConcatArrays<T>(IEnumerable<T[]> arrays)
{
    var result = GC.AllocateUnitializedArray<T>(arrays.Sum(a => a.Length));
    var offset = 0;
    foreach (var a in arrays)
    {
        a.CopyTo(result, offset);
        offset += a.Length;
    }
    return result;
}

Я хотел найти подход без использования каких-либо библиотек или функций, кроме самих массивов.

Первые два примера в основном предназначены для чтения логики с нуля, но мне также интересно, могут ли быть изменения производительности в зависимости от ситуации.

Третий пример является наиболее практичным выбором.

      // Two for-loops
private static int[] MergedArrays_1(int[] a, int[] b)
{
    int[] result = new int[a.Length + b.Length];
    for (int i = 0; i < a.Length; i++)
    {
        result[i] = a[i];
    }
    for (int i = a.Length; i < result.Length; i++)
    {
        result[i] = b[i - a.Length];
    }
    return result;
}

// One for-loop
private static int[] MergedArrays_2(int[] a, int[] b)
{
    int[] results = new int[a.Length + b.Length];
    for (int i = 0; i < results.Length; i++)
    {
        results[i] = (i < a.Length) ? a[i] : b[i - a.Length];
    }
    return results;
}

// Array Method
private static int[] MergedArrays_3(int[] a, int[] b)
{
    int[] results = new int[a.Length + b.Length];
    a.CopyTo(results, 0);
    b.CopyTo(results, a.Length);
    return results;
}

Наконец, я сделал четвертый пример, в котором можно объединить несколько массивов с помощью ключевого слова params.

      int[] result = MultipleMergedArrays(arrayOne, arrayTwo, arrayThree);
      private static int[] MultipleMergedArrays(params int[][] a)
{
    // Get Length
    int resultsLength = 0;
    for (int row = 0; row < a.GetLength(0); row++)
    {
        resultsLength += a.Length;
    }

    // Initialize
    int[] results = new int[resultsLength];

    // Add Items
    int index = 0;
    for (int row = 0; row < a.GetLength(0); row++)
    {
        a[row].CopyTo(results, index);
        index += a[row].Length;
    }
    return results;
}

То, как это работает при использовании параметров, заключается в том, что одномерные массивы передаются в зубчатый массив.

GetLength(0) возвращает количество массивов, содержащихся в зубчатом массиве.

Код сначала подсчитывает длину всех массивов, затем инициализирует новый массив на основе этого размера и начинает добавлять целые массивы в новый массив результатов с помощью метода CopyTo(), при этом добавляя длину каждого добавленного массива в индексный счетчик.

PS: Иногда необходимо удалить пустые элементы или определенные элементы из массивов при объединении.

      private static int[] RemoveEmpty(int[] array)
{
    int count = 0;
    for (int i = 0; i < array.Length; i++)
    {
        if (array[i] == 0) count++;
    }

    int[] result = new int[array.Length - count];

    count = 0;
    for (int i = 0; i < array.Length; i++)
    {
        if (array[i] == 0) continue;
        result[count] = array[i];
        count++;
    }

    return result;
}

Эту функцию можно комбинировать с вышеперечисленными.

Он принимает массив, подсчитывает количество элементов, совпадающих с нулем. И создает новый массив нужного размера. Затем счетчик перерабатывается и используется в качестве индекса для того, куда поместить значения входного массива в новый и меньший массив результатов. Когда элемент соответствует нулю, он пропускает остальную часть кода в этом раунде цикла и переходит к следующему раунду без увеличения целочисленного счетчика.

Создан и метод расширения для обработки нуля

public static class IEnumerableExtenions
{
    public static IEnumerable<T> UnionIfNotNull<T>(this IEnumerable<T> list1, IEnumerable<T> list2)
    {
        if (list1 != null && list2 != null)
            return list1.Union(list2);
        else if (list1 != null)
            return list1;
        else if (list2 != null)
            return list2;
        else return null;
    }
}
int [] SouceArray1 = new int[] {2,1,3};
int [] SourceArray2 = new int[] {4,5,6};
int [] targetArray = new int [SouceArray1.Length + SourceArray2.Length];
SouceArray1.CopyTo(targetArray,0);
SourceArray2.CopyTo(targetArray,SouceArray1.Length) ; 
foreach (int i in targetArray) Console.WriteLine(i + " ");  

Используя приведенный выше код, два массива можно легко объединить.

Я предполагаю, что вы используете свои собственные типы массивов, а не встроенные массивы.NET:

public string[] merge(input1, input2)
{
    string[] output = new string[input1.length + input2.length];
    for(int i = 0; i < output.length; i++)
    {
        if (i >= input1.length)
            output[i] = input2[i-input1.length];
        else
            output[i] = input1[i];
    }
    return output;
}

Другой способ сделать это - использовать встроенный класс ArrayList.

public ArrayList merge(input1, input2)
{
    Arraylist output = new ArrayList();
    foreach(string val in input1)
        output.add(val);
    foreach(string val in input2)
        output.add(val);
    return output;
}

Оба примера являются C#.

Для эффективного решения вы можете попробовать следующее:

              //
    ///<summary>Join two arrays together. At least one array must exist.</summary>
    public static T[] Append<T>( this T[] a1, T[] a2 ) {
        if ( null == a1 && null == a2 ) return null;
        int l1 = 0;
        if ( null != a1 ) l1 = a1.Length;
        int l2 = 0;
        if ( null != a2 ) l2 = a2.Length;
        var b = new T[ l1 + l2 ];
        if ( null != a1 ) Array.Copy( a1, b, l1 );
        if ( null != a2 ) Array.Copy( a2, 0, b, l1, l2 );
        return b;
    }
              string[] names1 = new string[] { "Ava", "Emma", "Olivia" };
        string[] names2 = new string[] { "Olivia", "Sophia", "Emma" };
        List<string> arr = new List<string>(names1.Length + names2.Length);
        arr.AddRange(names1);
        arr.AddRange(names2);
        string[] result = arr.Distinct().ToArray();
        foreach(string str in result)
        {
            Console.WriteLine(str.ToString());
        }
     
        Console.ReadLine();

Попробуй это:

ArrayLIst al = new ArrayList();
al.AddRange(array_1);
al.AddRange(array_2);
al.AddRange(array_3);
array_4 = al.ToArray();

Простой код для объединения нескольких массивов:

      string[] arr1 = ...
string[] arr2 = ...
string[] arr3 = ...    
List<string> arr = new List<string>(arr1.Length + arr2.Length + arr3.Length);
arr.AddRange(arr1);
arr.AddRange(arr2);
arr.AddRange(arr3);
string[] result = arr.ToArray();

Это еще один способ сделать это :)

      public static void ArrayPush<T>(ref T[] table, object value)
{
    Array.Resize(ref table, table.Length + 1); // Resizing the array for the cloned length (+-) (+1)
    table.SetValue(value, table.Length - 1); // Setting the value for the new element
}

public static void MergeArrays<T>(ref T[] tableOne, T[] tableTwo) {
    foreach(var element in tableTwo) {
        ArrayPush(ref tableOne, element);
    }
}

Вот фрагмент / пример

Этот код будет работать для всех случаев:

int[] a1 ={3,4,5,6};
int[] a2 = {4,7,9};
int i = a1.Length-1;
int j = a2.Length-1;
int resultIndex=  i+j+1;
Array.Resize(ref a2, a1.Length +a2.Length);
while(resultIndex >=0)
{
    if(i != 0 && j !=0)
    {
        if(a1[i] > a2[j])
        {
            a2[resultIndex--] = a[i--];
        }
        else
        {
            a2[resultIndex--] = a[j--];
        }
    }
    else if(i>=0 && j<=0)
    { 
        a2[resultIndex--] = a[i--];
    }
    else if(j>=0 && i <=0)
    {
       a2[resultIndex--] = a[j--];
    }
}
Другие вопросы по тегам