4

I write this function for merging two arrays.

private static int[] Merge(int[] array1, int[] array2)
{
    var mergedArray = new int[array1.Length + array2.Length];
    int i = 0, j = 0, k = 0;
    while(k < mergedArray.Length)
    {
        if(i == array1.Length || j == array2.Length)
        {
             if (i <= j)
                {
                    mergedArray[k] = array1[i];
                    i++;
                }
                else
                {
                    mergedArray[k] = array2[j];
                    j++;
                }
        }
        else
        {
            if(array1[i] < array2[j])
            {
                mergedArray[k] = array1[i];
                i++;
            }
            else
            {
                mergedArray[k] = array2[j];
                j++;
            }
        }
        k++;
    }
    return mergedArray;
}

How to reduce if statements in this code?

cdiggins
  • 17,602
  • 7
  • 105
  • 102
BILL
  • 4,711
  • 10
  • 57
  • 96
  • This is not the same question. The merging here takes into account the values of the arrays, while the possible duplicate only concatenates two arrays. – carlosfigueira Jul 11 '12 at 20:33
  • I think this does more than just merge. Maybe there some sorting going on (`if(array1[i] – John Alexiou Jul 11 '12 at 20:33
  • Yes. I use this merging for merge-sort. Please vote for reopen. – BILL Jul 11 '12 at 20:35
  • Sorry, I didn't know you were trying to do a merge-sort. If the question had been clearer, I wouldn't have voted to close. –  Jul 11 '12 at 20:35
  • 7
    _"Is there a way to simplify this code?"_ --> http://codereview.stackexchange.com/ – CodeCaster Jul 11 '12 at 20:36
  • 1
    Related http://stackoverflow.com/questions/9807701/is-there-an-easy-way-to-merge-two-ordered-sequences-using-linq – Brian Rasmussen Jul 11 '12 at 20:40
  • 1
    @Victor: Please provide an example set of arrays, and what the expected results are. The intent is not clear from the source what the `Merge()` function _actually_ does. – John Alexiou Jul 11 '12 at 20:46
  • Here's another Merge-Sort question: http://stackoverflow.com/q/7717871/284240 – Tim Schmelter Jul 11 '12 at 20:52
  • possible duplicate of [Most efficient algorithm for merging sorted IEnumerable](http://stackoverflow.com/questions/2767007/most-efficient-algorithm-for-merging-sorted-ienumerablet) – Matthew Finlay May 29 '14 at 05:11

6 Answers6

12

You can also make a Linq friendly version. This one is fast and will work on IEnumerable. You could easily translate this to any type T where T is IComparable.

    private static IEnumerable<int> Merge(IEnumerable<int> enum1, IEnumerable<int> enum2)
    {
        IEnumerator<int> e1 = enum1.GetEnumerator();
        IEnumerator<int> e2 = enum2.GetEnumerator();

        bool remaining1 = e1.MoveNext();
        bool remaining2 = e2.MoveNext();

        while (remaining1 || remaining2)
        {
            if (remaining1 && remaining2)
            {
                if (e1.Current > e2.Current)
                {
                    yield return e2.Current;
                    remaining2 = e2.MoveNext();
                }
                else
                {
                    yield return e1.Current;
                    remaining1 = e1.MoveNext();
                }
            }
            else if (remaining2)
            {
                yield return e2.Current;
                remaining2 = e2.MoveNext();
            }
            else
            {
                yield return e1.Current;
                remaining1 = e1.MoveNext();
            }
        }
    }
edeboursetty
  • 5,669
  • 2
  • 40
  • 67
10

Not as good as the Linq solution, but if you want the traditional if-then style function you could write:

private static int[] Merge(int[] array1, int[] array2)
{
    var mergedArray = new int[array1.Length + array2.Length];
    int i = 0, j = 0, k = 0;
    while(k < mergedArray.Length)
    {
        if (j == array2.Length || ((i < array1.Length) && (array[i] < array2[j])))
        {
            mergedArray[k] = array1[i];
            i++;
        }
        else
        {
            mergedArray[k] = array2[j];
            j++;
        }
        k++;
    }
    return mergedArray;
}

(edit: missing brace added)

Or in English:

If array2 is empty or if there are still values in array 1 and array1[i] is less than array2[j], then take value from array1, otherwise take from array 2

Or very concise (just for fun):

private static int[] Merge(int[] array1, int[] array2)
{
    var mergedArray = new int[array1.Length + array2.Length];
    int i = 0, j = 0;
    while(i+j < mergedArray.Length)
        if (j == array2.Length || ((i < array1.Length) && (array1[i] < array2[j])))
            mergedArray[i+j] = array1[i++];
        else
            mergedArray[i+j] = array2[j++];
    return mergedArray;
}
Community
  • 1
  • 1
nicholas
  • 2,969
  • 20
  • 39
  • This is much better than the Linq solution (you can measure the speed if you want). That said you must put the `j == array2.Length` test at the beginning of your test. ie `if (j == array2.Length || ((i < array1.Length) && (array[i] < array2[j])))` otherwise, array2[j] could throw an exception – edeboursetty Jul 11 '12 at 20:57
  • Would this also assume that the two arrays passed in as parameters are already ordered? – Erik Philips Jul 11 '12 at 21:11
  • @ErikPhilips the question asker assumes so, yes – jcolebrand Jul 11 '12 at 21:12
  • @jcolebrand I feel like i'm blind, where did he mention the arrays are pre-sorted? – Erik Philips Jul 11 '12 at 21:14
  • the title `How to optimize function for merging sorted arrays in C#` – jcolebrand Jul 11 '12 at 21:17
4

Linq is your friend, here is one way:

private static int[] Merge(int[] array1, int[] array2)
{
  List<int> merged = new List<int>(array1.Length + array2.Length);
  merged.AddRange(array1);
  merged.AddRange(array2);
  return merged.GroupBy(x => x)
               .Select(x => x.Key)
               .OrderBy(x => x)
               .ToArray();
}
Erik Philips
  • 53,428
  • 11
  • 128
  • 150
  • `Array.Copy(array1, newArray, array1.Length);` – BILL Jul 11 '12 at 20:45
  • I think that arrays more faster than Lists – BILL Jul 11 '12 at 20:48
  • @Victor You can entirely avoid copy if you yous `array1.Concat(array2)` instead of `merge`. Linq consumes anyway 'IEnumerable' so the `Concat` will first enumerate through `array1` and then through `array2`. – George Mamaladze Jul 11 '12 at 20:53
  • 1
    @Victor if the two arrays can contain the same values, this solution **removes the duplicates**. So while your `Merge(new [] {2,3,4}, new [] {4,5,6})` will results in 2,3,4,4,5,6 until Erik's solution results in 2,3,4,5,6 – nemesv Jul 11 '12 at 20:55
  • 2
    @Victor By the way is avoiding conditionals really your ultimative goal? You solution has `O(n)` time - with proposed Linq you will get `O(n log(n))` because it involves `OrderBy`. Are you really happy with the trade-off? – George Mamaladze Jul 11 '12 at 20:57
  • I agree with achitaka, this solution is terrible. It would be eaasy to write a merge function for two IEnumerable though – edeboursetty Jul 11 '12 at 21:01
2

Rather than being specific to arrays, I'd suggest looking at the following question for a number of different answers for IEnumerable<T>. Most efficient algorithm for merging sorted IEnumerable<T>

The answer I provided at https://stackoverflow.com/a/14444706/184528 has only one if statement and merge multiple enumerables.

For example to use it to merge three different arrays:

    public static void Main(string[] args)
    {
        var xs = new[] { 1, 5, 9 };
        var ys = new[] { 2, 7, 8 };
        var zs = new[] { 0, 3, 4, 6 };

        foreach (var a in new [] { xs, ys, zs }.Merge())
            Console.WriteLine(a);
    }
Community
  • 1
  • 1
cdiggins
  • 17,602
  • 7
  • 105
  • 102
0

It seems to me that this is the most "reduced" version of the function:

private static int[] Merge(int[] array1, int[] array2)
{
    return array1.Concat(array2).OrderBy(x => x).ToArray();
}

It can't get much simpler than this. Not one if remaining.

I ran this Merge(new [] { 1, 2, 2, 3 }, new [] { 1, 1, 2, 4, }) on both the original code and my solution and got the same answer.

Enigmativity
  • 113,464
  • 11
  • 89
  • 172
-1

How about using Queue<int> to drain the arrays:

    private static int[] Merge(int[] array1, int[] array2)
    {
        int N=array1.Length+array2.Length;
        Queue<int> Q1=new Queue<int>(array1);
        Queue<int> Q2=new Queue<int>(array2);
        Queue<int> result=new Queue<int>(N);

        for(int k=0; k<N; k++)
        {
            if(Q1.Count==0)
            {
                result.Enqueue(Q2.Dequeue());
            }
            else if(Q2.Count==0)
            {
                result.Enqueue(Q1.Dequeue());
            }
            else
            {
                result.Enqueue(
                    Q1.Peek()<Q2.Peek()?
                    Q1.Dequeue():
                    Q2.Dequeue());
            }
        }
        return result.ToArray();
    }
John Alexiou
  • 28,472
  • 11
  • 77
  • 133