Замечания и предложения

А можете сделать, чтоб оно работало не только массивами? Обычно этот функционал нужнее для yield последовательностей а не массивов…

Индексы - это массивы, а не последовательности

Это теория. А я говорю про то что на практике получается.

А на практике - .Numerate.Where чаще приходится применять последовательностям, чем массивам.

Технически - да, в последовательностях индексы отсутствуют. Но, например, у extension-метода Select есть перегрузка, принимающая не только изначальный элемент, но и его индекс. Дополнительных временных затрат на вычисление индекса не требуется. В случаях, где лишних затрат не требуется можно реализовать и дополнительный функционал, оперирующий ещё и с индексами элементов последовательностей. Решение, конечно же, за Вами, ибо не мне распоряжаться Вашим временем.

Та ну если дело только во времени… PABCSystem, строки 10415…10432

/// Возвращает последовательность индексов последовательности
function Indices<T>(Self: sequence of T): sequence of integer; extensionmethod :=
Range(0, Self is ICollection<T>(var c)? c.Count-1 : Self.Count-1 );

/// Возвращает последовательность индексов элементов одномерного массива, удовлетворяющих условию
function Indices<T>(Self: sequence of T; cond: T->boolean): sequence of integer; extensionmethod;
begin
  var i := 0;
  foreach var o in Self do
  begin
    if cond(o) then
      yield i;
    i += 1;
  end;
end;

/// Возвращает последовательность индексов элементов одномерного массива, удовлетворяющих условию
function Indices<T>(Self: sequence of T; cond: (T,integer) ->boolean): sequence of integer; extensionmethod;
begin
  var i := 0;
  foreach var o in Self do
  begin
    if cond(o, i) then
      yield i;
    i += 1;
  end;
end;

Ничего из старого кода - от этого не сломается, зато и для последовательностей будет работать.

А как вы будете затем получать доступ к элементу последовательности по индексу?

Это как?

if y < 10 then
    k := Sin(y) else
    k := Tan(y);

так, что ли? Я пока что не видел подобных образчиков записи условного оператора.

@Admin, а Вы что скажете по поводу описанной “хохмы с автозавершением”? А то странноватая идея с индексами последовательности Вас отвлекла, видимо.

Можно - через ElementAt, но данная операция выполняется за O(n). Но, разумеется, слишком часто применять подобные методы - скажется на производительности приложения. На мой взгляд, данный метод был реализован как дополнительный функционал (когда без него не обойтись), но вовсе не для того, чтобы им пользовались повсюду.

Поиск в последовательности есть и так. Получение набора индексов избавляет от множественного просмотра, т.е. если надо найти положение k элементов, это делается за один просмотр, а не за k. Но последующая выборка из массива идет мгновенно, а из последовательности как? k раз будем вызывать ElementAt( ) и потратим на это k * О(n) ? Так лучше цикл с перебором написать.

1 лайк

Всё это обсуждение началось именно с задаче, где спрашивались только индексы.
Ну сделайте уже хотя бы для IList, а не для массивов…

Да, именно так.

Уже не сказал а сделал:

1 лайк

Да, но для массива. Распространить на TList - да, разумно, С другой стороны, это не срочно, потому что списки TList, увы, менее популярны, чем массивы.

Не TList а IList. IList это интерфейс, от которого много что наследует. Но из самого популярного - массивы и просто List.

Если придираться к словам, то не наследует, а реализует (если речь ведётся про классы и записи). В случае же интерфейсов, где IList<T> - базовый - термин верен.

Прошу добавить математическое округление, например, так:

/// Возвращает x, округленное до ближайшего целого по математическому округлению.
function MathRound(x: real): integer;
begin
  if (Frac(x) = 0.5) then Result := Trunc(x + 0.5)
  else if (Frac(x) = -0.5) then Result := Trunc(x - 0.5)
  else Result := Round(x)
end;
/// Возвращает x, округленное до ближайшего вещественного по математическому округлению с digits знаками после десятичной точки
function MathRound(x: real; digits: integer): real;
begin
  if (Frac(x) = 0.5) then Result := x + 0.5
  else if (Frac(x) = -0.5) then Result := x - 0.5
  else Result := Round(x, digits)
end;
/// Возвращает x, округленное до ближайшего длинного целого по математическому округлению
function MathRoundBigInteger(x: real): BigInteger;
begin
  if (Frac(x) = 0.5) then BigInteger.Create(x + 0.5)
  else if (Frac(x) = -0.5) then BigInteger.Create(x - 0.5)
  else BigInteger.Create(Math.Round(x))
end;

/// Возвращает число, округленное до ближайшего целого по математическому округлению
function MathRound(Self: real): integer; extensionmethod;
begin
  if (Frac(Self) = 0.5) then Result := Trunc(Self + 0.5)
  else if (Frac(Self) = -0.5) then Result := Trunc(Self - 0.5)
  else Result := Round(Self)
end;

/// Возвращает x, округленное до ближайшего вещественного по математическому округлению с digits знаками после десятичной точки
function MathRound(Self: real; digits: integer): real; extensionmethod;
begin
  if (Frac(Self) = 0.5) then Result := Self + 0.5
  else if (Frac(Self) = -0.5) then Result := Self - 0.5
  else Result := Round(Self, digits)
end;

/// Возвращает число, округленное до ближайшего длинного целого по математическому округлению
function MathRoundBigInteger(Self: real): BigInteger; extensionmethod;
begin
  Result := MathRoundBigInteger(Self);
end;

Я считаю, оно важно для округления маленьких нестатестических данных.

Это не так делается.
Вообще, если я правильно помню - стандартное округление банковское. То есть если Frac=0.5 - округляет до ближайшего чётного, а не до Int(x).

Если нужно математическое - используйте System.Math напрямую (Round его вызывает):

System.Math.Round(x, System.MidpointRounding.AwayFromZero);
System.Math.Round(x, digits, System.MidpointRounding.AwayFromZero);

Если использовать напрямую - будет ещё и быстрее выполняться.

А как исправить ошибку, что, начиная с x=2.4999999999999999, Writeln(System.Math.Round(x, System.MidpointRounding.AwayFromZero)) выводит 3?

С real - никак, потому что число 2.4999999999999999 не может быть представлено в виде real:

begin
  var x := 2.4999999999999999;
  x.ToString('N99').Println; // 99 знаков после запятой, больше .ToString не принимает
end.
begin
  var x := real.Parse('2.4'+'9'*13); // больше девяток точность real не позволяет, из за чего округлит ещё в Parse. Компилятор вызывает real.Parse когда встречает в коде литерал из цифр и точки
  x.ToString('N99').Println;
end.

Из стандартных типов - точность больше только у decimal:

begin
  var x := decimal.Parse('2.4'+'9'*27);
  x.ToString('N99').Println;
end.

decimal ещё и хранит своё значение в 10-ичной СС, в отличии от real, которое хранится в 2-ичной СС. Это так же помогает сделать округление более адекватным в таком случае:

begin
  var x: real;
  loop 999 do x += 0.001; // 0.001 нельзя красиво представить в 2-ичной СС, потому что это 1/2/2/2/5/5/5, а 1/5 имеет бесконечность цифр после запятой в 2-ичной СС
  x.ToString('N20').Println;
end.
begin
  var x: decimal;
  var d := decimal(0.001); // а из за того что `decimal` хранится в 10-ичной СС - тут "0.001" сохранит без погрешности
  loop 999 do x := x+d;
  x.ToString('N20').Println;
end.

Но в отличии от real - decimal редко аппаратно поддерживается, поэтому действия с ним дольше считаются. Ну и памяти на большую точность, ещё и в 10-ичной СС, конечно, надо больше.


Из альтернатив:

  1. Описать тип дроби:

    type
      Дробь = record
        числитель, знаменатель: BigInteger; // BigInteger это целое, которое вместо переполнения выделяет себе больше оперативной памяти
        
        //ToDo и остальные операторы
    //    static function operator+(x,y: Дробь): Дробь;
        
      end;
    

    Таким образом точность получается бесконечная. Но при операциях вроде Sqrt или Sin всё равно придётся решить как округлять, потому что их результат не всегда можно представить дробью.

  2. Можно упростить, считая что знаменатель это всегда 10^N, где N - число цифр точности:

    type
      ОченьТочноеЧисло = record
        private val: BigInteger;
        private constructor(val: BigInteger) :=
        self.val := val;
        private const N = 100; // точность - N цифр после запятой
        private static неявный_знаменатель := BigInteger.Pow(10, N);
        
        public static function operator+(x,y: ОченьТочноеЧисло): ОченьТочноеЧисло :=
        new ОченьТочноеЧисло(x.val+y.val); // намного проще чем для дробей
        
        public static function operator*(x,y: ОченьТочноеЧисло): ОченьТочноеЧисло :=
        new ОченьТочноеЧисло(x.val*y.val div неявный_знаменатель); // но при умножении и делении - всё равно надо помнить про знаменатель
        
      end;
    

По крайней мере это 2 простейших альтернативы, которые я знаю. А так придумать можно что угодно, на что фантазии хватит.

Но, опять же, всё это будет очень медленно считаться, потому что аппаратная поддержка BigInteger вообще невозможна, из за того что его размер изменяется по случаю.

Это не ошибка, это особенность компьютерного представления данных с плавающей точкой. Если Вам нужна абсолютная точность - работайте с простыми дробями. Напишите себе библиотеку или для начала попробуйте использовать библиотеку NumLib.ABC - там реализован класс простых дробей “бесконечной” точности.

Такое предложение, чтобы вкладки IDE закрывались средней кнопкой мыши. Это трудно организовать?

Ну и Ctrl+W тогда. Давно оба просят, но разработчики не хотят на это время тратить.

Если очень надо - редачьте тут и кидайте пулл.