Merge pull request #23198 from gpanders/iter-fix

fix(iter): allow table values in iterator pipelines
This commit is contained in:
Gregory Anders 2023-04-19 08:23:42 -06:00 committed by GitHub
commit 0ad5237162
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 97 additions and 56 deletions

View File

@ -3037,6 +3037,19 @@ Iter:find({self}, {f}) *Iter:find()*
Iter:fold({self}, {init}, {f}) *Iter:fold()* Iter:fold({self}, {init}, {f}) *Iter:fold()*
Fold an iterator or table into a single value. Fold an iterator or table into a single value.
Examples: >
-- Create a new table with only even values
local t = { a = 1, b = 2, c = 3, d = 4 }
local it = vim.iter(t)
it:filter(function(k, v) return v % 2 == 0 end)
it:fold({}, function(t, k, v)
t[k] = v
return t
end)
-- { b = 2, d = 4 }
<
Parameters: ~ Parameters: ~
• {init} any Initial value of the accumulator. • {init} any Initial value of the accumulator.
• {f} function(acc:any, ...):A Accumulation function. • {f} function(acc:any, ...):A Accumulation function.
@ -3297,9 +3310,7 @@ Iter:totable({self}) *Iter:totable()*
The resulting table depends on the initial source in the iterator The resulting table depends on the initial source in the iterator
pipeline. List-like tables and function iterators will be collected into a pipeline. List-like tables and function iterators will be collected into a
list-like table. If multiple values are returned from the final stage in list-like table. If multiple values are returned from the final stage in
the iterator pipeline, each value will be included in a table. If a the iterator pipeline, each value will be included in a table.
map-like table was used as the initial source, then a map-like table is
returned.
Examples: >lua Examples: >lua
@ -3310,9 +3321,13 @@ Iter:totable({self}) *Iter:totable()*
-- { { 1, 2 }, { 2, 4 }, { 3, 6 } } -- { { 1, 2 }, { 2, 4 }, { 3, 6 } }
vim.iter({ a = 1, b = 2, c = 3 }):filter(function(k, v) return v % 2 ~= 0 end):totable() vim.iter({ a = 1, b = 2, c = 3 }):filter(function(k, v) return v % 2 ~= 0 end):totable()
-- { a = 1, c = 3 } -- { { 'a', 1 }, { 'c', 3 } }
< <
The generated table is a list-like table with consecutive, numeric
indices. To create a map-like table with arbitrary keys, use
|Iter:fold()|.
Return: ~ Return: ~
(table) (table)

View File

@ -18,26 +18,19 @@ ListIter.__call = function(self)
return self:next() return self:next()
end end
--- Special case implementations for iterators on non-list tables.
---@class TableIter : Iter
local TableIter = {}
TableIter.__index = setmetatable(TableIter, Iter)
TableIter.__call = function(self)
return self:next()
end
---@private ---@private
local function unpack(t) local function unpack(t)
if type(t) == 'table' then if type(t) == 'table' and t.__n ~= nil then
return _G.unpack(t) return _G.unpack(t, 1, t.__n)
end end
return t return t
end end
---@private ---@private
local function pack(...) local function pack(...)
if select('#', ...) > 1 then local n = select('#', ...)
return { ... } if n > 1 then
return { __n = n, ... }
end end
return ... return ...
end end
@ -184,10 +177,10 @@ end
--- Collect the iterator into a table. --- Collect the iterator into a table.
--- ---
--- The resulting table depends on the initial source in the iterator pipeline. List-like tables --- The resulting table depends on the initial source in the iterator pipeline.
--- and function iterators will be collected into a list-like table. If multiple values are returned --- List-like tables and function iterators will be collected into a list-like
--- from the final stage in the iterator pipeline, each value will be included in a table. If a --- table. If multiple values are returned from the final stage in the iterator
--- map-like table was used as the initial source, then a map-like table is returned. --- pipeline, each value will be included in a table.
--- ---
--- Examples: --- Examples:
--- <pre>lua --- <pre>lua
@ -198,9 +191,13 @@ end
--- -- { { 1, 2 }, { 2, 4 }, { 3, 6 } } --- -- { { 1, 2 }, { 2, 4 }, { 3, 6 } }
--- ---
--- vim.iter({ a = 1, b = 2, c = 3 }):filter(function(k, v) return v % 2 ~= 0 end):totable() --- vim.iter({ a = 1, b = 2, c = 3 }):filter(function(k, v) return v % 2 ~= 0 end):totable()
--- -- { a = 1, c = 3 } --- -- { { 'a', 1 }, { 'c', 3 } }
--- </pre> --- </pre>
--- ---
--- The generated table is a list-like table with consecutive, numeric indices.
--- To create a map-like table with arbitrary keys, use |Iter:fold()|.
---
---
---@return table ---@return table
function Iter.totable(self) function Iter.totable(self)
local t = {} local t = {}
@ -210,6 +207,12 @@ function Iter.totable(self)
if args == nil then if args == nil then
break break
end end
if type(args) == 'table' then
-- Removed packed table tag if it exists
args.__n = nil
end
t[#t + 1] = args t[#t + 1] = args
end end
return t return t
@ -218,23 +221,35 @@ end
---@private ---@private
function ListIter.totable(self) function ListIter.totable(self)
if self._head == 1 and self._tail == #self._table + 1 and self.next == ListIter.next then if self._head == 1 and self._tail == #self._table + 1 and self.next == ListIter.next then
-- Remove any packed table tags
for i = 1, #self._table do
local v = self._table[i]
if type(v) == 'table' then
v.__n = nil
self._table[i] = v
end
end
return self._table return self._table
end end
return Iter.totable(self) return Iter.totable(self)
end end
---@private
function TableIter.totable(self)
local t = {}
for k, v in self do
t[k] = v
end
return t
end
--- Fold an iterator or table into a single value. --- Fold an iterator or table into a single value.
--- ---
--- Examples:
--- <pre>
--- -- Create a new table with only even values
--- local t = { a = 1, b = 2, c = 3, d = 4 }
--- local it = vim.iter(t)
--- it:filter(function(k, v) return v % 2 == 0 end)
--- it:fold({}, function(t, k, v)
--- t[k] = v
--- return t
--- end)
--- -- { b = 2, d = 4 }
--- </pre>
---
---@generic A ---@generic A
--- ---
---@param init A Initial value of the accumulator. ---@param init A Initial value of the accumulator.
@ -747,7 +762,7 @@ function ListIter.enumerate(self)
local inc = self._head < self._tail and 1 or -1 local inc = self._head < self._tail and 1 or -1
for i = self._head, self._tail - inc, inc do for i = self._head, self._tail - inc, inc do
local v = self._table[i] local v = self._table[i]
self._table[i] = { i, v } self._table[i] = pack(i, v)
end end
return self return self
end end
@ -768,7 +783,7 @@ function Iter.new(src, ...)
count = count + 1 count = count + 1
local v = src[count] local v = src[count]
if v == nil then if v == nil then
return TableIter.new(src) return Iter.new(pairs(src))
end end
t[count] = v t[count] = v
end end
@ -812,25 +827,4 @@ function ListIter.new(t)
return it return it
end end
--- Create a new TableIter
---
---@param t table Table to iterate over. For list-like tables, use ListIter.new instead.
---@return Iter
---@private
function TableIter.new(t)
local it = {}
local index = nil
function it.next()
local k, v = next(t, index)
if k ~= nil then
index = k
return k, v
end
end
setmetatable(it, TableIter)
return it
end
return Iter return Iter

View File

@ -3374,13 +3374,45 @@ describe('lua stdlib', function()
end) end)
it('handles map-like tables', function() it('handles map-like tables', function()
local t = { a = 1, b = 2, c = 3 } local it = vim.iter({ a = 1, b = 2, c = 3 }):map(function(k, v)
local it = vim.iter(t):map(function(k, v)
if v % 2 ~= 0 then if v % 2 ~= 0 then
return k:upper(), v * 2 return k:upper(), v * 2
end end
end) end)
eq({ A = 2, C = 6 }, it:totable())
local t = it:fold({}, function(t, k, v)
t[k] = v
return t
end)
eq({ A = 2, C = 6 }, t)
end)
it('handles table values mid-pipeline', function()
local map = {
item = {
file = 'test',
},
item_2 = {
file = 'test',
},
item_3 = {
file = 'test',
},
}
local output = vim.iter(map):map(function(key, value)
return { [key] = value.file }
end):totable()
table.sort(output, function(a, b)
return next(a) < next(b)
end)
eq({
{ item = 'test' },
{ item_2 = 'test' },
{ item_3 = 'test' },
}, output)
end) end)
end) end)
end) end)