C#中类似于Python的range函数带步长的等价函数是什么?

5
有没有C#版本的Python range 函数,它可以设置步长?

文档:

对于正数的步长,范围(range)函数的内容由公式 r[i] = start + step*i 决定,其中 i >= 0r[i] < stop

对于负数的步长,范围(range)函数的内容仍由公式 r[i] = start + step*i 决定,但限制条件变为 i >= 0r[i] > stop


例子:

>>> list(range(0, 10, 3))
[0, 3, 6, 9]
>>> list(range(0, -10, -1))
[0, -1, -2, -3, -4, -5, -6, -7, -8, -9]

3
Enumerable.Range(0, ((stop-start)/step) + ((stop-start)%step == 0 ? 0 : 1 )).Select(i => start + step * i) - juharr
C#现在也有范围(语法为start..end),但据我所知它不支持步长参数。 - Hutch Moore
3个回答

6

我建议采用两种方法进行实现。第一种方法是验证参数并提供默认值:

public static IEnumerable<int> Range(int start, int stop, int step = 1)
{
    if (step == 0)
        throw new ArgumentException(nameof(step));

    return RangeIterator(start, stop, step);
}

这对于具有延迟执行的迭代器是必需的。否则,在迭代器被执行之前,您将无法验证参数。这可能会在您获得迭代器引用之后很长一段时间才发生。而迭代器本身(实际上,使用C# 7,您可以使用本地函数而不是创建单独的方法):

private static IEnumerable<int> RangeIterator(int start, int stop, int step)
{
    int x = start;

    do
    {
        yield return x;
        x += step;
        if (step < 0 && x <= stop || 0 < step && stop <= x)
            break;
    }
    while (true);
}

为了实现Python的range行为,我们需要一个只接受“stop”参数的方法。我们可以使用C# 6表达式主体成员简化代码:
public static IEnumerable<int> Range(int stop) => RangeIterator(0, stop, 1);

你可以使用C#6使静态方法在全局范围内可用。假设包含Range方法的类的描述名为PythonUtils:

(参考链接)

using static YourNamespace.PythonUtils;

代码中的使用方式如下:

foreach(var i in Range(0, 10, 3))
   Print(i);

您还可以使用默认值。
Range(0, 10, 3)     // [0,3,6,9]
Range(4, -3, -1)    // [4,3,2,1,0,-1,-2]
Range(5)            // [0,1,2,3,4]
Range(2, 5)         // [2,3,4]

看起来是Pascal-case Python :)


1
我喜欢这个解决方案胜过我的,只是有几件事情我想指出:range()在Python中无效,而且range(5)应该返回[0, 1, 2, 3, 4]。我在原来的问题中没有指定这些情况,我只是在我的答案中包含它们,以使其尽可能地像Python。 - budi
1
@budi 抱歉,我忘记了 Python 中 range 的细节。通过添加额外的 Range 方法更新了答案。同时我还从带有三个参数的 Range 方法中删除了一些默认值。 - Sergey Berezovskiy
1
这里有点挑剔,但是在Python中list(range(1, -3, 2))返回[],然而你的解决方案Range(1, -3, 2)返回[1]。我暂时取消了接受,因为我的解决方案处理了这种情况;如果您想更新您的解决方案,我很乐意重新接受! - budi

4
我们可以实现一个“静态”实用类来处理这个问题。
为了完整起见,此解决方案模仿Python的range行为,适用于一个参数(stop),两个参数(start,stop)和三个参数(start,stop,step):
using System;
using System.Collections.Generic;

public static class EnumerableUtilities
{
    public static IEnumerable<int> RangePython(int start, int stop, int step = 1)
    {
        if (step == 0)
            throw new ArgumentException("Parameter step cannot equal zero.");

        if (start < stop && step > 0)
        {
            for (var i = start; i < stop; i += step)
            {
                yield return i;
            }
        }
        else if (start > stop && step < 0)
        {
            for (var i = start; i > stop; i += step)
            {
                yield return i;
            }
        }
    }

    public static IEnumerable<int> RangePython(int stop)
    {
        return RangePython(0, stop);
    }
}

使用示例:


foreach (var i in EnumerableUtilities.RangePython(0, 10, 3))
{
    Console.WriteLine(i);
}

输出:

0
3
6
9

1

受Sergey的解决方案启发,模仿Python的range函数。

static class Utils
{
    public static IEnumerable<int> Range(int start, int stop, int step = 1)
    {
        if (step == 0)
            throw new ArgumentException(nameof(step));

        while (step > 0 && start < stop || step < 0 && start > stop)
        {
            yield return start;
            start += step;
        }
    }

    public static IEnumerable<int> Range(int stop) => Range(0, stop, 1);
}

void Main()
{
    var ranges = new IEnumerable<int>[] {
        Utils.Range(0, 10, 3),    // [0,3,6,9]
        Utils.Range(4, -3, -1),   // [4,3,2,1,0,-1,-2]
        Utils.Range(5),           // [0,1,2,3,4]
        Utils.Range(2, 5),        // [2,3,4]
        Utils.Range(1, -3, 2),    // []
    };
    
    Array.ForEach(ranges, Console.WriteLine);
}

可以对while循环进行一些小的优化,以获得更好的性能,我很喜欢这种方法。

if (step > 0) while (start < stop)
{
    yield return start;
    start += step;
}
else while (start > stop)
{
    yield return start;
    start += step;
}

可能有更聪明的方法来做到这一点。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接