对于长度相等的字符串数组,使用LINQ获取其自身数组中每个字符串的第n个字符

时间:2018-10-18 18:01:17

标签: c# arrays linq

所以这是我关于StackOverflow的第一个问题,如果在这里违反任何约定,我深表歉意...

我有一个可变长度的数组,其中每个元素都是一个字符串(每个字符串元素具有相同的长度)。我需要将该数组中每个字符串的第n个字符放入它自己的数组中。

简单地说,我有以下内容:

string[] source = new string[3] { "abcd", "efgh", "ijkl" };

并想返回:

string[] output = new string[4] { "aei", "bfj", "cgk", "dhl" };

这又是非常简化的,因为实际上我正在处理MB数据,这就是为什么我需要使用LINQ而不是一堆嵌套的for循环的解决方案的原因。

提前谢谢大家!

1 个答案:

答案 0 :(得分:0)

一项快速测试显示,将@jdweng的纯LINQ解决方案设置为1X,将LINQ滥用索引的解决方案设置为7.4X,使用for的解决方案设置为28.4X。

(Ab)结合使用LINQ和索引:

var c = Enumerable.Range(0, source[0].Length).Select(chpos => Enumerable.Range(0, source.Length).Select(si => source[si][chpos]).Join()).ToArray();

使用嵌套的for循环:

var sourcelen = source.Length;
var strlen = source[0].Length;
var c = new String[strlen];
var sb = new StringBuilder(strlen);
for (int chpos = 0; chpos < strlen; ++chpos) {
    for (int si = 0; si < sourcelen; ++si)
        sb.Append(source[si][chpos]);
    c[chpos] = sb.ToString();
    sb.Length = 0;
}

运行一些进一步的测试,Append比索引StringBuilder快一点,直到有很多长字符串并且并行(顺序)构建所有字符串都不快,并且字符串的长度增加,如果您有大量(10,000)的字符串,则Parallel.For版本会赶上最快的嵌套for版本,然后超过该版本。有趣的是,Append比在Parallel中建立索引要慢得多。

以下是每个索引并行版本的构建:

    var sourcelen = source.Length;
    var strlen = source[0].Length;
    var c = new String[strlen];
    Parallel.For(0, strlen, chpos => {
        var sb = new StringBuilder(sourcelen);
        sb.Length = sourcelen;
        for (int si = 0; si < sourcelen; ++si)
            sb[si] = source[si][chpos];
        c[chpos] = sb.ToString();
    });

使用这种Join扩展方法可以大大加快使用String.Join(String.Empty,String.Join("",的解决方案的速度:

public static string Join(this IEnumerable<char> src) {
    var sb = new StringBuilder();
    foreach (var c in src)
        sb.Append(c);
    return sb.ToString();
}

关于时间的一些注释:我使用LINQPad编写了一些代码来生成示例source

var lens = 10000;
var source = new string[10000];
//
{
    var s = Enumerable.Range(0,26).Select(letternum => new String(Convert.ToChar('a'+letternum), lens)).ToArray();
    for (int j1 = 0; j1 < source.Length; ++j1)
        source[j1] = s[j1 % 26].ToString();
}

然后,使用LINQPad的Util.ElapsedTime,我测量了通过各种实现方式处理source的时间:

TimeSpan basetime;
//
{
    var start = Util.ElapsedTime;
    var c = source.Select(x => x.Select((y, i) => new { chr = y, index = i })).SelectMany(x => x).GroupBy(x => x.index).Select(x => x.Select(y => y.chr).Join()).ToArray();
    basetime = Util.ElapsedTime - start;
    basetime.Dump("Elapsed LINQ My Join 1X");
    //c.Dump();
}
//
{
    var start = Util.ElapsedTime;
    var sourcelen = source.Length;
    var strlen = source[0].Length;
    var c = new String[strlen];
    Parallel.For(0, strlen, chpos => {
        var sb = new StringBuilder(sourcelen);
        sb.Length = sourcelen;
        for (int si = 0; si < sourcelen; ++si)
            sb[si] = source[si][chpos];
        c[chpos] = sb.ToString();
    });
    var myt = Util.ElapsedTime - start;
    myt.Dump($"Elapsed build each indexed parallel {basetime.TotalSeconds / myt.TotalSeconds:0.0}X");
    c.Dump();
}