【问题标题】:How to merge an array that was split into 2 arrays with odd and even indices?如何合并一个被拆分为 2 个具有奇数和偶数索引的数组的数组?
【发布时间】:2018-05-13 07:28:54
【问题描述】:

来自这个问题: How to split an array to 2 arrays with odd and even indices respectively?

我已经使用这种方法将一个数组拆分为 2 个分别具有奇数和偶数索引的数组:

int[] a = new int[] { 1, 3, 7, 8 };

int[] aEven = a.Where((x, i) => i % 2 == 0).ToArray();
int[] aOdd = a.Where((x, i) => i % 2 != 0).ToArray();

这会产生 2 个数组:

aEven : {1, 7}
aOdd  : {3, 8}

如何以优雅的方式将aEven/aOdd 合并回原来的a 数组?

注意:我不会更改 aEven/aOdd 数组,并且保证两个数组的长度相同。

我需要来自aEven/aOdd 输入的输出是:

{ 1, 3, 7, 8 };

【问题讨论】:

  • 它不是 C。它是 C# - 完全不同的语言。
  • 这只是一个错误。对不起
  • 任何解决方案都将涉及一个循环,无论是直接的(即for)还是间接的(即LINQ的zip
  • 要更新原始数组吗?看来您从未修改过它,所以它已经是正确的。还是您的意思是您想合并这两个数组以创建一个与原始数组相同的新数组(这样您可能根本没有原始数组,而您正在尝试重建它)?
  • 希望在我的编辑之后,所有投票以unclear what you're asking 结束的人都会知道我在问什么 :)

标签: c# linq collections


【解决方案1】:

一种方法是结合Enumerable.ZipEnumerable.Aggregate 以及一点技巧。请注意,这仍然在引擎盖下使用循环。

var aList = aEven.Zip(aOdd, (even, odd) => new {even, odd})
                 .Aggregate(new List<int>(aEven.Length + aOdd.Length),
                            (list, z) =>
                            {
                                list.Add(z.even);
                                list.Add(z.odd);
                                return list;
                            });
if (aEven.Length != aOdd.Length)
    aList.Add(aEven[aEven.Length-1]);

var aOutput = aList.ToArray();

for (var i = 0; i < aOutput.Length; ++i)
    Console.WriteLine($"aOutput[{i}] ==> {aOutput[i]} == {a[i]} <== a[{i}]");

这仅适用于您的场景但是(拆分然后通过奇数/偶数索引恢复数组,假设“子数组”的顺序被维护)。

结果数组将具有相同的大小(原始数组具有偶数项)或偶数数组将具有一个额外项(原始数组具有奇数项)。在后一种情况下,额外的项目将被Zip 丢弃,需要手动计算。这不适用于通过其他方式计算两个子数组的其他场景。

您也可以在没有中间列表的情况下使用预先分配的数组来执行此操作,但您必须跟踪 LINQ 调用之外的索引(我不太喜欢):

var index = 0;
var aOutput = aEven.Zip(aOdd, (even, odd) => new {even, odd})
                   .Aggregate(new int[aEven.Length + aOdd.Length],
                             (arr, z) =>
                             {
                                 arr[index++] = z.even;
                                 arr[index++] = z.odd;
                                 return arr;
                             });
if (aEven.Length != aOdd.Length)
    aOutput[index] = aEven[aEven.Length-1];

另一种方法是使用ZipSelectManyConcat 的组合(考虑最后一项):

var aOutput = aEven.Zip(aOdd, (even, odd) => new[]{ even, odd })
    .SelectMany(z => z)
    .Concat(aEven.Length == aOdd.Length ? new int[0] : new []{ aEven[aEven.Length - 1] })
    .ToArray();

简单的 for 循环可能仍然是最简单的解决方案。

【讨论】:

  • 如果你能保证它的长度总是均匀的,那么你不会
【解决方案2】:

只需使用 for 循环代替。它不会创建所有这些中间元组数组,但具有相当的可读性并且需要相同数量的代码。

int[] Merge(int[] evenItems, int[] oddItems)
{
    var itemsCount = evenItems.Length + oddItems.Length;
    var result = new int[itemsCount];

    for (var i = 0; i < itemsCount; i++)
    {
        var sourceIndex = Math.DivRem(i, 2, out var remainder);
        var source = remainder == 0 ? evenItems : oddItems;
        result[i] = source[sourceIndex];
    }

    return result;
}

【讨论】:

  • 它只适用于这种情况,例如运行这个:Merge( new int[] { 1, 2, 5, 10 }, new int[] { 8, 11 });
  • @MohamedElshawaf 当初始数组被分成两部分且元素保持在偶数和奇数索引时,您无法获得这样的输入。
【解决方案3】:
public static IEnumerable<T> MergeByZipAndRemainder<T>(T[] this firsts, T[] seconds)
{
  int maxLength = Math.Max(firsts.Length, seconds.Length);
  foreach(int i in Enumerable.Range(0, maxLength))
  {
     if (i < firsts.Length) { yield return firsts[i]; }
     if (i < seconds.Length) { yield return seconds[i]; }
  }
}

然后:

var result = evens.MergeByZipAndRemainder(odds).ToArray();

这种方法遇到了回答者的一些批评。

  • 优雅。
  • 它解决了合并长度可能非常不同的数组的一般情况。
  • 它只枚举一次 O(N+M)。
  • 它不分配需要垃圾回收的中间数组对象。
  • 理论上应该更快,但我没有测量性能。

【讨论】:

    【解决方案4】:

    创建InterlockWith 扩展方法,是的,没有逃脱循环:

    public static IEnumerable<T> InterlockWith<T>(this IEnumerable<T> seq1, IEnumerable<T> seq2)
        {
            Tuple<T[], int>[] metaSequences = new Tuple<T[], int>[2];
            metaSequences[0] = Tuple.Create(seq1.ToArray(), seq1.Count());
            metaSequences[1] = Tuple.Create(seq2.ToArray(), seq2.Count());
            var orderedMetas = metaSequences.OrderBy(x => x.Item2).ToArray();
    
            for (int i = 0; i < orderedMetas[0].Item2; i++)
            {
                yield return metaSequences[0].Item1[i];
                yield return metaSequences[1].Item1[i];
            }
    
            var remainingItems = orderedMetas[1].Item1.Skip(orderedMetas[0].Item2);
            foreach (var item in remainingItems)
            {
                yield return item;
            }
        }
    

    你可以像这样调用它来获得你的结果集:

    a = aEven.InterlockWith(aOdd).ToArray();
    

    【讨论】:

      【解决方案5】:
      var merged = evens
        .Zip(odds, (even, odd) => new [] {even, odd})
        .SelectMany(pair => pair)
        .Concat(evens.Skip(odds.Length))
        .ToArray();
      

      【讨论】:

      • 这段代码是相反的(evens 应该先开始。),但是在我进行修改之后,它工作得很好。非常好。
      • 哎呀,呵呵。好的 - 做出了改变。谢谢!
      • 附带说明,如果计数超过evens,它将跳过odds 中的一项,但对OP 问题很有效,我喜欢你使用这些LINQ 方法组合的方式
      • @MohamedElshawaf 一般的问题,调用Concat两次即可。
      • Skip 不是“专门”用于数组或列表(例如 ElementAt 是,例如 List 或其他几个),这意味着 Skip 调用必须迭代 数组的所有项目(可能根本不产生任何项目)然后可能调用两次??如果你问我,在一般情况下不是很好。
      【解决方案6】:

      首先你可以使用 Concat 合并它们,然后如果你想对 reslt 进行排序,你可以使用 OrderBy :

      aEven.Concat(aOdd).OrderBy(b => b).ToArray();
      

      【讨论】:

      • 它不会保持与a相同的顺序
      • 除非知道a 的顺序(在示例中似乎是升序),否则无法解决此问题。没有它,合并两个数组不能保证恢复原始顺序,因为不能保证它在偶数和奇数之间交替......实际上在示例中没有。
      • 您必须将发布的代码分配给 var result = aEven.Concat(aOdd).OrderBy(b =&gt; b).ToArray(); 这样的变量,因为它不会更改 OP 问题中声明的任何变量
      • @SlavenTojić:谢谢,你是对的,我认为这很清楚,但正如你所做的那样,最好提一下。
      猜你喜欢
      • 1970-01-01
      • 2012-09-06
      • 2015-01-28
      • 2020-03-29
      • 1970-01-01
      • 2022-10-01
      • 1970-01-01
      • 1970-01-01
      • 2021-10-21
      相关资源
      最近更新 更多