Skip to content

Commit

Permalink
feat(permute) add permutations of values besides order
Browse files Browse the repository at this point in the history
  • Loading branch information
Tieske committed Oct 9, 2020
1 parent 5bbc1f9 commit 89f0b80
Show file tree
Hide file tree
Showing 3 changed files with 217 additions and 2 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@

see [CONTRIBUTING.md](CONTRIBUTING.md#release-instructions-for-a-new-version) for release instructions

## unreleased

- deprecate: `permute.iter`, renamed to `permute.order_iter` (removal later)
[#360](https://github.com/lunarmodules/Penlight/pull/360)
- deprecate: `permute.table`, renamed to `permute.order_table` (removal later)
[#360](https://github.com/lunarmodules/Penlight/pull/360)
- feat: `permute.list_iter` to iterate over different sets of values
[#360](https://github.com/lunarmodules/Penlight/pull/360)
- feat: `permute.list_table` generate table with different sets of values
[#360](https://github.com/lunarmodules/Penlight/pull/360)


## 1.9.1 (2020-09-27)

- fix: dir.walk [#350](https://github.com/lunarmodules/Penlight/pull/350)
Expand Down
86 changes: 84 additions & 2 deletions lua/pl/permute.lua
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,10 @@ function permute.order_iter(a)
end


--- construct a table containing all the permutations of a list.
--- construct a table containing all the order-permutations of a list.
-- @param a list-like table
-- @return a table of tables
-- @usage permute.table {1,2,3} --> {{2,3,1},{3,2,1},{3,1,2},{1,3,2},{2,1,3},{1,2,3}}
-- @usage permute.order_table {1,2,3} --> {{2,3,1},{3,2,1},{3,1,2},{1,3,2},{2,1,3},{1,2,3}}
function permute.order_table (a)
assert_arg(1,a,'table')
local res = {}
Expand All @@ -80,16 +80,98 @@ end



--- an iterator over all permutations of the elements of the given lists.
-- @param ... list-like tables, they are nil-safe if a length-field `n` is provided (see `utils.pack`)
-- @return an iterator which provides the next permutation as return values in the same order as the provided lists, preceeded by an index
-- @usage
-- local strs = utils.pack("one", nil, "three") -- adds an 'n' field for nil-safety
-- local bools = utils.pack(true, false)
-- local iter = permute.list_iter(strs, bools)
--
-- print(iter()) --> 1, three, false
-- print(iter()) --> 2, three, true
-- print(iter()) --> 3, nil, false
-- print(iter()) --> 4, nil, true
-- print(iter()) --> 5, one, false
-- print(iter()) --> 6, one, true
function permute.list_iter(...)
local elements = {...}
local pointers = {}
for i, list in ipairs(elements) do
assert_arg(i,list,'table')
pointers[i] = list.n or #list
end
local count = 0

return function()
if pointers[1] == 0 then return end -- we're done
count = count + 1
local r = { n = #elements }
local cascade_down = true
for i = #elements, 1, -1 do
r[i] = elements[i][pointers[i]]
if cascade_down then
pointers[i] = pointers[i] - 1
if pointers[i] > 0 then
-- this list is not done yet, stop cascade
cascade_down = false
else
-- this list is done
if i ~= 1 then
-- reset pointer
pointers[i] = elements[i].n or #elements[i]
end
end
end
end
return count, utils.unpack(r)
end
end



--- construct a table containing all the permutations of a set of lists.
-- @param ... list-like tables, they are nil-safe if a length-field `n` is provided
-- @return a list of lists, the sub-lists have an 'n' field for nil-safety
-- @usage
-- local strs = utils.pack("one", nil, "three") -- adds an 'n' field for nil-safety
-- local bools = utils.pack(true, false)
-- local results = permute.list_table(strs, bools)
-- -- results = {
-- -- { "three", false, n = 2 },
-- -- { "three, true, n = 2 },
-- -- { nil, false, n = 2 },
-- -- { nil, true, n = 2 },
-- -- { "one, false, n = 2 },
-- -- { "one, true, n = 2 }
-- -- }
function permute.list_table(...)
local iter = permute.list_iter(...)
local results = {}
local i = 1
while true do
local values = utils.pack(iter())
if values[1] == nil then return results end
for i = 1, values.n do values[i] = values[i+1] end
values.n = values.n - 1
results[i] = values
i = i + 1
end
end


-- backward compat, to be deprecated

--- deprecated.
-- @param ...
-- @see permute.order_iter
function permute.iter(...)
--TODO: add deprecation warning here
return permute.order_iter(...)
end

--- deprecated.
-- @param ...
-- @see permute.order_iter
function permute.table(...)
--TODO: add deprecation warning here
Expand Down
121 changes: 121 additions & 0 deletions spec/permute_spec.lua
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
local permute = require("pl.permute")
local tcopy = require("pl.tablex").copy
local utils = require("pl.utils")

describe("pl.permute", function()

Expand Down Expand Up @@ -89,4 +90,124 @@ describe("pl.permute", function()

end)



describe("list_iter", function()

it("returns all combinations from sub-lists", function()
local result = {}
local strs = {"one", "two", "three"}
local ints = { 1,2,3 }
local bools = { true, false }
for count, str, int, bool in permute.list_iter(strs, ints, bools) do
result[#result+1] = {count, str, int, bool}
end
assert.same({
[1] = {1, 'three', 3, false },
[2] = {2, 'three', 3, true },
[3] = {3, 'three', 2, false },
[4] = {4, 'three', 2, true },
[5] = {5, 'three', 1, false },
[6] = {6, 'three', 1, true },
[7] = {7, 'two', 3, false },
[8] = {8, 'two', 3, true },
[9] = {9, 'two', 2, false },
[10] = {10, 'two', 2, true },
[11] = {11, 'two', 1, false },
[12] = {12, 'two', 1, true },
[13] = {13, 'one', 3, false },
[14] = {14, 'one', 3, true },
[15] = {15, 'one', 2, false },
[16] = {16, 'one', 2, true },
[17] = {17, 'one', 1, false },
[18] = {18, 'one', 1, true },
}, result)
end)


it("is nil-safe, given 'n' is set", function()
local result = {}
local strs = utils.pack("one", "two", nil)
local bools = utils.pack(nil, true, false)
for count, bool, str in permute.list_iter(bools, strs) do
result[#result+1] = {count, bool, str}
end
assert.same({
[1] = {1, false, nil },
[2] = {2, false, 'two' },
[3] = {3, false, 'one' },
[4] = {4, true, nil },
[5] = {5, true, 'two' },
[6] = {6, true, 'one' },
[7] = {7, nil, nil },
[8] = {8, nil, 'two' },
[9] = {9, nil, 'one' },
}, result)
end)


it("returns nil on empty list", function()
local count = 0
for list in permute.list_iter({}) do
count = count + 1
end
assert.equal(0, count)
end)

end)



describe("list_table", function()

it("returns all combinations from sub-lists", function()
local strs = {"one", "two", "three"}
local ints = { 1,2,3 }
local bools = { true, false }
assert.same({
[1] = {'three', 3, false, n = 3 },
[2] = {'three', 3, true, n = 3 },
[3] = {'three', 2, false, n = 3 },
[4] = {'three', 2, true, n = 3 },
[5] = {'three', 1, false, n = 3 },
[6] = {'three', 1, true, n = 3 },
[7] = {'two', 3, false, n = 3 },
[8] = {'two', 3, true, n = 3 },
[9] = {'two', 2, false, n = 3 },
[10] = {'two', 2, true, n = 3 },
[11] = {'two', 1, false, n = 3 },
[12] = {'two', 1, true, n = 3 },
[13] = {'one', 3, false, n = 3 },
[14] = {'one', 3, true, n = 3 },
[15] = {'one', 2, false, n = 3 },
[16] = {'one', 2, true, n = 3 },
[17] = {'one', 1, false, n = 3 },
[18] = {'one', 1, true, n = 3 },
}, permute.list_table(strs, ints, bools))
end)


it("is nil-safe, given 'n' is set", function()
local strs = utils.pack("one", "two", nil)
local bools = utils.pack(nil, true, false)
assert.same({
[1] = {false, nil, n = 2 },
[2] = {false, 'two', n = 2 },
[3] = {false, 'one', n = 2 },
[4] = {true, nil, n = 2 },
[5] = {true, 'two', n = 2 },
[6] = {true, 'one', n = 2 },
[7] = {nil, nil, n = 2 },
[8] = {nil, 'two', n = 2 },
[9] = {nil, 'one', n = 2 },
}, permute.list_table(bools, strs))
end)


it("returns nil on empty list", function()
assert.same({}, permute.list_table({}))
end)

end)

end)

0 comments on commit 89f0b80

Please sign in to comment.