Слияние двух массивов в.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);
В 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--];
}
}