从string中解析int的动态List的最有效方法

时间:2013-07-29 18:39:26

标签: c# list

出于好奇,是否有更快/更有效的方法来解析int的动态string列表?

目前我有这个,它的工作非常好;我只是觉得可能有更好的方法,因为对于这么简单的事情来说这似乎有点过于复杂。

public static void Send(string providerIDList)
{
String[] providerIDArray = providerIDList.Split('|');
var providerIDs = new List<int>();
for (int counter = 0; counter < providerIDArray.Count(); counter++)
{
     providerIDs.Add(int.Parse(providerIDArray[counter].ToString()));
}
//do some stuff with the parsed list of int

编辑:也许我应该说一个更简单的方法从字符串中解析出我的列表。但由于最初的问题确实表明更快,更有效,所选择的答案将反映出这一点。

4 个答案:

答案 0 :(得分:14)

肯定有更好的方法。使用LINQ:

var providerIDs = providerIDList.Split('|')
                                .Select(x => int.Parse(x))
                                .ToList();

或者使用方法组转换而不是lambda表达式:

var providerIDs = providerIDList.Split('|')
                                .Select(int.Parse)
                                .ToList();

不是最有效的方式,但它可能是最简单的。它与您的方法一样高效 - 尽管可以相当轻松地提高效率,例如给List初始容量。

性能上的差异可能是无关紧要的,所以我会坚持使用这个简单的代码,直到你得到它是瓶颈的证据。

请注意,如果您不需要List<int> - 如果您只需要迭代一次 - 您可以终止ToList来电并使用providerIDs作为{{ 1}}。

编辑:如果我们从事效率业务,那么这里是IEnumerable<int>方法的改编版,以避免使用ForEachChar

int.Parse

注意:

  • 这将为任何连续的分隔符添加零,或在开头或结尾添加分隔符
  • 它不处理负数
  • 不检查溢出
  • 如评论中所述,使用public static List<int> ForEachCharManualParse(string s, char delim) { List<int> result = new List<int>(); int tmp = 0; foreach(char x in s) { if(x == delim) { result.Add(tmp); tmp = 0; } else if (x >= '0' && x <= '9') { tmp = tmp * 10 + x - '0'; } else { throw new ArgumentException("Invalid input: " + s); } } result.Add(tmp); return result; } 语句代替switch可以进一步提高效果(提高约10-15%)

如果这些都不是问题,那么它比我机器上的x >= '0' && x <= '9'快约7倍:

ForEachChar

可以解决这些限制,但我没有打扰......如果他们对您有重大顾虑,请告诉我。

答案 1 :(得分:2)

到目前为止,我不喜欢任何答案。因此,为了实际回答OP提出的“最快/最有效”的String.Split与Int.Parse的问题,我编写并测试了一些代码。

在Intel 3770k上使用Mono。

我发现使用String.Split + IEnum.Select并不是最快(也许是最漂亮的)解决方案。事实上它是最慢的。

以下是一些基准测试结果

ListSize 1000 : StringLen 10468 
SplitForEach1000 Time : 00:00:02.8704048 
SplitSelect1000 Time : 00:00:02.9134658 
ForEachChar1000 Time : 00:00:01.8254438 
SplitParallelSelectr1000 Time : 00:00:07.5421146 
ForParallelForEachChar1000 Time : 00:00:05.3534218

ListSize 100000 : StringLen 1048233 
SplitForEach100000 Time : 00:00:01.9500846 
SplitSelect100000 Time : 00:00:02.2662606 
ForEachChar100000 Time : 00:00:01.2554577 
SplitParallelSelectr100000 Time : 00:00:02.6509969 
ForParallelForEachChar100000 Time : 00:00:01.5842131

ListSize 10000000 : StringLen 104824707 
SplitForEach10000000 Time : 00:00:18.2658261 
SplitSelect10000000 Time : 00:00:20.6043874 
ForEachChar10000000 Time : 00:00:10.0555613 
SplitParallelSelectr10000000 Time : 00:00:18.1908017 
ForParallelForEachChar10000000 Time : 00:00:08.6756213

以下是获取基准测试结果的代码

using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics;

namespace FastStringSplit
{
    class MainClass
    {
        public static void Main (string[] args)
        {
            Random rnd = new Random();
            char delim = ':';
            int[] sizes = new int[]{1000, 100000, 10000000 };
            int[] iters = new int[]{10000, 100, 10};
            Stopwatch sw;

            List<int> list, result = new List<int>();
            string str;
            for(int s=0; s<sizes.Length; s++) {
                list = new List<int>(sizes[s]);
                for(int i=0; i<sizes[s]; i++)
                    list.Add (rnd.Next());
                str = string.Join(":", list);
                Console.WriteLine(string.Format("\nListSize {0} : StringLen {1}", sizes[s], str.Length));
                ////
                sw = new Stopwatch();
                for(int i=0; i<iters[s]; i++) {
                    sw.Start();
                    result = SplitForEach(str, delim);
                    sw.Stop();
                }
                Console.WriteLine("SplitForEach" + result.Count + " Time : " + sw.Elapsed.ToString());
                ////
                sw = new Stopwatch();
                for(int i=0; i<iters[s]; i++) {
                    sw.Start();
                    result = SplitSelect(str, delim);
                    sw.Stop();
                }
                Console.WriteLine("SplitSelect" + result.Count + " Time : " + sw.Elapsed.ToString());
                ////
                sw = new Stopwatch();
                for(int i=0; i<iters[s]; i++) {
                    sw.Start();
                    result = ForEachChar(str, delim);
                    sw.Stop();
                }
                Console.WriteLine("ForEachChar" + result.Count + " Time : " + sw.Elapsed.ToString());
                ////
                sw = new Stopwatch();
                for(int i=0; i<iters[s]; i++) {
                    sw.Start();
                    result = SplitParallelSelect(str, delim);
                    sw.Stop();
                }
                Console.WriteLine("SplitParallelSelectr" + result.Count + " Time : " + sw.Elapsed.ToString());
                ////
                sw = new Stopwatch();
                for(int i=0; i<iters[s]; i++) {
                    sw.Start();
                    result = ForParallelForEachChar(str, delim);
                    sw.Stop();
                }
                Console.WriteLine("ForParallelForEachChar" + result.Count + " Time : " + sw.Elapsed.ToString());
            }
        }
        public static List<int> SplitForEach(string s, char delim) {
            List<int> result = new List<int>();
            foreach(string x in s.Split(delim))
                result.Add(int.Parse (x));
            return result;
        }
        public static List<int> SplitSelect(string s, char delim) {
            return s.Split(delim)
                .Select(int.Parse)
                    .ToList();
        }
        public static List<int> ForEachChar(string s, char delim) {
            List<int> result = new List<int>();
            int start = 0;
            int end = 0;
            foreach(char x in s) {
                if(x == delim || end == s.Length - 1) {
                    if(end == s.Length - 1)
                        end++;
                    result.Add(int.Parse (s.Substring(start, end-start)));
                    start = end + 1;
                }
                end++;
            }
            return result;
        }
        public static List<int> SplitParallelSelect(string s, char delim) {
            return s.Split(delim)
                .AsParallel()
                    .Select(int.Parse)
                        .ToList();
        }
        public static int NumOfThreads = Environment.ProcessorCount > 2 ? Environment.ProcessorCount : 2;
        public static List<int> ForParallelForEachChar(string s, char delim) {
            int chunkSize = (s.Length / NumOfThreads) + 1;
            ConcurrentBag<int> result = new ConcurrentBag<int>();
            int[] chunks = new int[NumOfThreads+1];
            Task[] tasks = new Task[NumOfThreads];
            for(int x=0; x<NumOfThreads; x++) {
                int next = chunks[x] + chunkSize;
                while(next < s.Length) {
                    if(s[next] == delim)
                        break;
                    next++;
                }
                //Console.WriteLine(next);
                chunks[x+1] = Math.Min(next, s.Length);
                tasks[x] = Task.Factory.StartNew((o) => {
                    int chunkId = (int)o;
                    int start = chunks[chunkId];
                    int end = chunks[chunkId + 1];
                    if(start >= s.Length)
                        return;
                    if(s[start] == delim)
                        start++;
                    //Console.WriteLine(string.Format("{0} {1}", start, end));
                    for(int i = start; i<end; i++) {
                        if(s[i] == delim || i == end-1) {
                            if(i == end-1) 
                                i++;
                            result.Add(int.Parse (s.Substring(start, i-start)));
                            start = i + 1;
                        }
                    }
                }, x);
            }
            Task.WaitAll(tasks);
            return result.ToList();
        }
    }
}

以下是我推荐的功能

        public static List<int> ForEachChar(string s, char delim) {
            List<int> result = new List<int>();
            int start = 0;
            int end = 0;
            foreach(char x in s) {
                if(x == delim || end == s.Length - 1) {
                    if(end == s.Length - 1)
                        end++;
                    result.Add(int.Parse (s.Substring(start, end-start)));
                    start = end + 1;
                }
                end++;
            }
            return result;
        }

为什么它更快?

它不会先将字符串拆分为数组。它同时进行拆分和解析,因此没有额外的开销来迭代字符串以拆分它然后迭代数组来解析它。

我还使用任务投入了并行化版本,但在非常大的字符串的情况下它只会更快。

答案 2 :(得分:0)

这看起来更干净:

var providerIDs = providerIDList.Split('|').Select(x => int.Parse(x)).ToList();

答案 3 :(得分:-1)

如果你真的想知道最有效的方法,那么使用不安全的代码,从字符串定义char指针,迭代所有字符递增字符指针,缓冲读取字符直到下一个'|',将缓冲字符转换为int32。如果你想要真的很快,那么手动做(从最后一个char开始,子结构值'0'char,将它乘以10,100,1000 ...根据迭代变量,然后将它添加到sum变量。我不有时间编写代码,但希望你能得到这个想法