Как распечатать обратный массив, используя диапазон? - PullRequest
6 голосов
/ 16 марта 2019

В настоящее время я как бы играю с этой новой функцией c# 8.0, которая диапазонов .

И до сих пор у меня есть вопрос. Могу ли я создать range от конца до начала?

Допустим, у меня есть array, и я хотел бы распечатать его в обратном направлении через range:

static void Test()
{
    int[] array = new int[10];
    for (int i = 0; i < 10; i++) array[i] = i;
    Range range = ^1..0;

    foreach (int v in array[range]) WriteLine(v);
}

Но я получаю ошибку во время выполнения - System.OverflowException: 'Arithmetic operation resulted in an overflow.' Так что я прав, что для ranges?

разрешено только одно направление (от начала до конца)

Ответы [ 2 ]

2 голосов
/ 19 марта 2019

Обратные диапазоны являются не столько ограничением для типа Range, сколько ограничением для типов Span (и связанных с ним), которые предполагают положительное приращение. Рациональным является то, что многие из низкоуровневых оптимизаций и сценариев использования для slice массива ориентированы только на базовое понятие «указатель плюс размер». ( Ссылка на использование диапазона в Framework )

Хотя это не позволит обойти обратный Span, можно использовать метод расширения, чтобы обеспечить простой и производительный синтаксис для перечисления обратного диапазона:

public static class RangeExtensions
{
    public static int Normalize(this Index index, int length) => 
           index.FromEnd ? length - index.Value : index.Value;

    public static (int start, int end) Normalize(this Range range, int length) => 
           (range.Start.Normalize(length), range.End.Normalize(length));

    public static IEnumerable<T> Enumerate<T>(this T[] items, Range range)
    {
        var (start, end) = range.Normalize(items.Length);

        return start <= end ? items[range] : GetRangeReverse();

        IEnumerable<T> GetRangeReverse()
        {
            for (int i = start; i >= end; i--)
                yield return items[i];
        }
    }
}

...

var numbers = Enumerable.Range(0, 10).ToArray();

foreach (var num in numbers.Enumerate(^3..1))
    Console.WriteLine(num);
2 голосов
/ 18 марта 2019

Я не мог найти ничего, что говорит, что это возможно.Но, похоже, это не является языковым ограничением, поскольку мы можем создать диапазон, например Range r = 5..1;, и он не генерирует исключения, поэтому это должна быть реализация Array[Range] и других типов, таких как строка, которая не 'Я не принимаю это.Таким образом, вы, вероятно, можете создать метод, который принимает Range и делает то, что вы хотите.

Редактировать: я сделал следующий метод, который работает с массивами:

static T[] MySlice<T>(T[] array, Range r)
{
    //Transforms indexes "from end" to indexes "from start"    
    if(r.Start.ToString()[0] == '^'){
        var startIdx = array.Length - r.Start.Value;
        r = new Range(startIdx,r.End);
    }
    if(r.End.ToString()[0] == '^'){
        var endIdx = array.Length - r.End.Value;
        r = new Range(r.Start,endIdx);
    }
    //Check if start value is greater than end value. If so, invert it
    if(r.Start.Value > r.End.Value)
    {                
        r = new Range(r.End,r.Start);
        var invArr = array[r];
        Array.Reverse(invArr);
        return invArr;
    }
    return array[r];
}

И выможно использовать как

int[] arr = Enumerable.Range(0,10).ToArray();
Range r = ^0..0;

foreach(var v in MySlice(arr,r)){
    Console.WriteLine(v);
}

Какие выходные данные:

9
8
7
6
5
4
3
2
1
0

Редактировать: в документации сказано, что Index es имеет логическое свойство с именем FromEnd, которое указывает, является ли индекс концом.Во время моих тестов я обнаружил, что имя реализованного свойства (в ядре dotnet 3.0.100-preview3-010431) равно IsFromEnd.Поскольку дотнет-ядро 3 все еще находится в бета-версии, название может измениться, поэтому я придерживаюсь r.Start.ToString()[0] == '^', чтобы выяснить, является ли Index от конца, пока до окончательного выпуска дотнет-ядра 3. 3. 1020 *

...