比使用 3 个嵌套 for 循环获取我需要的数据更好的方法

A better way than having 3 nested for loops to get data I need

我可以用我目前正在做的方式建立一个字典,但我想知道是否有更好更快的方法。我正在尝试使用 LINQ SelecMany 语句但遇到了问题。

var replyChildren = reply.Children[0];
Dictionary<int, string> slowLog = new Dictionary<int, string>();
for (int i = 0; i < replyChildren.Children.Count(); i++)
{
    var ithReplyChildren = replyChildren.Children[i];
    for (int j = 0; j < 4; j++)
    {
        if (j == 3)
        {
            var jthReplyChildren = ithReplyChildren.Children[j];
            for (int k = 0; k < jthReplyChildren.Count; k++)
            {
                serverInfo += jthReplyChildren.Children[k].TextData; //+ "::";
            }
            break;
        }
        serverInfo += ithReplyChildren.Children[j].TextData + ":";
    }
    slowLog.Add(i, serverInfo);
    serverInfo = "";
}

Reply 将在(第 0 个元素)内有子项,然后在其中将有子项,依此类推。第二个 for 循环将只有 4 个子循环,而第 4 个 for 循环将不得不更深入一步并获得它的子循环。

第一步,让我们展开中间循环:

var replyChildren = reply.Children[0];
Dictionary<int, string> slowLog = new Dictionary<int, string>();
for (int i = 0; i < replyChildren.Children.Count(); i++)
{
    var ithReplyChildren = replyChildren.Children[i];
    serverInfo += ithReplyChildren.Children[0].TextData + ":";
    serverInfo += ithReplyChildren.Children[1].TextData + ":";
    serverInfo += ithReplyChildren.Children[2].TextData + ":";
    var jthReplyChildren = ithReplyChildren.Children[3];
    for (int k = 0; k < jthReplyChildren.Count; k++)
    {
        serverInfo += jthReplyChildren.Children[k].TextData;//+ "::";
    }

    slowLog.Add(i, serverInfo);
    serverInfo = "";
}

然后我们将 for() 替换为 foreach(()

var replyChildren = reply.Children[0];
var slowLog = new Dictionary<int, string>();
int i = 0;

foreach(var ithReplyChildren in replyChildren.Children)
{
    serverInfo += ithReplyChildren.Children[0].TextData + ":";
    serverInfo += ithReplyChildren.Children[1].TextData + ":";
    serverInfo += ithReplyChildren.Children[2].TextData + ":";

    var jthReplyChildren = ithReplyChildren.Children[3];
    foreach (var kthReplyChildren in jthReplyChildren.Children)
    {
        serverInfo += kthReplyChildren.TextData;//+ "::";
    }

    slowLog.Add(i++, serverInfo);
    serverInfo = "";
}

然后迈克建议使用 StringBuilder

var replyChildren = reply.Children[0];
var slowLog = new Dictionary<int, string>();
int i = 0;

foreach (var ithReplyChildren in replyChildren.Children)
{
    var serverInfo = new StringBuilder();
    serverInfo.Append(ithReplyChildren.Children[0].TextData);
    serverInfo.Append(':');

    serverInfo.Append(ithReplyChildren.Children[1].TextData);
    serverInfo.Append(':');
    serverInfo.Append(ithReplyChildren.Children[2].TextData);
    serverInfo.Append(':');

    var jthReplyChildren = ithReplyChildren.Children[3];
    foreach (var kthReplyChildren in jthReplyChildren.Children)
    {
        serverInfo.Append(kthReplyChildren.TextData);
    }

    slowLog.Add(i++, serverInfo.ToString());
}

我还没有接近 IDE 来测试这个,但我把它缩小了一点:

var replyChildren = reply.Children[0];
Dictionary<int, string> slowLog = new Dictionary<int, string>();

int i = 0;
foreach (var child in replyChildren.Children)
{
    StringBuilder sb = new StringBuilder();

    sb.AppendFormat("{0}:", child.Children[0].TextData);
    sb.AppendFormat("{0}:", child.Children[1].TextData);
    sb.AppendFormat("{0}:", child.Children[2].TextData);

    sb.Append(string.Join(string.Empty, child.Children[3].Select(x => x.TextData));

    slowLog.Add(i, sb.ToString());
    i++;
}

我不完全知道你的要求,但正如@EricLippert 在评论中提到的,你可以使用 List<string>...

第一步:停止使用字典。我们已经有了一个数据结构,可以将密集的、从零开始的整数范围映射到数据:List<T>。重构:

static string GetServerInfo(Thing thing)
{
  string serverInfo = "";
  for (int j = 0; j < 4; j++)
  {
    if (j == 3)
    {
      var jthReplyChildren = thing.Children[j];
      for (int k = 0; k < jthReplyChildren.Count; k++)
      {
        serverInfo += jthReplyChildren.Children[k].TextData; //+ "::";
      }
      break;
    }
    serverInfo += thing.Children[j].TextData + ":";
  }
  return serverInfo;
}

现在你的主程序是:

List<string> slowLog = reply.Children[0].Children.Select(GetServerInfo).ToList(); 

现在我们已经消除了一个循环并将问题减少到使 GetServerInfo 看起来不那么可怕。

展开外循环:

static string GetServerInfo(Thing thing)
{
  string serverInfo = "";
  serverInfo += thing.Children[0].TextData + ":";
  serverInfo += thing.Children[1].TextData + ":";
  serverInfo += thing.Children[2].TextData + ":";
  var fourthChild = thing.Children[3];
  for (int k = 0; k < fourthChild.Count; k++)
    serverInfo += fourthChild.Children[k].TextData;
  return serverInfo;
}

太好了,现在我们只剩下一个循环了。消除那个循环:

static string GetServerInfo(Thing thing)
{
  string serverInfo = "";
  serverInfo += thing.Children[0].TextData + ":";
  serverInfo += thing.Children[1].TextData + ":";
  serverInfo += thing.Children[2].TextData + ":";
  serverInfo += string.Join("", thing.Children[3].Children.Select(x => x.TextData));
  return serverInfo;
}

我们也可以为其他人使用 Join:

static string GetServerInfo(Thing thing)
{
  string firstThree = string.Join("", thing.Children.Take(3).Select( x => x.TextData + ":"));
  string fourth = string.Join("", thing.Children[3].Children.Select(x => x.TextData));
  return firstThree + fourth;
}

进一步减少:

static string GetServerInfo(Thing thing)
{
  return 
    string.Join("", thing.Children.Take(3).Select(x => x.TextData + ":")) +
    string.Join("", thing.Children[3].Children.Select(x => x.TextData));
}

使用箭头表示法:

static string GetServerInfo(Thing thing) => 
    string.Join("", thing.Children.Take(3).Select(x => x.TextData + ":")) +
    string.Join("", thing.Children[3].Children.Select(x => x.TextData));

等一下,现在我们有了 lambda。改写原站:

List<string> slowLog = reply.Children[0].Children
  .Select(thing => 
    string.Join("", thing.Children.Take(3).Select(x => x.TextData + ":")) +
    string.Join("", thing.Children[3].Children.Select(x => x.TextData)))
  .ToList(); 

我们只剩下一个声明了。 没有循环;没有循环问题。逻辑表达的很清楚——取其中三个,将它们连接在一起,blah blah blah,把它变成一个列表,完成。

我们还能更进一步吗?当然!我们可以在助手中组合 select 和连接运算符,使我们的程序更 流畅:

public static string SelectConcat<T>(this IEnumerable<T> items, Func<T, string> f) =>
  string.Join("", items.Select(f));

现在我们的程序变成了

List<string> slowLog = reply.Children[0].Children
  .Select(thing => 
    thing.Children.Take(3).SelectConcat(x => x.TextData + ":") +
    thing.Children[3].Children.SelectConcat(x => x.TextData))
  .ToList(); 

现在非常清楚我们在做什么。

这里的要点是:目标不是让程序变小;这不是代码高尔夫。 使代码读起来更像其预期的语义

你的原始代码让它看起来像是程序片段中最重要的东西是循环索引的值;查看有多少屏幕空间用于 i、j 和 k。这些是 机制 。编写代码以强调含义。如果你的意思是 "take the first three",那么那里应该有一个 Take(3),而不是 for 循环。

我们如何从强调i、j和k值的初始程序发展到描述对数据的操作的程序?看看我在那里做了什么:我进行了一系列小而简单的重构,每个重构 提高了抽象级别 以使代码读起来更像其预期的语义或消除了不必要的 "ceremony".