Объединение нескольких списков с "выталкивающими" элементами переменной длины из каждого

Я хотел бы отсортировать несколько списков (их переменное число) в один список, но с сохранением определенного порядка. Например:

List A: { 1,2,3,4,5 }
List B: { 6,7,8 }
List C: { 9,10,11,12 }

Result List: { 1,6,9,2,7,10,3,8,11,4,12,5 }

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

7 ответов

Решение

Для более гибкого использования

 public static string MergeArrays(params IList<int>[] items)
    {

        var result = new List<int>();
        for (var i = 0; i < items.Max(x => x.Count); i++)
            result.AddRange(from rowList in items where rowList.Count > i select rowList[i]);

        return string.Join(",", result);
    }

,

        var a = new List<int>() { 1, 2, 3, 4, 5 };
        var b = new List<int>() { 6, 7, 8 };
        var c = new List<int>() { 9, 10, 11, 12, 0, 2, 1 };

        var r = MergeArrays(a, b, c);

Я предлагаю использовать IEnumerator<T> перечислять списки, когда они имеют элементы:

private static IEnumerable<T> Merge<T>(params IEnumerable<T>[] sources) {
  List<IEnumerator<T>> enums = sources
    .Select(source => source.GetEnumerator())
    .ToList();

  try {
    while (enums.Any()) {
      for (int i = 0; i < enums.Count;)
        if (enums[i].MoveNext()) {
          yield return enums[i].Current;

          i += 1;
        }
        else {
          // exhausted, let's remove enumerator
          enums[i].Dispose();
          enums.RemoveAt(i);
        }
    }
  }
  finally {
    foreach (var en in enums)
      en.Dispose();
  }
}

Тестовое задание

List<int> A = new List<int>() { 1, 2, 3, 4, 5 };
List<int> B = new List<int>() { 6, 7, 8 };
List<int> C = new List<int>() { 9, 10, 11, 12 };

var result = Merge(A, B, C)
  .ToList();

Console.Write(string.Join(", ", result));

Результат

1, 6, 9, 2, 7, 10, 3, 8, 11, 4, 12, 5

По моему мнению, нет смысла чрезмерно усложнять это, почему бы не использовать простой цикл for, чтобы выполнить то, что вам нужно?

List<int> list1 = new List<int> { 1, 2, 3, 4, 5 };
List<int> list2 = new List<int> { 6, 7, 8 };
List<int> list3 = new List<int> { 9, 10, 11, 12 };
List<int> resultList = new List<int>();

for (int i = 0; i < list1.Count || i < list2.Count || i < list3.Count; i++)
{
    if (i < list1.Count) resultList.Add(list1[i]);
    if (i < list2.Count) resultList.Add(list2[i]);
    if (i < list3.Count) resultList.Add(list3[i]);
}

Результат: 1,6,9,2,7,10,3,8,11,4,12,5

Вот довольно простой способ. В любом случае было весело писать.
Нет, это не самое лучшее, но это работает, и вы можете расширить его в соответствии с вашими потребностями использования List<List<int>> очень легко.

//Using arrays for simplicity, you get the idea.
int[] A = { 1, 2, 3, 4, 5 };
int[] B = { 6, 7, 8 };
int[] C = { 9, 10, 11, 12 };

List<int> ResultSet = new List<int>();

//Determine this somehow. I'm doing this for simplicity.
int longest = 5; 

for (int i = 0; i < longest; i++)
{
    if (i < A.Length)
        ResultSet.Add(A[i]);
    if (i < B.Length)
        ResultSet.Add(B[i]);
    if (i < C.Length)
        ResultSet.Add(C[i]);
}

//ResultSet contains: { 1, 6, 9, 2, 7, 10, 3, 8, 11, 4, 12, 5 }

Как видите, просто вставьте это в метод и прокрутите списки списков, правильно определив максимальную длину всех списков.

Я бы пошел с:

static void Main(string[] args)
{
    var a = new List<int>() { 1, 2, 3, 4, 5 };
    var b = new List<int>() { 6, 7, 8 };
    var c = new List<int>() { 9, 10, 11, 12 };

    var abc = XYZ<int>(new[] { a, b, c }).ToList();
}

static IEnumerable<T> XYZ<T>(IEnumerable<IList<T>> lists)
{
    if (lists == null)
        throw new ArgumentNullException();
    var finished = false;
    for (int index = 0; !finished; index++)
    {
        finished = true;
        foreach (var list in lists)
            if (list.Count > index) // list != null (prior checking for count)
            {
                finished = false;
                yield return list[index];
            }
    }
}

Я должен был использовать использование IList иметь индексатор и Count, Он ничего не создает (без перечислителей, списков и т. Д.), Чисто yield return,

Для вашей задачи я создаю статический метод, который может объединять любые коллекции, как вы хотите:

public static class CollectionsHandling
{
    /// <summary>
    /// Merge collections to one by index
    /// </summary>
    /// <typeparam name="T">Type of collection elements</typeparam>
    /// <param name="collections">Merging Collections</param>
    /// <returns>New collection {firsts items, second items...}</returns>
    public static IEnumerable<T> Merge<T>(params IEnumerable<T>[] collections)
    {
        // Max length of sent collections
        var maxLength = 0;

        // Enumerators of all collections
        var enumerators = new List<IEnumerator<T>>();

        foreach (var item in collections)
        {
            maxLength = Math.Max(item.Count(), maxLength);
            if(collections.Any())
                enumerators.Add(item.GetEnumerator());
        }
        // Set enumerators to first item
        enumerators.ForEach(e => e.MoveNext());

        var result = new List<T>();
        for (int i = 0; i < maxLength; i++)
        {
            // Add elements to result collection
            enumerators.ForEach(e => result.Add(e.Current));

            // Remobve enumerators, in which no longer have elements
            enumerators = enumerators.Where(e => e.MoveNext()).ToList();
        }

        return result;
    }
}

Пример использования:

static void Main(string[] args)
{
    var a = new List<int> { 1, 2, 3, 4, 5 };
    var b = new List<int> { 6, 7, 8 };
    var c = new List<int> { 9, 10, 11, 12 };

    var result= CollectionsHandling.Merge(a, b, c);
}

Когда вы поймете, как это работает, можно будет уменьшить метод до меньшего.

Самое короткое и, вероятно, самое медленное решение

int[] A = { 1, 2, 3, 4, 5 };
int[] B = { 6, 7, 8 };
int[] C = { 9, 10, 11, 12 };


var arrs = new[] { A, B, C };
var merged = Enumerable.Range(0, arrs.Max(a => a.Length))
    .Select(x => arrs.Where(a=>a.Length>x).Select(a=>a[x]))
    .SelectMany(x=>x)
    .ToArray();

UPD.

Еще один способ решить - я просто переделал ответ @Sinatr.

static IEnumerable<T> XYZ<T>(IEnumerable<IList<T>> lists)
{
    if (lists == null)  
        throw new ArgumentNullException();
    var index = 0;

    while (lists.Any(l => l.Count > index))
    {
        foreach (var list in lists)     
            if (list.Count > index)         
                yield return list[index];       
        index++;
    }
}
Другие вопросы по тегам