简体   繁体   English

C# 拆分列表<string>价值</string>

[英]C# Splitting a List<string> Value

I have a List with values {"1 120 12", "1 130 22", "2 110 21", "2 100 18"}, etc.我有一个包含值 {"1 120 12"、"1 130 22"、"2 110 21"、"2 100 18"} 等的列表。

List<string> myList = new List<string>();
myList.Add("1 120 12"); 
myList.Add("1 130 22"); 
myList.Add("2 110 21"); 
myList.Add("2 100 18");

I need to count based on the first number (ID) is and sum the consequent values for this IDs ie for ID = 1 -> 120+130=150 and 12+22=34 and so on... I have to return an array with these values.我需要根据第一个数字 (ID) 进行计数,并对这个 ID 的结果值求和,即 ID = 1 -> 120+130=150 和 12+22=34 等等...我必须返回一个具有这些值的数组。

I know I can get these individual values, add them to an array and split it by the empty space between them with something like:我知道我可以获取这些单独的值,将它们添加到一个数组中,然后用它们之间的空白空间将其拆分,例如:

string[] arr2 = arr[i].Split(' ');

and loop thru them to do the sum of each value, but... is there an easy way to do it straight using Lists or Linq Lambda expression?并循环通过它们来计算每个值的总和,但是......有没有一种简单的方法可以直接使用 Lists 或 Linq Lambda 表达式来完成它?

You can do it in LINQ like this:您可以像这样在 LINQ 中执行此操作:

var result = myList.Select(x => x.Split(' ').Select(int.Parse))
                   .GroupBy(x => x.First())
                   .Select(x => x.Select(y => y.Skip(1).ToArray())
                                 .Aggregate(new [] {0,0}, (y,z) => new int[] {y[0] + z[0], y[1] + z[1]}));

First, the strings are split and converted to int, then they are grouped by ID, then the ID is dropped, and in the end, they are summed together.首先将字符串拆分并转换为int,然后将它们按ID分组,然后将ID丢弃,最后将它们相加。

But I strongly recommend not doing it in LINQ, because this expression is not easy to understand.但是我强烈建议不要在LINQ中做,因为这个表达式不好理解。 If you do it the classic way with a loop, it is quite clear what is going on at first sight.如果你用循环的经典方式来做,第一眼就很清楚发生了什么。 But put this code containing the loop into a separate method, because that way it won't distract you and you still only call a one-liner as in the LINQ solution.但是把这个包含循环的代码放到一个单独的方法中,因为这样它不会分散你的注意力,你仍然只调用一个单行,就像在 LINQ 解决方案中一样。

To do it straight, no LINQ, perhaps:直接做,没有LINQ,也许:

var d = new Dictionary<string, (int A, int B)>();

foreach(var s in myList){
  var bits = s.Split();
  if(!d.ContainsKey(bits[0])) 
    d[bits[0]] = (int.Parse(bits[1]), int.Parse(bits[2]));
  else { 
    (int A, int B) x = d[bits[0]];
    d[bits[0]] = (x.A + int.Parse(bits[1]), x.B + int.Parse(bits[2]));
  }
}

Using LINQ to parse the int, and switching to using TryGetValue, will tidy it up a bit:使用 LINQ 解析 int,并切换到使用 TryGetValue,会整理一下:

var d = new Dictionary<int, (int A, int B)>();

foreach(var s in myList){
  var bits = s.Split().Select(int.Parse).ToArray();
  if(d.TryGetValue(bits[0], out (int A, int B) x)) 
    d[bits[0]] = ((x.A + bits[1], x.B + bits[2]));
  else 
    d[bits[0]] = (bits[1], bits[2]);
 
}

Introducing a local function to safely get either the existing nums in the dictionary or a (0,0) pair might reduce it a bit too:引入本地 function 以安全地获取字典中的现有数字或 (0,0) 对也可能会减少一点:

var d = new Dictionary<int, (int A, int B)>();
(int A, int B) safeGet(int i) => d.ContainsKey(i) ? d[i]: (0,0);

foreach(var s in myList){
  var bits = s.Split().Select(int.Parse).ToArray();
  var nums = safeGet(bits[0]);
  d[bits[0]] = (bits[1] + nums.A, bits[2] + nums.B);
}

Is it any more readable than a linq version?它是否比 linq 版本更具可读性? Hmm... Depends on your experience with Linq, and tuples, I suppose..嗯......取决于你对 Linq 和元组的经验,我想......

I know this question already has a lot of answers, but I have not seen one yet that focuses on readability .我知道这个问题已经有很多答案了,但我还没有看到一个专注于可读性的答案。

If you split your code into a parsing phase and a calculation phase , we can use LINQ without sacrificing readability or maintainability, because each phase only does one thing:如果将代码拆分为解析阶段计算阶段,我们可以在牺牲可读性或可维护性的情况下使用 LINQ,因为每个阶段只做件事:

List<string> myList = new List<string>();
myList.Add("1 120 12"); 
myList.Add("1 130 22"); 
myList.Add("2 110 21"); 
myList.Add("2 100 18");

var parsed = (from item in myList
              let split = item.Split(' ')
              select new 
              { 
                  ID = int.Parse(split[0]),
                  Foo = int.Parse(split[1]),
                  Bar = int.Parse(split[2])
              });

var summed = (from item in parsed
              group item by item.ID into groupedByID
              select new 
              {
                  ID = groupedByID.Key,
                  SumOfFoo = groupedByID.Sum(g => g.Foo),
                  SumOfBar = groupedByID.Sum(g => g.Bar)
              }).ToList();

foreach (var s in summed)
{
    Console.WriteLine($"ID: {s.ID}, SumOfFoo: {s.SumOfFoo}, SumOfBar: {s.SumOfBar}");
}

fiddle小提琴

If you want, but I think it will be much easier to edit and optimize using the usual value.如果您愿意,但我认为使用通常的值进行编辑和优化会容易得多。 I don't find using this kind of logic inside LINQ will stay that way for a long period of time.我发现在 LINQ 内部使用这种逻辑不会长时间保持这种状态。 Usually, we need to add more values, more parsing, etc. Make it not really suitable for everyday use.通常,我们需要添加更多的值、更多的解析等。使它不太适合日常使用。

    var query = myList.Select(a => a.Split(' ').Select(int.Parse).ToArray())
        .GroupBy(
          index => index[0], 
          amount => new
                {
                    First = amount[1],
                    Second = amount[2]
                }, 
          (index, amount) => new
                {
                    Index = index, 
                    SumFirst = amount.Sum(a => a.First), 
                    SumSecond = amount.Sum(a => a.Second) 
                }
                );

fiddle小提琴

is there an easy way to do it straight using Lists or Linq Lambda expression?有没有一种简单的方法可以直接使用 Lists 或 Linq Lambda 表达式?

Maybe, is it wise to do this?也许,这样做是否明智? Probably not.可能不是。 Your code will be hard to understand, impossible to unit test, the code will probably not be reusable, and small changes are difficult.您的代码将难以理解,无法进行单元测试,代码可能无法重用,小改动也很困难。

But let's first answer your question as a one LINQ statement:但是,让我们首先以 LINQ 声明的形式回答您的问题:

const char separatorChar = ' ';
IEnumerable<string> inputText = ...
var result = inputtext.Split(separatorChar)
   .Select(text => Int32.Parse(text))
   .Select(numbers => new
     {
         Id = numbers.First()
         Sum = numbers.Skip(1).Sum(),
     }); 

Not reusable, hard to unit test, difficult to change, not efficient, do you need more arguments?不可重用、难以单元测试、难以更改、效率不高,您需要更多的 arguments 吗?

It would be better to have a procedure that converts one input string into a proper object that contains what your input string really represents.最好有一个程序将一个输入字符串转换为正确的 object,其中包含您的输入字符串真正代表的内容。

Alas, you didn't tell us if every input string contains three integer numbers, of that some might contain invalid text, and some might contain more or less than three integer numbers.唉,您没有告诉我们每个输入字符串是否包含三个 integer 数字,其中一些可能包含无效文本,而有些可能包含多于或少于三个 integer 数字。

You forgot to tell use what your input string represents.你忘了告诉使用你的输入字符串代表什么。 So I'll just make up an identifier:所以我就编一个标识符:

class ProductSize
{
    public int ProductId {get; set;}     // The first number in the string
    public int Width {get; set;}         // The 2nd number
    public int Height {get; set;}        // The 3rd number
}

You need a static procedure with input a string, and output one ProductSize:您需要一个 static 过程并输入一个字符串,以及 output 一个 ProductSize:

public static ProductSize FromText(string productSizeText)
{
    // Todo: check input
    const char separatorChar = ' ';
    var splitNumbers = productSizeText.Split(separatorChar)
        .Select(splitText => Int32.Parse(splitText))
        .ToList();

    return new ProductSize
    {
         ProductId = splitNumbers[0],
         Width = splitNumbers[1],
         Height = splitNumbers[2],
    };
}

I need to count based on the first number (ID) is and sum the consequent values for this IDs我需要根据第一个数字 (ID) 进行计数,并将此 ID 的结果值相加

After creating method ParseProductSize this is easy:创建方法 ParseProductSize 后,这很容易:

IEnumerable<string> textProductSizes = ...

var result = textProductSizes.Select(text => ProductSize.FromText(text))
   .Select(productSize => new
     {
         Id = productSize.Id,
         Sum = productSize.Width + productSize.Height,
     });

If your strings do not always have three numbers如果您的字符串并不总是包含三个数字

If you don't have always three numbers, then you won't have Width and Height, but a property:如果您不总是有三个数字,那么您将没有宽度和高度,而是一个属性:

IEnumerable<int> Numbers {get; set;}        // TODO: invent proper name

And in ParseProductSize:在 ParseProductSize 中:

var splitText = productSizeText.Split(separatorChar);
        
return new ProductSize
{
     ProductId = Int32.Parse(splitText[0]),
     Numbers = splitText.Skip(1)
         .Select(text => Int32.Parse(text));

I deliberately keep it an IEnumerable, so if you don't use all Numbers, you won't have parsed numbers for nothing.我故意将其保留为 IEnumerable,因此如果您不使用所有数字,您将不会无缘无故地解析数字。

The LINQ: LINQ:

var result = textProductSizes.Select(text => ProductSize.FromText(text))
   .Select(productSize => new
     {
         Id = productSize.Id,
         Sum = productSize.Numbers.Sum(),
     });

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM