使用C#中第二个数组的值替换数组中的给定值

时间:2016-01-14 17:39:03

标签: c# arrays performance linq optimization

我有一组ints,其中包含许多负值:

var arrayExisting = new int[]{1,2,-1,3,5,-1,0,0,-1};

另一个数组有一组相应的值我要插入到第一个数组中:

var replacements = new int[]{7,6,5};

有没有一种真正有效的方法呢?

目前我所拥有的是:

var newArray = arrayExisting.Select(val =>
        {
            if (val != -1) return val;
            var ret = replacements[i];
            i++;
            return ret;
        }).ToArray();

这很快。所讨论的数组长度只有大约15个整数,这可能会增加,但不可能超过100.问题是我必须为我的中等测试系统和现实系统做超过25万次我正在考虑将涉及此代码的10e10次迭代!

3 个答案:

答案 0 :(得分:3)

我会使用for循环并替换原始数组中的值。

int replacementIndex = 0;
for (var i = 0; i < arrayExisting.Length; i++) {
    if (arrayExisting[i] < 0) {
        arrayExisting[i] = replacements[replacementIndex++];
    }
}

这样可以避免创建新数组的开销。如果您需要创建新阵列,可以创建new int[arrayExisting.Length]

运行快速基准测试似乎for循环速度提高了约4倍,即使在最糟糕的情况下,你必须每次更换并构建一个新数组来保存替换。

Select: 12672
For: 3386

如果您感兴趣,这是基准。

var loops = 1000000;
            var arrayExisting = Enumerable.Repeat(-1, 1000).ToArray();
            var replacements = Enumerable.Repeat(1, 1000).ToArray();

            var selectTimer = Stopwatch.StartNew();
            for (var j = 0; j < loops; j++)
            {
                var i = 0;
                var newArray = arrayExisting.Select(val =>
                {
                    if (val != -1) return val;
                    var ret = replacements[i];
                    i++;
                    return ret;
                }).ToArray();
            }
            selectTimer.Stop();

            var forTimer = Stopwatch.StartNew();
            for (var j = 0; j < loops; j++)
            {
                var replaced = new int[arrayExisting.Length];
                int replacementIndex = 0;
                for (var i = 0; i < arrayExisting.Length; i++)
                {
                    if (arrayExisting[i] < 0)
                    {
                        replaced[i] = replacements[replacementIndex++];
                    }
                    else
                    {
                        replaced[i] = arrayExisting[i];
                    }
                }
            }
            forTimer.Stop();

            Console.WriteLine("Select: " + selectTimer.ElapsedMilliseconds);
            Console.WriteLine("For: " + forTimer.ElapsedMilliseconds);

答案 1 :(得分:0)

尝试使用指针:

int replacementsLength = arrayReplacements.Length;
fixed (int* existing = arrayExisting, replacements = arrayReplacements)
{
    int* exist = existing;
    int* replace = replacements;
    int i = 0;
    while (i < replacementsLength)
    {
        if (*exist == -1)
        {
            *exist = *replace;
            i++;
            replace++;
        }
        exist++; //edit: forgot to put exist++ outside the if block
    }
}

编辑:此代码仅在您确定具有完全相同的替换数量和-1时才有效 要处理每个方案,请使用以下代码:

int replacementsLength = arrayReplacements.Length;
int existingLength = arrayExisting.Length;
fixed (int* existing = copy, replacements = arrayReplacements)
{
    int* exist = existing;
    int* replace = replacements;
    int i = 0;
    int x = 0;
    while (i < replacementsLength && x < existingLength)
    {
        if (*exist == -1)
        {
            *exist = *replace;
            i++;
            replace++;
        }
        exist++;
        x++;
    }
}

运行与Joey相同的测试结果是:

选择:17378
对于:2172
指针:1780
编辑:我的错误,我忘了迭代我的代码1000000.仍然更快。

这是测试代码:

private unsafe static void test()
{
    var loops = 1000000;
    var arrayExisting = Enumerable.Repeat(-1, 1000).ToArray();
    var arrayReplacements = Enumerable.Repeat(1, 1000).ToArray();
    int[] newArray = null;

    var selectTimer = Stopwatch.StartNew();
    for (var j = 0; j < loops; j++)
    {
        var i = 0;
        newArray = arrayExisting.Select(val =>
        {
            if (val != -1) return val;
            var ret = arrayReplacements[i];
            i++;
            return ret;
        }).ToArray();
    }
    selectTimer.Stop();

    printResult("linQ", newArray);

    arrayExisting = Enumerable.Repeat(-1, 1000).ToArray();
    arrayReplacements = Enumerable.Repeat(1, 1000).ToArray();
    int[] replaced = null;

    var forTimer = Stopwatch.StartNew();
    for (var j = 0; j < loops; j++)
    {
        replaced = new int[arrayExisting.Length];
        int replacementIndex = 0;
        for (var i = 0; i < arrayExisting.Length; i++)
        {
            if (arrayExisting[i] < 0)
            {
                replaced[i] = arrayReplacements[replacementIndex++];
            }
            else
            {
                replaced[i] = arrayExisting[i];
            }
        }
    }
    forTimer.Stop();

    printResult("for", replaced);

    arrayExisting = Enumerable.Repeat(-1, 1000).ToArray();
    arrayReplacements = Enumerable.Repeat(1, 1000).ToArray();

    int[] copy = null;

    var pointerTimer = Stopwatch.StartNew();
    //EDIT: fixed the test code
    for (int j = 0; j < loops; j++)
    {
        copy = new int[arrayExisting.Length];
        Array.Copy(arrayExisting, copy, arrayExisting.Length);
        int replacementsLength = arrayReplacements.Length;
        int existingLength = arrayExisting.Length;
        fixed (int* existing = copy, replacements = arrayReplacements)
        {
            int* exist = existing;
            int* replace = replacements;
            int i = 0;
            int x = 0;
            while (i < replacementsLength && x < existingLength)
            {
                if (*exist == -1)
                {
                    *exist = *replace;
                    i++;
                    replace++;
                }
                exist++;
                x++;
            }
        }
    }
    pointerTimer.Stop();

    printResult("pointer", copy);

    File.AppendAllText(@"E:\dev\test.txt", "\r\n" +
        "Select: " + selectTimer.ElapsedMilliseconds + "\r\n" +
        "For: " + forTimer.ElapsedMilliseconds + "\r\n" + 
        "Pointer: " + pointerTimer.ElapsedMilliseconds);
}

答案 2 :(得分:0)

使用@TVOHM对原始问题的评论,我实现了以下代码

public static int[] ReplaceUsingLinq(IEnumerable<int> arrayFromExisting, IEnumerable<int> x)
    {
        var indices = x.ToArray();
        var i = 0;
        var newArray = arrayFromExisting.Select(val =>
        {
            if (val != -1) return val;
            var ret = indices[i];
            i++;
            return ret;
        }).ToArray();
        return newArray;

    }

    public static int[] ReplceUsingForLoop(int[] arrayExisting, IEnumerable<int> x)
    {

        var arrayReplacements = x.ToArray();
        var replaced = new int[arrayExisting.Length];
        var replacementIndex = 0;
        for (var i = 0; i < arrayExisting.Length; i++)
        {
            if (arrayExisting[i] < 0)
            {
                replaced[i] = arrayReplacements[replacementIndex++];
            }
            else
            {
                replaced[i] = arrayExisting[i];
            }
        }

        return replaced;
    }

    public static unsafe int[] ReplaceUsingPointers(int[] arrayExisting, IEnumerable<int> reps)
    {

        var arrayReplacements = reps.ToArray();
        int replacementsLength = arrayReplacements.Length;
        var replaced = new int[arrayExisting.Length];
        Array.Copy(arrayExisting, replaced, arrayExisting.Length);
        int existingLength = replaced.Length;
        fixed (int* existing = replaced, replacements = arrayReplacements)
        {
            int* exist = existing;
            int* replace = replacements;
            int i = 0;
            int x = 0;
            while (i < replacementsLength && x < existingLength)
            {
                if (*exist == -1)
                {
                    *exist = *replace;
                    i++;
                    replace++;
                }
                exist++;
                x++;
            }
        }

        return replaced;
    }

    public static int[] ReplaceUsingLoopWithMissingArray(int[] arrayExisting, IEnumerable<int> x,
        int[] missingIndices)
    {

        var arrayReplacements = x.ToArray();
        var replaced = new int[arrayExisting.Length];
        Array.Copy(arrayExisting, replaced, arrayExisting.Length);
        var replacementIndex = 0;
        foreach (var index in missingIndices)
        {
            replaced[index] = arrayReplacements[replacementIndex];
            replacementIndex++;
        }
        return replaced;
    }

使用以下代码对此进行基准测试:

public void BenchmarkArrayItemReplacements()
    {
        var rand = new Random();
        var arrayExisting = Enumerable.Repeat(2, 1000).ToArray();
        var arrayReplacements = Enumerable.Repeat(1, 100);
        var toReplace = Enumerable.Range(0, 100).Select(x => rand.Next(100)).ToList();
        toReplace.ForEach(x => arrayExisting[x] = -1);
        var misisngIndices = toReplace.ToArray();
        var sw = Stopwatch.StartNew();


        var result = ArrayReplacement.ReplceUsingForLoop(arrayExisting, arrayReplacements);
        Console.WriteLine($"for loop took {sw.ElapsedTicks}");

        sw.Restart();
        result = ArrayReplacement.ReplaceUsingLinq(arrayExisting, arrayReplacements);
        Console.WriteLine($"linq took {sw.ElapsedTicks}");
        sw.Restart();
        result = ArrayReplacement.ReplaceUsingLoopWithMissingArray(arrayExisting, arrayReplacements, misisngIndices);
        Console.WriteLine($"with missing took {sw.ElapsedTicks}");
        sw.Restart();
        result = ArrayReplacement.ReplaceUsingPointers(arrayExisting, arrayReplacements);
        Console.WriteLine($"Pointers took {sw.ElapsedTicks}");

    }

这给出了结果:

for loop took      848
linq took         2879
with missing took  584
Pointers took      722

因此,我们知道我们缺少值的位置(-1s所在的位置)是它快速的关键。

顺便说一句,如果我将每个调用循环到相关方法10000次并检查我得到的时间:

for loop took     190988
linq took         489052
with missing took  69198
Pointers took     159102

这里效果更大