Массивы. Чехарда с индексами: 0-based & 1-based

Автор eeigor, 29 марта 2023, 07:13

0 Пользователи и 1 гость просматривают эту тему.

eeigor

Разработал функцию, которая принимает диапазон листа (при использовании функции на листе).
  Function GetEntries(aData(), ...)
1) Поскольку StarBasic не работает с диапазонами как таковыми, то в функцию в действительности передаются данные диапазона.
Данные представляют собой двумерный массив (строка, столбец), индексы первого элемента начинаются с 1 (см. скриншот). Почему?

2) Позднее мне понадобилось вызывать эту функцию из модуля. Передаю в качестве 1-го аргумента свойство диапазона DataArray. Ну, во-первых, это массив массивов, и его надо сначала преобразовать в двумерный массив. Это несложно, хотя и неэффективно.
Есть ли быстрый способ преобразовать массив так, как это делает Calc? Или StarBasic...
Function DataArrayTo2D(aIn, Optional bTransposed As Boolean)
''' Convert cell range data array (array of arrays) to 2D array.
'''
''' Restriction: The number of elements in all nested arrays is the same
''' (it's not jagged) and is defined by the first nested array.
''' ‹Jagged› – Ru: зазубренный, неровный (о массиве массивов).
''' Arguments:
''' aIn:
''' 1D array of arrays.
''' bTransposed (optional, default is False):
''' If True, swap rows and columns.
''' Returns: 2D array (matrix).
'''
On Error GoTo HandleErrors
Dim aOut, c&, r&

ReDim aOut(UBound(aIn), UBound(aIn(0)))  '0-based
If IsMissing(bTransposed) Then bTransposed = False
 
' NOTE: Dimension 1 is used by default and omitted.
For r = LBound(aIn) To UBound(aIn)
For c = LBound(aIn(r)) To UBound(aIn(r))
If bTransposed Then
aOut(r, c) = aIn(c)(r)  'transposed
Else
aOut(r, c) = aIn(r)(c)
End If
Next c
Next r

DataArrayTo2D = aOut
Exit Function

HandleErrors:
MsgBox Error, MB_ICONEXCLAMATION _
, "Error " & Err & " at line " & Erl & " in DataArrayTo2D()"
End Function

Но почему массив DataArray начинается с 0? По этой причине функцию вызывать не удалось.
Тогда нужна функция, которая преобразует массив массивов DataArray в двумерный массив со смещением индексов обеих размерностей на 1.
См. код выше:
  ReDim aOut(UBound(aIn), UBound(aIn(0)))  '0-based – БЫЛО
  ReDim aOut(1 To UBound(aIn) + 1, 1 To UBound(aIn(0)) + 1)  '1-based – СТАНЕТ
Что, переделывать здесь? Как-то всё это нелогично, и мне не нравится...
Полагаю, что при использовании языка Python эта проблема тоже присутствует.

Правильнее, конечно, было бы спросить, почему в первом случае массив начинается с 1 (при вызове на листе)?
Кто и зачем всё это перепутал?! Какая-то чехарда с индексами. Ведь с 1 индексы строк и столбцов начинаются в Excel. См. скриншот снова, но это не Excel, а Calc.
Ubuntu 18.04 LTS • LibreOffice 7.5.1.2 Community

mikekaganski

Вероятнее всего, что так хотели пользователи, непривычные к нумерации с нуля. В API всё единообразно с нуля, но UDF - это совсем другое.

Но в чём реальная проблема? Почему обязательно надо преобразовывать к одной системе? LBound и UBound в сочетании с текущим индексом позволяют работать единообразно с любыми начальными индексами, хоть минус пять.

  ArrInBase = LBound(ArrIn)
  ArrOutBase = LBound(ArrOut)
  for i = 0 to N
    ArrOut(ArrOutBase + i) = MyFunc(ArrIn(ArrInBase + i))
  next i
С уважением,
Михаил Каганский

eeigor

#2
Михаил, спасибо за отклик. Я поправлю код. Теперь возьму за правило использовать LBound().
    j = LBound(aData, 2)
    For i = LBound(aData) To UBound(aData)
Dim aEntries$()  '0-based

j = LBound(aData, 2)  '1-based, if called from sheet
For i = LBound(aData) To UBound(aData)  '1-based, if called from sheet
' Проверяем, есть ли в результирующем массиве i-й элемент.
If bUnique Then
bFound = False
For m = 0 To UBound(aEntries)
If aEntries(m) = aData(i, j) Then
bFound = True
Exit For
End If
Next m

Проблема решена просто. Вопрос был больше связан с однообразием в подходах. В любом случае, об этой особенности надо знать.

UPD. Использование двух массивов в коде выше с разным основанием (точнее, второй – с переменным) всё равно не есть гуд. Нарушен главный принцип: "безобразно, но однообразно".
Ubuntu 18.04 LTS • LibreOffice 7.5.1.2 Community

sokol92

#3
Я практически везде перешел на использование массива массивов (и не жалею  :) ).
Для UDF-функций проще использовать опцию VBASupport 1, что дает больше возможностей.
Переход к массиву массивов (VBARange - параметр UDF):
Option VbaSupport 1
Option Explicit
Function MyFunc(VBARange)
 Dim aData
 aData=VBARange.cellRange.dataArray
 ' ---
End Function
Владимир.

eeigor

#4
Цитата: sokol92 от 29 марта 2023, 15:02Я практически везде перешел на использование массива массивов
Владимир, когда Вы использует функцию Array, массив будет начинаться с 0, и это 1D массив.

Тут ещё полезно иметь под рукой функцию, реализованную с помощью конвертора по совету от Михаила (@mikekaganski).
ND в имени – это n-размерный массив, AoA – Array of Arrays. 2 (two), читается (to) и означает "преобразовать в" (двойка в этом качестве используется часто).

Function NDArray2AoA(aIn())
''' Returns: An array of arrays of a given type.
''' Works with all data types { % | & | ! | # | $ | @ } except Currency.

Dim oConverter As Object, aOut()

oConverter = createUnoService("com.sun.star.script.Converter")
aOut() = oConverter.convertTo(aIn, "any")
NDArray2AoA = aOut
End Function

Sub Test_NDArray2AoA()
Dim i%(1, 2), d#(1, 2), s$(1, 2), c@(1, 2), v(1, 2), a(3, 2, 1), AoA()
AoA = NDArray2AoA(c)  'replace <a> with <d>, <s>, <c>, <v> and <a> (3D-Array of Variant)
End Sub
Ubuntu 18.04 LTS • LibreOffice 7.5.1.2 Community