Как объединить два массива в C #? - PullRequest
228 голосов
/ 10 октября 2009
int[] x = new int [] { 1, 2, 3};
int[] y = new int [] { 4, 5 };

int[] z = // your answer here...

Debug.Assert(z.SequenceEqual(new int[] { 1, 2, 3, 4, 5 }));

-

Прямо сейчас я использую

int[] z = x.Concat(y).ToArray();

Есть ли более простой или более эффективный метод?

Ответы [ 22 ]

4 голосов
/ 15 января 2015

Более эффективно (быстрее) использовать Buffer.BlockCopy более Array.CopyTo,

int[] x = new int [] { 1, 2, 3};
int[] y = new int [] { 4, 5 };

int[] z = new int[x.Length + y.Length];
var byteIndex = x.Length * sizeof(int);
Buffer.BlockCopy(x, 0, z, 0, byteIndex);
Buffer.BlockCopy(y, 0, z, byteIndex, y.Length * sizeof(int));

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

Для 10 000 000 итераций примера в вопросе

Конкат занял 3088мс

Копирование заняло 1079мс

BlockCopy занял 603 мс

Если я изменю тестовые массивы на две последовательности от 0 до 99, тогда я получу результаты, подобные этим,

Конкат занял 45945мс

CopyTo заняло 2230мс

BlockCopy заняло 1689мс

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

Чтобы пояснить этот ответ, если производительность не имеет значения, или будет несколько итераций, выберите метод, который вы находите наиболее простым. Buffer.BlockCopy предлагает некоторую утилиту для преобразования типов, выходящую за рамки этого вопроса.

2 голосов
/ 10 октября 2009

Наиболее эффективной структурой с точки зрения оперативной памяти (и ЦП) для хранения объединенного массива будет специальный класс, который реализует IEnumerable (или, если хотите, даже наследуется от Array) и внутренне связывается с исходными массивами для чтения значений. AFAIK Concat делает именно это.

В вашем примере кода вы можете опустить .ToArray (), что сделает его более эффективным.

2 голосов
/ 10 октября 2009

Вы можете сделать это так, как вы упомянули, или, если вы хотите получить действительно руководство по этому поводу, вы можете свернуть свой собственный цикл:

        string[] one = new string[] { "a", "b" };
        string[] two = new string[] { "c", "d" };
        string[] three;

        three = new string[one.Length + two.Length];

        int idx = 0;

        for (int i = 0; i < one.Length; i++)
            three[idx++] = one[i];
        for (int j = 0; j < two.Length; j++)
            three[idx++] = two[j];
2 голосов
/ 22 августа 2013

Я нашел элегантное однострочное решение с использованием выражения LINQ или Lambda , оба работают одинаково (LINQ преобразуется в Lambda при компиляции программы). Решение работает для любого типа массива и для любого количества массивов.

Использование LINQ:

public static T[] ConcatArraysLinq<T>(params T[][] arrays)
{
    return (from array in arrays
            from arr in array
            select arr).ToArray();
}

Использование лямбды:

public static T[] ConcatArraysLambda<T>(params T[][] arrays)
{
    return arrays.SelectMany(array => array.Select(arr => arr)).ToArray();
}

Я предоставил оба по своему вкусу. С точки зрения производительности @ Решения Сергея Штейна или @ deepee1 * немного быстрее, а лямбда-выражение - самое медленное. Требуемое время зависит от типа (типов) элементов массива, но если нет миллионов вызовов, между методами нет существенной разницы.

1 голос
/ 11 октября 2018

Вот мой ответ:

int[] z = new List<string>()
    .Concat(a)
    .Concat(b)
    .Concat(c)
    .ToArray();

Этот метод может использоваться на уровне инициализации, например, для определения статической конкатенации статических массивов:

public static int[] a = new int [] { 1, 2, 3, 4, 5 };
public static int[] b = new int [] { 6, 7, 8 };
public static int[] c = new int [] { 9, 10 };

public static int[] z = new List<string>()
    .Concat(a)
    .Concat(b)
    .Concat(c)
    .ToArray();

Тем не менее, он имеет две оговорки, которые необходимо учитывать:

  • Метод Concat создает итератор для обоих массивов: он не создает новый массив, что делает его эффективным с точки зрения используемой памяти: однако последующее ToArray сведет на нет такое преимущество, поскольку фактически создаст новый массив и занять память для нового массива.
  • Как сказал @Джодрелл, Concat было бы довольно неэффективно для больших массивов: его следует использовать только для массивов среднего размера.

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

/// <summary>
/// Concatenates two or more arrays into a single one.
/// </summary>
public static T[] Concat<T>(params T[][] arrays)
{
    // return (from array in arrays from arr in array select arr).ToArray();

    var result = new T[arrays.Sum(a => a.Length)];
    int offset = 0;
    for (int x = 0; x < arrays.Length; x++)
    {
        arrays[x].CopyTo(result, offset);
        offset += arrays[x].Length;
    }
    return result;
}

или (для однострочных вентиляторов):

int[] z = (from arrays in new[] { a, b, c } from arr in arrays select arr).ToArray();

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

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

1 голос
/ 17 января 2017

Извините, что оживил старую ветку, но как на счет этого:

static IEnumerable<T> Merge<T>(params T[][] arrays)
{
    var merged = arrays.SelectMany(arr => arr);

    foreach (var t in merged)
        yield return t;
}

Тогда в вашем коде:

int[] x={1, 2, 3};
int[] y={4, 5, 6};

var z=Merge(x, y);  // 'z' is IEnumerable<T>

var za=z.ToArray(); // 'za' is int[]

Пока вы не вызовете .ToArray(), .ToList() или .ToDictionary(...), память не будет выделена, вы можете «построить свой запрос» и либо вызвать один из этих трех, чтобы выполнить его, либо просто просмотреть их все с помощью предложения foreach (var i in z){...}, которое возвращает элемент за раз из yield return t; выше ...

Вышеприведенную функцию можно превратить в расширение следующим образом:

static IEnumerable<T> Merge<T>(this T[] array1, T[] array2)
{
    var merged = array1.Concat(array2);

    foreach (var t in merged)
        yield return t;
}

Итак, в коде вы можете сделать что-то вроде:

int[] x1={1, 2, 3};
int[] x2={4, 5, 6};
int[] x3={7, 8};

var z=x1.Merge(x2).Merge(x3);   // 'z' is IEnumerable<T>

var za=z.ToArray(); // 'za' is int[]

Остальное такое же, как и раньше.

Еще одним улучшением этого было бы изменение T[] в IEnumerable<T> (так что params T[][] станет params IEnumerable<T>[]), чтобы эти функции принимали больше, чем просто массивы.

Надеюсь, это поможет.

1 голос
/ 10 октября 2009

Необходимо помнить, что при использовании LINQ вы используете отложенное выполнение. Все описанные здесь методы работают отлично, но они выполняются немедленно. Кроме того, функция Concat (), вероятно, оптимизирована таким образом, что вы не можете делать это самостоятельно (вызовы внутренних API, вызовы ОС и т. Д.). В любом случае, если вам действительно не нужно пытаться оптимизировать, вы находитесь на пути к «корню всего зла»;)

1 голос
/ 01 декабря 2013

Попробуйте следующее:

T[] r1 = new T[size1];
T[] r2 = new T[size2];

List<T> targetList = new List<T>(r1);
targetList.Concat(r2);
T[] targetArray = targetList.ToArray();
0 голосов
/ 10 октября 2009

Для int [] то, что вы сделали, выглядит хорошо для меня. Ответ astander также подойдет для List<int>.

0 голосов
/ 16 июня 2016

int [] x = new int [] {1, 2, 3}; int [] y = new int [] {4, 5};

int [] z = x.Union (y) .ToArray ();

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...