Как реализовать собственный setlength()

Я хочу реализовать свою собственную функцию MySetLength(), которая могла бы работать точно как оригинальная функция SetLength() (из delphi 6) для массивов, за исключением, которую я хотел бы передать >float параметры как размеры (затем, в конце концов, округлить их внутри моей функции, прежде чем вызывать оригинальный Setlength()). например:

procedure MySetLength(var A: Array type; Len1: **double**; Len2...);

вместо оригинала:

procedure SetLength(var A: Array type; Len1: **Integer**; Len2...);

где A может иметь любое количество измерений и может быть любого типа (строки или числа с плавающей запятой)


person dan matei    schedule 21.06.2016    source источник
comment
Итак, в чем ваш вопрос/проблема?   -  person Dsm    schedule 21.06.2016
comment
SetLength — это волшебство, точное воспроизведение невозможно. Хотя проблема выполнима, вам нужно разрешить некоторые ограничения.   -  person Free Consulting    schedule 21.06.2016
comment
@danmatei: Вы пишете это в Delphi 6 или просто хотите, чтобы он вел себя так же, как в Delphi 6? Если вы используете более позднюю версию, вы можете использовать Generics и Overloads для решения этой проблемы. Но если вы на самом деле используете Delphi 6, дженерики не подходят.   -  person Remy Lebeau    schedule 21.06.2016
comment
@RemyLebeau, перегрузки не работают с SetLength/Length. Поскольку они являются встроенными, перегрузка скроет встроенные функции. Мы были на этом с недавно представленным Low(string)\High(String) некоторое время назад.   -  person LU RD    schedule 21.06.2016
comment
@LURD: я предполагал, что сам MySetLength() может быть перегружен, одна версия для ввода String, одна версия для ввода массива. procedure MySetLength(var A: String; Len1: Double); overload; type TWhateverArray = array of Whatever; procedure MySetLength(var A: TWhateverArray; Len1: Double); overload; procedure MySetLength(var A: TWhateverArray; Len1, Len2: Double); overload; и т.д...   -  person Remy Lebeau    schedule 21.06.2016
comment
@RemyLebeau, хорошо, перегрузка MySetLength выполнима, без сомнения.   -  person LU RD    schedule 21.06.2016
comment
Это не то, о чем просят   -  person David Heffernan    schedule 22.06.2016
comment
@DavidHeffernan: * Я хочу реализовать свою собственную функцию MySetLength() * - это именно то, что запрашивается. Однако это не то, что OP должен на самом деле реализовать. Решение Deltics было бы гораздо лучшим вариантом.   -  person Remy Lebeau    schedule 22.06.2016
comment
@remy Asker хочет написать единую функцию, которая может принимать произвольные типы и числа аргументов измерения. Не писать по одной функции для каждой комбинации, как при перегрузке. Deltics предлагает именно то, что я предложил в своем ответе, и это, как вы говорите, единственный разумный подход.   -  person David Heffernan    schedule 22.06.2016
comment
XY Проблема... Почему вы хотите передать значения Double в SetLength. Поплавки принципиально неуместны в качестве размеров массива, и тот факт, что вы даже рассматриваете это, означает, что вы делаете что-то ужасно неправильное. Возможно, объясните свою настоящую проблему и получите лучшее решение.   -  person Disillusioned    schedule 22.06.2016
comment
Да, я пишу в Delphi 6. Мне нужны числа с плавающей запятой, потому что размеры исходят из вычислений с плавающей запятой, и я не хочу округлять их каждый раз, когда мне нужно вызвать функцию.   -  person dan matei    schedule 23.06.2016
comment
Что это за расчеты? Возможно, их можно сделать с помощью целочисленной арифметики?   -  person David Heffernan    schedule 23.06.2016


Ответы (3)


Будет невозможно реализовать это и заставить работать точно так же, как SetLength, потому что SetLength — это встроенная функция, которая поддерживает компилятор и среду выполнения. Вот как функция может принимать произвольные типы массивов и строк, а также принимать произвольное количество аргументов измерения. Только поставщик компилятора может сделать такую ​​функцию.

У вас действительно нет надежды создать что-то отдаленно полезное, что соответствует спецификации вопроса. Бесспорно лучший подход — вызвать SetLength напрямую и применить функцию Round к любым аргументам, которые необходимо округлить.

person David Heffernan    schedule 21.06.2016
comment
Я уже округлил каждый аргумент, но подумал, что есть более простое решение, так как утомительно и некрасиво редактировать много кода, добавляя ROUND() во всех местах. Спасибо. - person dan matei; 23.06.2016
comment
Возможно, вы можете уменьшить это влияние, инкапсулировав эти массивы, чтобы не ссылаться на них напрямую в коде приложения, а только в коде библиотеки. - person David Heffernan; 23.06.2016
comment
@danmatei - Round() использует особую стратегию округления, называемую банковским округлением, где 0,5 округляется до ближайшего четного числа (которое может быть в большую или меньшую сторону). Вот почему я предлагаю вам реализовать конкретную стратегию округления, которая, по вашему мнению, подходит для размеров вашего массива, и применять ее в вызовах SetLength(). Я обновлю свой ответ, чтобы прояснить это для тех, кто еще не знает. - person Deltics; 24.06.2016

На ум приходят перегрузка и дженерики:

type
  TMyHelper = class
  public
    class procedure SetLength(var A: String; Len: Double); overload; static;
    class procedure SetLength<T>(var A: TArray<T>; Len: Double); overload; static;
    class procedure SetLength<T>(var A: TArray<TArray<T>>; Len1, Len2: Double); overload; static;
    // as so on...
  end;

class procedure TMyHelper.SetLength(var A: String; Len: Double);
var
  iLen: Integer;
begin
  iLen := ...; // round Len as needed...
  System.SetLength(A, iLen);
end;

class procedure TMyHelper.SetLength<T>(var A: TArray<T>; Len: Double);
var
  iLen: Integer;
begin
  iLen := ...; // round Len as needed...
  System.SetLength(A, iLen);
end;

class procedure TMyHelper.SetLength<T>(var A: TArray<TArray<T>>; Len1, Len2: Double);
var
  iLen1, iLen2: Integer;
begin
  iLen1 := ...; // round Len1 as needed...
  iLen2 := ...; // round Len2 as needed...
  System.SetLength(A, iLen1, iLen2);
end;

// and so on...

var
  s: string;
  a1: TArray<Integer>;
  a2: TArray<TArray<String>>;
begin
  TMyHelper.SetLength(s, 10);
  TMyHelper.SetLength<Integer>(a1, 10);
  TMyHelper.SetLength<String>(a2, 5, 10);
end;

If you are using a version of Delphi that does not support Generics, you can't overload on array element types generically, but you can still overload on arrays if the element types you use is limited, eg:

type
  TInteger1DimArray = array of Integer;
  TInteger2DimArray = array of TInteger1DimArray;

  TString1DimArray = array of String;
  TString2DimArray = array of TString1DimArray;

  // and so on...

  TMyHelper = class
  public
    class procedure SetLength(var A: String; Len: Double); overload; static;
    class procedure SetLength(var A: TInteger1DimArray; Len: Double); overload; static;
    class procedure SetLength(var A: TInteger2DimArray; Len1, Len2: Double); overload; static;
    class procedure SetLength(var A: TString1DimArray; Len: Double); overload; static;
    class procedure SetLength(var A: TString2DimArray; Len1, Len2: Double); overload; static;
    // as so on...
  end;

class procedure TMyHelper.SetLength(var A: String; Len: Double);
var
  iLen: Integer;
begin
  iLen := ...; // round Len as needed...
  System.SetLength(A, iLen);
end;

class procedure TMyHelper.SetLength(var A: TInteger1DimArray; Len: Double);
var
  iLen: Integer;
begin
  iLen := ...; // round Len as needed...
  System.SetLength(A, iLen);
end;

class procedure TMyHelper.SetLength(var A: TInteger2DimArray; Len1, Len2: Double);
var
  iLen1, iLen2: Integer;
begin
  iLen1 := ...; // round Len1 as needed...
  iLen2 := ...; // round Len2 as needed...
  System.SetLength(A, iLen1, iLen2);
end;

class procedure TMyHelper.SetLength(var A: TString1DimArray; Len: Double);
var
  iLen: Integer;
begin
  iLen := ...; // round Len as needed...
  System.SetLength(A, iLen);
end;

class procedure TMyHelper.SetLength(var A: TString2DimArray; Len1, Len2: Double);
var
  iLen1, iLen2: Integer;
begin
  iLen1 := ...; // round Len1 as needed...
  iLen2 := ...; // round Len2 as needed...
  System.SetLength(A, iLen1, iLen2);
end;

// and so on...

var
  s: string;
  a1: TInteger1DimArray;
  a2: TString2DimArray;
begin
  TMyHelper.SetLength(s, 10);
  TMyHelper.SetLength(a1, 10);
  TMyHelper.SetLength(a2, 5, 10);
end;

As you can see, implementing SetLength() for a single String is easy, but implementing SetLength() for arrays can get quite complex, depending on the type of arrays you use. This solution is about as close as you can get in user code without resorting to RTTI so you can call the RTL's DynArraySetLength() function directly.

person Remy Lebeau    schedule 21.06.2016
comment
Спасибо, но ваше решение не охватывает произвольное количество измерений массива. - person dan matei; 23.06.2016
comment
@danmatei не так эффективен, как собственный SetLength(), но при необходимости для каждого дополнительного измерения можно определить дополнительные перегрузки. - person Remy Lebeau; 23.06.2016

Зачем усложнять вещи, пытаясь обойти проблемы с перегрузкой или заменой встроенного ?

Почему бы не реализовать стратегию округления, которую вы использовали бы в своей замене SetLength, и просто включить ее в свои вызовы встроенной функции, где это уместно?

SetLength(arr, ArrayDim(dbl));

Конечно, вы сохраняете все возможности SetLength, в том числе возможность применять правила границ измерения для двойных значений к нескольким измерениям, где это необходимо:

SetLength(multiDimArr, ArrayDim(dblDim1), ArrayDim(dblDim2));

Где ArrayDim (или имя, которое вы считаете нужным) — это функция, которая реализует вашу стратегию округления и возвращает соответствующее измерение массива (целое число) для указанного входного значения (двойное значение). ).

Почему вы должны сделать это, а не просто использовать Round()?

Ответ заключается в том, что Round() в Delphi использует особую стратегию округления, называемую Округление банкиров. Это означает, что n.5 можно округлить в большую сторону или в меньшую сторону, в зависимости от значения n.

Даже если эта стратегия округления подходит для ваших нужд в настоящее время, вы должны реализовать функцию для явного применения конкретной стратегии округления, подходящей для размеров массива, и использовать ее в своих вызовах SetLength().

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

person Deltics    schedule 21.06.2016
comment
Что, если ARR может иметь любое количество измерений? - person dan matei; 23.06.2016
comment
SetLength принимает массивы произвольной размерности - person David Heffernan; 23.06.2016