有没有比 count++ 更优雅的方式来处理 foreach 枚举中的第一个和最后一个项目?

发布于 2024-09-05 17:55:09 字数 1208 浏览 7 评论 0原文

在迭代 foreach 循环时,是否有比增加单独的计数器并每次检查它更优雅的方法来对第一个和最后一个项目进行操作?

例如,以下代码输出:

>>> [line1], [line2], [line3], [line4] <<<

这需要知道您何时对第一个和最后一个项目进行操作。现在在 C# 3 / C# 4 中是否有更优雅的方法来做到这一点?看来我可以使用 .Last() 或 .First() 或类似的东西。

using System;
using System.Collections.Generic;
using System.Text;

namespace TestForNext29343
{
    class Program
    {
        static void Main(string[] args)
        {
            StringBuilder sb = new StringBuilder();
            List<string> lines = new List<string>
            {
                "line1",
                "line2",
                "line3",
                "line4"
            };
            int index = 0;
            foreach (var line in lines)
            {
                if (index == 0)
                    sb.Append(">>> ");

                sb.Append("[" + line + "]");

                if (index < lines.Count - 1)
                    sb.Append(", ");
                else
                    sb.Append(" <<<");

                index++;
            }

            Console.WriteLine(sb.ToString());
            Console.ReadLine();
        }
    }
}

Is there a more elegant way to act on the first and last items when iterating through a foreach loop than incrementing a separate counter and checking it each time?

For instance, the following code outputs:

>>> [line1], [line2], [line3], [line4] <<<

which requires knowing when you are acting on the first and last item. Is there a more elegant way to do this now in C# 3 / C# 4? It seems like I could use .Last() or .First() or something like that.

using System;
using System.Collections.Generic;
using System.Text;

namespace TestForNext29343
{
    class Program
    {
        static void Main(string[] args)
        {
            StringBuilder sb = new StringBuilder();
            List<string> lines = new List<string>
            {
                "line1",
                "line2",
                "line3",
                "line4"
            };
            int index = 0;
            foreach (var line in lines)
            {
                if (index == 0)
                    sb.Append(">>> ");

                sb.Append("[" + line + "]");

                if (index < lines.Count - 1)
                    sb.Append(", ");
                else
                    sb.Append(" <<<");

                index++;
            }

            Console.WriteLine(sb.ToString());
            Console.ReadLine();
        }
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(11

小矜持 2024-09-12 17:55:09

您当前的示例无需迭代即可完成。

Console.WriteLine(">>> " + String.Join(lines, ", ") + " <<<);

如果您只是进行迭代,我发现将其替换为常规 for 循环并检查边界会更容易。

for(int i=0; i<list.count; i++)
{
  if(i == 0)
   //First one
  else if(i == list.count -1)
   //Last one
}

它比使用 .First() 和 .Last() 扩展方法要快得多。此外,如果列表中有两个项目具有相同(字符串)值,则与“最后一个”或“第一个”相比将不起作用。

Your current example can be done without iterating.

Console.WriteLine(">>> " + String.Join(lines, ", ") + " <<<);

If you're just iterating I find it easier to just replace it with a regular for loop and check the boundaries.

for(int i=0; i<list.count; i++)
{
  if(i == 0)
   //First one
  else if(i == list.count -1)
   //Last one
}

It'll be a lot faster than using the .First() and .Last() extension methods. Besides, if you have two items in your list with the same (string) value comparing to Last or First won't work.

日久见人心 2024-09-12 17:55:09

对于当您只有 IEnumerable 时如何以不同方式处理第一个和最后一个情况的一般问题,可以执行此操作的一种方法是直接使用枚举器:

    public static void MyForEach<T>(this IEnumerable<T> items, Action<T> onFirst, Action<T> onMiddle, Action<T> onLast)
    {
        using (var enumerator = items.GetEnumerator())
        {
            if (enumerator.MoveNext())
            {
                onFirst(enumerator.Current);
            }
            else
            {
                return;
            }

            //If there is only a single item in the list, we treat it as the first (ignoring middle and last)
            if (!enumerator.MoveNext())
                return;

            do
            {
                var current = enumerator.Current;
                if (enumerator.MoveNext())
                {
                    onMiddle(current);
                }
                else
                {
                    onLast(current);
                    return;
                }
            } while (true);
        }
    }

For the general question of how to handle First and Last cases differently when you only have an IEnumerable<T>, one way you can do this is by using the enumerator directly:

    public static void MyForEach<T>(this IEnumerable<T> items, Action<T> onFirst, Action<T> onMiddle, Action<T> onLast)
    {
        using (var enumerator = items.GetEnumerator())
        {
            if (enumerator.MoveNext())
            {
                onFirst(enumerator.Current);
            }
            else
            {
                return;
            }

            //If there is only a single item in the list, we treat it as the first (ignoring middle and last)
            if (!enumerator.MoveNext())
                return;

            do
            {
                var current = enumerator.Current;
                if (enumerator.MoveNext())
                {
                    onMiddle(current);
                }
                else
                {
                    onLast(current);
                    return;
                }
            } while (true);
        }
    }
把回忆走一遍 2024-09-12 17:55:09

不回答你的问题,但为了你的目的,我会使用

return String.Format(">>> {0} <<<",String.Join(lines.ToArray(),","));

Not answering your question but for your purpose I would use

return String.Format(">>> {0} <<<",String.Join(lines.ToArray(),","));
蓝眼泪 2024-09-12 17:55:09

尝试以下代码。

foreach (var item in ForEachHelper.WithIndex(collection))
{
    Console.Write("Index=" + item.Index);
    Console.Write(";Value= " + item.Value);
    Console.Write(";IsLast=" + item.IsLast);
    Console.WriteLine();
}

以下是 ForEachHelper 类的代码。

public static class ForEachHelper
{
    public sealed class Item<T>
    {
        public int Index { get; set; }
        public T Value { get; set; }
        public bool IsLast { get; set; }
    }

    public static IEnumerable<Item<T>> WithIndex<T>(IEnumerable<T> enumerable)
    {
        Item<T> item = null;
        foreach (T value in enumerable)
        {
            Item<T> next = new Item<T>();
            next.Index = 0;
            next.Value = value;
            next.IsLast = false;
            if (item != null)
            {
                next.Index = item.Index + 1;
                yield return item;
            }
            item = next;
        }
        if (item != null)
        {
            item.IsLast = true;
            yield return item;
        }            
    }
}

Try the following code.

foreach (var item in ForEachHelper.WithIndex(collection))
{
    Console.Write("Index=" + item.Index);
    Console.Write(";Value= " + item.Value);
    Console.Write(";IsLast=" + item.IsLast);
    Console.WriteLine();
}

Here is the code for the ForEachHelper class.

public static class ForEachHelper
{
    public sealed class Item<T>
    {
        public int Index { get; set; }
        public T Value { get; set; }
        public bool IsLast { get; set; }
    }

    public static IEnumerable<Item<T>> WithIndex<T>(IEnumerable<T> enumerable)
    {
        Item<T> item = null;
        foreach (T value in enumerable)
        {
            Item<T> next = new Item<T>();
            next.Index = 0;
            next.Value = value;
            next.IsLast = false;
            if (item != null)
            {
                next.Index = item.Index + 1;
                yield return item;
            }
            item = next;
        }
        if (item != null)
        {
            item.IsLast = true;
            yield return item;
        }            
    }
}
ゃ人海孤独症 2024-09-12 17:55:09
int i, iMax;
i = iMax = lines.length;
sb.Append(">>> "); // ACT ON THE FIRST ITEM
while(i--) {
  sb.Append("[" + lines[length - i] + "]"); // ACT ON ITEM
  if(i) {
    sb.Append(", ");  // ACT ON NOT THE LAST ITEM
  } else {
    sb.Append(" <<<");  // ACT ON LAST ITEM
  }
}

我通常采用这种方法,在循环之前处理一个边界,然后在循环内处理另一个边界。而且我也喜欢递减而不是递增。我认为这主要是用户的偏好...

int i, iMax;
i = iMax = lines.length;
sb.Append(">>> "); // ACT ON THE FIRST ITEM
while(i--) {
  sb.Append("[" + lines[length - i] + "]"); // ACT ON ITEM
  if(i) {
    sb.Append(", ");  // ACT ON NOT THE LAST ITEM
  } else {
    sb.Append(" <<<");  // ACT ON LAST ITEM
  }
}

I usually take this approach, handle one boundary before the loop, then handle the other inside the loop. And i also like decrementing instead of incrementing. I think it is mostly user preference though...

七堇年 2024-09-12 17:55:09

我建议将 >>><<< 放在循环之外。

StringBuilder sb = new StringBuilder();
sb.Append(">>> ");

bool first = true;
foreach(var line in lines)
{
    if (!first) sb.Append(", ");
    sb.Append("[" + line + "]");
    first = false;
}
sb.Append(" <<<");

您还可以使用 String.Join 而不是 foreach 循环。

String.Join(", ", lines);

I would recommend putting your >>> and <<< outside of the loop.

StringBuilder sb = new StringBuilder();
sb.Append(">>> ");

bool first = true;
foreach(var line in lines)
{
    if (!first) sb.Append(", ");
    sb.Append("[" + line + "]");
    first = false;
}
sb.Append(" <<<");

You could also use a String.Join instead of a foreach loop.

String.Join(", ", lines);
去了角落 2024-09-12 17:55:09

可能有一种更“优雅”的使用 FirstLast 对其进行编码的方式,但效率低下使其不值得。

我为 IEnumerable 编写了自己的 Join 运算符(来自 Nito.KitchenSink)。它是完全可重用的(在.NET 3.5或4.0中):

/// <summary>
/// Concatenates a separator between each element of a string enumeration.
/// </summary>
/// <param name="source">The string enumeration.</param>
/// <param name="separator">The separator string. This may not be null.</param>
/// <returns>The concatenated string.</returns>
public static string Join(this IEnumerable<string> source, string separator)
{
    StringBuilder ret = new StringBuilder();
    bool first = true;
    foreach (string str in source)
    {
        if (first)
        {
            first = false;
        }
        else
        {
            ret.Append(separator);
        }

        ret.Append(str);
    }

    return ret.ToString();
}

/// <summary>
/// Concatenates a sequence of strings.
/// </summary>
/// <param name="source">The sequence of strings.</param>
/// <returns>The concatenated string.</returns>
public static string Join(this IEnumerable<string> source)
{
    return source.Join(string.Empty);
}

There may be a more "elegant" way of coding it using First and Last, but the inefficiency makes it not worth it.

I coded up my own Join operator for IEnumerable<string>s (from Nito.KitchenSink). It's fully reusable (in .NET 3.5 or 4.0):

/// <summary>
/// Concatenates a separator between each element of a string enumeration.
/// </summary>
/// <param name="source">The string enumeration.</param>
/// <param name="separator">The separator string. This may not be null.</param>
/// <returns>The concatenated string.</returns>
public static string Join(this IEnumerable<string> source, string separator)
{
    StringBuilder ret = new StringBuilder();
    bool first = true;
    foreach (string str in source)
    {
        if (first)
        {
            first = false;
        }
        else
        {
            ret.Append(separator);
        }

        ret.Append(str);
    }

    return ret.ToString();
}

/// <summary>
/// Concatenates a sequence of strings.
/// </summary>
/// <param name="source">The sequence of strings.</param>
/// <returns>The concatenated string.</returns>
public static string Join(this IEnumerable<string> source)
{
    return source.Join(string.Empty);
}
书间行客 2024-09-12 17:55:09

您可以执行以下操作:

Console.WriteLine(">>>> [" + String.Join("], [", lines.ToArray()) + "] <<<<");

我知道这不能回答您的问题,但它可以解决您的问题......

You could do the following:

Console.WriteLine(">>>> [" + String.Join("], [", lines.ToArray()) + "] <<<<");

I know this does not answer your question but it solves your problem...

痴梦一场 2024-09-12 17:55:09

如果 StringBuilder 不为空,为什么不直接在 foreach 循环末尾追加呢?

...
for (int i=0; i<lines.Count; i++)
{
    sb.Append("[" + lines[i] + "]");

    if (i < lines.Count - 1)
        sb.Append(", ");

}

if (sb.Length != 0)
{
    sb.Insert(0, ">>> ");
    sb.Append(" >>>");
}

Why not just append at the end of the foreach loop if the StringBuilder isn't empty?

...
for (int i=0; i<lines.Count; i++)
{
    sb.Append("[" + lines[i] + "]");

    if (i < lines.Count - 1)
        sb.Append(", ");

}

if (sb.Length != 0)
{
    sb.Insert(0, ">>> ");
    sb.Append(" >>>");
}
情未る 2024-09-12 17:55:09

我的 C# 有点生疏,但应该是这样的:

StringBuilder sb;
List<string> lines = ....;
sb.Append(">>> [").Append(lines[0]);
for (int idx = 1; idx < lines.Count; idx++)
    sb.Append("], [").Append(lines[idx]);
sb.Append("] <<<");

从循环中排除第一项要容易得多(通过启动索引
在一个)而不是排除最后一个。我从 Eric Lippert 的博客中得到了这个想法
前一段时间,但我现在无法找到该帖子......

My C# is a bit rusty, but it should be something like:

StringBuilder sb;
List<string> lines = ....;
sb.Append(">>> [").Append(lines[0]);
for (int idx = 1; idx < lines.Count; idx++)
    sb.Append("], [").Append(lines[idx]);
sb.Append("] <<<");

It's much easier to exclude the first item from the loop (by starting the index
at one) than it is to exclude the last. I got this idea from Eric Lippert's blog
some time ago but I can't for the life of me find the post at the moment....

和我恋爱吧 2024-09-12 17:55:09

List 在内部使用数组来存储项目(称为 _items),因此lines[i] 本质上与访问数组成员一样快。 Enumerable.First() 和 Enumerable.Last() 使用列表的索引器访问列表的第一个和最后一个成员,因此lines.First()本质上是lines[0],lines.Last本质上是lines[lines.Count-1 ],加上一些范围检查。

这意味着 line==lines.First() 的成本相当于数组成员引用加上引用比较。除非您执行大量迭代,否则这不会打扰您。

如果你需要更快的东西,你可以使用 LinkedList。在这种情况下,First() 和 Last() 直接返回第一个和最后一个项目,但这似乎有点矫枉过正。

List uses an array internally to store items (called _items), so lines[i] is essentially as fast as accessing an array member. Enumerable.First() and Enumerable.Last() access the first and last members of the List using the Lists's indexer, so lines.First() is essentially lines[0] and lines.Last is essentially lines[lines.Count-1], plus some range checking.

What this means is that the cost of line==lines.First() amounts to an array member reference plus a reference comparison. Unless you perform a LOT of iterations, this shouldn't bother you.

If you need something faster you can use a LinkedList. In this case First() and Last() return the first and last items directly, but that seems like overkill.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文