将数组中的给定值替换为 C# 中第二个数组中的值

本文关键字:数组 第二个 替换 | 更新日期: 2023-09-27 18:30:17

我有一个整数数组,其中有许多负值:

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。问题是,对于我的中等测试系统,我必须这样做超过四分之一次,而我正在考虑的实际系统将涉及此代码的大约 10e10 次迭代!

将数组中的给定值替换为 C# 中第二个数组中的值

我会使用 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);

尝试使用指针:

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++;
    }
}

运行与乔伊相同的测试,结果为:

选择: 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);
}

使用 @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

因此,知道我们在哪里有缺失值(-1 在哪里)是它快速的关键。

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

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

这里的效果更大