В Lua, как я должен обрабатывать индекс массива с нулевым значением, который исходит из C?

Внутри кода C у меня есть массив и нулевой индекс, используемые для поиска внутри него, например:

char * names[] = {"Apple", "Banana", "Carrot"};
char * name = names[index];

Из встроенного Lua script у меня есть доступ к index с помощью функции getIndex() и вы хотите реплицировать поиск массива. Есть ли согласованный "лучший" метод для этого, учитывая Lua на основе одного массива?

Например, я мог бы создать массив Lua с тем же содержимым, что и мой массив C, но для индексации требуется добавить 1:

names = {"Apple", "Banana", "Carrot"}
name = names[getIndex() + 1]

Или я мог бы избежать необходимости добавлять 1, используя более сложную таблицу, но это нарушит такие вещи, как #names:

names = {[0] = "Apple", "Banana", "Carrot"}
name = names[getIndex()]

Какой подход рекомендуется?

Изменить: Спасибо за ответы. К сожалению, решение добавления 1 к индексу внутри функции getIndex не всегда применимо. Это связано с тем, что в некоторых случаях индексы "хорошо известны", то есть можно документировать, что индекс 0 означает "Apple" и т.д. В этой ситуации следует ли выбрать один или другой из вышеуказанных решений или есть лучшая альтернатива?

Изменить 2: Еще раз спасибо за ответы и комментарии, они действительно помогли мне задуматься над этой проблемой. Я понял, что могут быть два разных сценария, в которых возникает проблема, и идеальное решение может быть различным для каждого.

В первом случае рассмотрим, например, массив, который может время от времени отличаться и индекс, который является просто относительно текущего массива. Индексы не имеют никакого значения вне кода. Doug Currie и RBerteig абсолютно правильны: массив должен быть основан на 1, а getIndex должен содержать +1. Как уже упоминалось, это позволяет коду на обеих сторонах C и Lua быть идиоматическим.

Второй случай включает индексы, имеющие смысл, и, вероятно, массив, который всегда один и тот же. Крайним примером может быть где names содержит "Zero", "One", "Two". В этом случае ожидаемое значение для каждого индекса хорошо известно, и я чувствую, что сделать индекс на стороне Lua на основе одного неинтуитивно. Я считаю, что один из других подходов должен быть предпочтительным.

Ответы

Ответ 1

Используйте 1-основанные таблицы Lua и похороните + 1 внутри функции getIndex.

Ответ 2

Я предпочитаю

names = {[0] = "Apple", "Banana", "Carrot"}
name = names[getIndex()]

Некоторые функции обработки таблиц - #, insert, remove, sort - нарушены.
Другие - concat(t, sep, 0), unpack(t, 0) - требуют, чтобы явный начальный индекс работал правильно:

print(table.concat(names, ',', 0))  --> Apple,Banana,Carrot
print(unpack(names, 0))             --> Apple   Banana  Carrot

Я ненавижу постоянно помнить об этом +1, чтобы удовлетворить Lua default 1-based style. Вы должны отражать индексы вашего домена, чтобы они были более удобочитаемыми. Если индексы на основе 0 подходят для вашей задачи, вы должны использовать индексы на основе 0 в Lua.

Мне нравится, как индексы массивов реализованы в Pascal: вы абсолютно свободны в выборе любого диапазона, который хотите, например, array[-10..-5]of byte абсолютно подходит для массива из 6 элементов.

Ответ 3

Прежде всего, эта ситуация не уникальна для приложений, которые смешивают Lua и C; вы можете столкнуться с тем же вопросом, даже когда используете только приложения Lua. Например, я использую компонент редактора, который индексирует строки, начинающиеся с 0 (да, это C-based, но я использую только интерфейс Lua), но строки в script, которые я редактирую в редакторе, являются 1 на основе. Итак, если пользователь устанавливает точку останова в строке 3 (начиная с 0 в редакторе), мне нужно отправить команду отладчику, чтобы установить ее в строке 4 в script (и конвертировать назад при ударе точки останова).

Теперь предложения.

(1) Мне лично не нравится использовать [0] хак для массивов, поскольку он ломает слишком много вещей. Вы и Егор уже перечислили многие из них; самое главное для меня он разбивает # и ipairs.

(2) При использовании массивов на основе 1 я стараюсь избегать их индексирования и использовать как можно больше итераторов: for i, v in ipairs(...) do вместо for i = 1, #array do).

(3) Я также пытаюсь изолировать свой код, который имеет дело с этими преобразованиями; например, если вы конвертируете строки в редакторе для управления маркерами и строками в script, тогда есть функции marker2script и script2marker, которые выполняют преобразование (даже если это простые операции +1 и -1). У вас было бы что-то вроде этого в любом случае даже без корректировок + 1/-1, это было бы просто неявным.

(4) Если вы не можете скрыть преобразование (и я согласен, +1 может выглядеть уродливым), сделайте его еще более заметным: используйте вызовы c2l и l2c, которые делают преобразование. По-моему, это не так уродливо, как + 1/-1, но имеет преимущество передачи намерения, а также дает вам простой способ поиска всех мест, где происходит преобразование. Это очень полезно, если вы ищете ошибки от одного или когда изменения API вызывают обновление этой логики.

В целом, я бы не стал слишком беспокоиться об этих аспектах. Я работаю над довольно сложным приложением Lua, которое обертывает несколько компонентов на основе 0 и не запоминает никаких проблем, вызванных различной индексацией...

Ответ 4

Здесь Lua metemethods и metatables пригождаются. Используя прокси-сервер таблицы и несколько метаметодов, вы можете изменить доступ к таблице таким образом, который бы соответствовал вашим потребностям.

local names = {"Apple", "Banana", "Carrot"} -- Original Table
local _names = names -- Keep private access to the table
local names = {}    -- Proxy table, used to capture all accesses to the original table

local mt = {
  __index = function (t,k)
    return _names[k+1]   -- Access the original table
  end,

  __newindex = function (t,k,v)
    _names[k+1] = v   -- Update original table
  end
}

setmetatable(names, mt)  

Итак, что происходит здесь, заключается в том, что исходная таблица имеет прокси-сервер для себя, тогда прокси-сервер ловит каждую попытку доступа к таблице. Когда к таблице обращаются, она увеличивает значение, к которому он был обращен, имитируя массив на основе 0. Вот результат печати:

print(names[0]) --> Apple
print(names[1]) --> Banana
print(names[2]) --> Carrot
print(names[3]) --> nil

names[3] = "Orange" --Add a new field to the table
print(names[3]) --> Orange

Все операции с таблицами действуют так, как обычно. С помощью этого метода вам не нужно беспокоиться о беспорядочном доступе к таблице.

EDIT: Я хотел бы указать, что новая таблица имен является просто прокси для доступа к исходной таблице имен. Поэтому, если вы запросили #names, результат будет равен нулю, потому что сама таблица не имеет значений. Чтобы получить доступ к размеру исходной таблицы, вам нужно запросить #_names.

EDIT 2: Как отметил Чарльз Стюарт в комментарии ниже, вы можете добавить метатет __len в таблицу mt, чтобы гарантировать, что вызов #names дает правильные результаты.

Ответ 5

Почему бы просто не превратить C-массив в массив на основе 1?

char * names[] = {NULL, "Apple", "Banana", "Carrot"};
char * name = names[index];

Откровенно говоря, это приведет к некоторому неинтуитивному коду на стороне С, но если вы настаиваете на том, что должны быть "хорошо известные" индексы, которые работают с обеих сторон, это, пожалуй, лучший вариант.

Чистое решение, конечно же, не должно делать эти "хорошо известные" индексы частью интерфейса. Например, вы можете использовать именованные идентификаторы вместо простых чисел. Enums - это хорошее совпадение для этого на стороне C, тогда как в Lua вы можете даже использовать строки в виде клавиш таблицы.

Другая возможность состоит в том, чтобы инкапсулировать таблицу за интерфейсом, чтобы пользователь никогда не обращался непосредственно к массиву, а только через вызов функции C, который затем может выполнять сколь угодно сложные преобразования индексов. Тогда вам нужно только открыть эту функцию C в Lua, и у вас есть чистое и поддерживаемое решение.

Ответ 6

Почему бы не представить свой массив C в Lua как userdata? Этот метод описывается кодом PiL, раздел "Userdata" ; вы можете установить метатетируемые методы __index, __newindex и __len, и вы можете наследовать от класса, чтобы обеспечить другие функции обработки последовательности как обычные методы (например, определить массив с array.remove, array.sort, array.pairs функции, которые могут быть определены как методы объекта путем дополнительной настройки на __index). Выполнение этого способа означает, что у вас нет проблем с синхронизацией между Lua и C, и это позволяет избежать рисков, которые таблицы "массива" обрабатываются как обычные таблицы, что приводит к ошибкам "один за другим".

Ответ 7

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

function iarray(a)
  local n = 0
  local s = #a
  if a[0] ~= nil then
    n = -1
  end
  return function()
    n = n + 1
    if n <= s then return n,a[n] end
  end
end

Однако вам все равно придется добавить нулевой элемент вручную:

Пример использования:

myArray = {1,2,3,4,5}
myArray[0] = 0
for _,e in iarray(myArray) do
  -- do something with element e
end