функция «Transpose / Zip» работает не так, как ожидалось

#lua

#lua

Вопрос:

Я пытаюсь создать элегантную функцию транспонирования, используя функции mapn и zip в Lua.

Mapn и zip являются следующими (из книги lua):

 function map(func, array)
 local new_array = {}
 for i,v in ipairs(array) do
   new_array[i] = func(v)
 end
 return new_array
end


function mapn(func, ...)
 local new_array = {}
 local i=1
 local arg_length = table.getn(arg)
 while true do
   local arg_list = map(function(arr) return arr[i] end, arg)
   if table.getn(arg_list) < arg_length then return new_array end
   new_array[i] = func(unpack(arg_list))
   i = i 1
 end
end
  

Они работают так, как ожидалось.

Затем я определяю zip и transpose как:

 function zip(...)
  return mapn(function(...) return {...} end,...)
end

function transpose(...)
  return zip(unpack(...))
end
  

Теперь транспонируйте({{1,2},{3,4},{5,6}}) выдает {{1,3,5},{2,4,6}}, как и ожидалось.

Но транспонировать({{1,2},{3,4},{5}}) не выдает {{1,3,5},{2,4}}. Она выдает только одну строку.

Как я могу заставить ее выдать желаемый результат?


Я просто решил вместо этого написать «неэлегантную» функцию. Кажется, нет простого способа использовать mapn и друзей.

 function transp(L)
  local n=#L


  local m,M=1e42,0
  --Get the beginning and end of resultant transpose list.
  for i=1,n do
    for k,v in pairs(L[i]) do
      if M<k then M=k end
      if m>k then m=k end
    end
  end

  local nt={}
  for i=m,M do
    local rt={}
    for j=1,n do
      rt[j]=L[j][i]
    end
    table.insert(nt,rt)
  end
  return nt
end
  

Пожалуйста, критикуйте и улучшайте это решение-кандидат.

Комментарии:

1. Ваш код содержит некоторые артефакты Lua 5.0 (а именно arg и table.getn ). Если вы читаете «Программирование на Lua», убедитесь, что это второе издание.

2. Это unpack(...) выглядит для меня крайне подозрительно. Убедитесь, что вы понимаете, что она делает.

3. Вы уверены, что весь код в вашем сообщении правильный? Я протестировал transpose({{1,2},{3,4},{5,6}}) и transpose({{1,2},{3,4},{5}}) , и это вернулось {{1},{2}} в обоих случаях. Кстати, я использую Lua 5.1.

4. @kikito Я думаю, он имел в виду использовать mapn inside zip вместо map .

5. Проблема в if table.getn(arg_list) < arg_length then return new_array end . Во второй раз, table.getn({2,4}) < table.getn({{1,2},{3,4},{5}}) и поэтому она возвращается перед добавлением arg_list в new_array . Оставьте это, как в ответе понзао, и это должно сработать.

Ответ №1:

Я исправил несколько вещей в вашем коде, и я думаю, что теперь он работает так, как задумано, я добавил встроенные комментарии.

 function map(func, array)
  local new_array = {}
  for i, v in ipairs(array) do
    new_array[#new_array   1] = func(v)
  end 
  return new_array
end

function mapn(func, ...)
  -- Variadic arguments bound to an array.
  local arrays = {...}
  local new_array = {}
  -- Simple for-loop.
  local i = 1 
  while true do
    local arg_list = map(function(arr) return arr[i] end, arrays)
    if #arg_list == 0 then
      break
    end 
    new_array[i] = func(unpack(arg_list))
    i = i   1 
  end 
  return new_array
end


-- Using 'mapn' instead of 'map' (probably how you intended).
function zip(...)
  return mapn(function(...) return {...} end,...)
end

-- Same as before.
function transpose(...)
  return zip(unpack(...))
end
  

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

 for _, row in pairs(transpose({{1,2},{3,4},{5}})) do
  for _, col in pairs(row) do io.write(col .. ' ') end
  io.write('n')
end
-- Output: 1 3 5 
--         2 4
  

Комментарии:

1. Ваш map и его map делают то же самое, из-за того, как ipairs работает.

2. @ponzao: вы выиграли, я допустил небольшую ошибку.

3. @Robin True, исправит это, я не знаю, о чем я думал (некоторое время не кодировал в Lua).

4. Предполагается, что ваш цикл mapn распространяется только на #arrays? Это выглядит неправильно в простом случае, когда в mapn передается только один список.

5. @JohnSmith Вы правы, вот почему всегда следует писать тесты. Я исправлю свой ответ.

Ответ №2:

{5} В вашем примере игнорируется из-за этой строки:

 if table.getn(arg_list) < arg_length then return new_array end
  

Вместо этого вы можете захотеть выйти из цикла только тогда, arg_list когда он пуст.

Затем это даст желаемый результат при условии, что строки монотонно увеличиваются в длине.

В более общем случае, когда более поздние строки могут быть короче предыдущих
(например {{1,2},{3,4,5},{6}} ), вам нужно будет отслеживать длины строк, чтобы учесть отверстия. Это можно сделать, добавив необязательный аргумент (и дополнительное возвращаемое значение) в map , чтобы указать максимальный индекс, i для которого func(array[i]) был вычислен:

 function map(func, array, len)
 local new_array = {}
 len = len or #array
 for i=1,len do
   new_array[i] = func(array[i])
 end
 return new_array, len
end

function mapn(func, ...)
 local new_array = {}
 local i=1
 local arg_length = select('#', ...)
 local args = {...}
 while true do
   local arg_list, num_results = map(function(arr) return arr[i] end, args, arg_length)
   if not next(arg_list) then return new_array end
   new_array[i] = func(unpack(arg_list, 1, num_results))
   i = i 1
 end
end

function zip(...)
  return mapn(function(...) return {...} end,...)
end

function transpose(...)
  return zip(unpack(...))
end