• Welcome to the Kancolle Wiki!
  • If you have any questions regarding site content, account registration, etc., please visit the KanColle Wiki Discord

Difference between revisions of "Module:Iterator"

From Kancolle Wiki
Jump to navigation Jump to search
 
(62 intermediate revisions by 5 users not shown)
Line 1: Line 1:
 +
-- [[Category:Todo]]:
 +
-- more generic interface and compositions (filtering, grouping, mapping, sorting, etc.)
 +
-- prevent clients from infinite loops
 +
-- load modules more lazily?
 +
-- generalize equipment/enemy iterators
 +
 +
local U = require('Module:Core')
 +
local Equipment = require('Module:Equipment')
 +
local Ship = require('Module:Ship')
 +
local CollectionShips = require('Module:Collection/Ships')
 +
local CollectionEquipment = require('Module:Data/Equipment')
 +
local EnemyShip = require('Module:EnemyShip')
 +
local CollectionEnemy = require('Module:Collection/EnemyShips')
 +
 
local Iterator = {}
 
local Iterator = {}
  
 +
-- legacy, not a proper iterator
 
function Iterator.array(arr, i, n)
 
function Iterator.array(arr, i, n)
 
     i = i or 0
 
     i = i or 0
Line 15: Line 30:
 
end
 
end
  
function Iterator.equipmentById(context)
+
function stringKey(name, context, n, default)
     local from = context and context.from and tonumber(context.from) or 1
+
    return context and context[name .. (n and tostring(n) or '')] or default
     local to = context and context.to and tonumber(context.to) or 500
+
end
     local collection = require('Module:Collection/Equipment')
+
 
 +
function numberKey(name, context, n, default)
 +
    return context and tonumber(context[name .. (n and tostring(n) or '')]) or default
 +
end
 +
 
 +
-- * Ship iterators.
 +
 
 +
function Iterator.shipsByNo(context)
 +
    local extra = stringKey('extra', context)
 +
    local function prefix(i)
 +
        if extra then
 +
            i = i + 1300
 +
        end
 +
        if (i - 1) % 10 == 0 then
 +
            local key = 'key' .. i
 +
            context['custom_row_' .. key .. '_content'] = string.format('==No.%d-%d==', i, i - 1 + 10)
 +
            return '!' .. key
 +
        end
 +
    end
 +
    local collection = U.ifilter(require('Module:Collection/ShipsByNo'), function(e)
 +
        if extra then
 +
            return e.no >= 1301
 +
        else
 +
            return e.no < 1301
 +
        end
 +
    end)
 +
    local i = 0
 +
    local current
 +
    local prefixFlag = true
 +
    return {
 +
        next = function()
 +
            i = i + 1
 +
            local e = collection[i]
 +
            if e then
 +
                local prefixValue = prefix(i)
 +
                if prefixFlag and prefixValue then
 +
                    i = i - 1
 +
                    current = prefixValue
 +
                    prefixFlag = false
 +
                else
 +
                    current = e.name or '-'
 +
                    prefixFlag = true
 +
                end
 +
                return true
 +
            end
 +
            return false
 +
        end,
 +
        current = function()
 +
            return current
 +
        end,
 +
    }
 +
end
 +
 
 +
function Iterator.shipsBy(context, n, pred, pre, nItems)
 +
    local predKey = stringKey('pred', context, n)
 +
    local pred2
 +
    if predKey then
 +
        pred2 = function(e)
 +
            local obj = Ship(e._name)
 +
            return obj[predKey](obj)
 +
        end
 +
    end
 +
    local preCollection
 +
    local collectionKey = stringKey('collection', context, n)
 +
    if collectionKey then
 +
        local _, CollectionData = U.requireModule(string.format("Collection/%s", collectionKey))
 +
        preCollection = U.icopy(CollectionData)
 +
    else
 +
        preCollection = U.icopy(CollectionShips)
 +
    end
 +
   
 +
    local allowedRemodels = context['allowedRemodels']
 +
     local listBase = (context['listBase'] == 'true')
 +
    local collection = {}
 +
    local index = 1
 +
    for i = 1, #preCollection do
 +
    local _, CollectionData = U.requireModule(string.format("Data/Ship/%s", preCollection[i]))
 +
    if _ and CollectionData then
 +
    for j,v in pairs(CollectionData) do
 +
    if v._suffix then
 +
    if (allowedRemodels == nil or allowedRemodels[v._suffix]) then
 +
    local fullName = string.format("%s/%s", v._name, v._suffix)
 +
    collection[index] = v
 +
    collection[index]._fullName = fullName
 +
    index = index + 1
 +
end
 +
    elseif v._id then
 +
if v._remodel_from or (v._name and listBase) then
 +
    collection[index] = v
 +
    collection[index]._fullName = v._name
 +
    index = index + 1
 +
    end
 +
    end
 +
end
 +
    end
 +
end
 +
   
 +
    local sortKey = stringKey('sort', context, n)
 +
    if sortKey then
 +
        table.sort(collection, function(a, b)
 +
            if a[sortKey] and b[sortKey] and a[sortKey] ~= b[sortKey] then
 +
                return a[sortKey] < b[sortKey]
 +
            elseif a._id == b._id then
 +
            return a._api_id < b._api_id
 +
            else
 +
                return a._id < b._id
 +
            end
 +
        end)
 +
    end
 +
 +
    local i = 1
 +
    local current = nil
 +
    local preFlag = true
 +
    local nCollection = nItems == true and #collection or nItems or #collection
 +
    return {
 +
        next = function()
 +
            for _ = i, nCollection do
 +
                local e
 +
                if nItems then
 +
                    e = U.ifindBy(collection, function(e) return e._id == i end) or { _id = i }
 +
                else
 +
                    e = collection[i]
 +
                end
 +
                if (pred and not pred(e, i)) or (pred2 and not pred2(e, i)) then
 +
                    i = i + 1
 +
                else
 +
                    if pre and preFlag then
 +
                        local value = pre(e, i)
 +
                        if value then
 +
                            current = value
 +
                            preFlag = false
 +
                            return true
 +
                        end
 +
                    end
 +
                    current = e._fullName
 +
                    if nItems and not current then
 +
                        current = '-'
 +
                    end
 +
                    i = i + 1
 +
                    preFlag = true
 +
                    return true
 +
                end
 +
            end
 +
            current = nil
 +
            return false
 +
        end,
 +
        current = function()
 +
            return current
 +
        end,
 +
    }
 +
end
 +
 
 +
function Iterator.shipsByType(context, n)
 +
    local type = numberKey('type', context, n)
 +
    return Iterator.shipsBy(context, n, function(e)
 +
        return type == nil or e._type == type
 +
    end)
 +
end
 +
 
 +
function Iterator.shipsByTrueId(context, n)
 +
    local from = numberKey('from', context, n, 1)
 +
     local to = numberKey('to', context, n, 1500)
 +
    return Iterator.shipsBy(context, n, function(e)
 +
        if e._true_id then
 +
            return e._true_id >= from and e._true_id <= to
 +
        else
 +
            return e._id >= from and e._id <= to
 +
        end
 +
    end)
 +
end
 +
 
 +
-- * Equipment iterators.
 +
 
 +
function Iterator.equipmentBy(context, n, pred, pre, nItems)
 +
    local predKey = stringKey('pred', context, n)
 +
    local pred2
 +
    if predKey then
 +
        pred2 = function(e)
 +
            local obj = Equipment(e._name)
 +
            return obj[predKey](obj)
 +
        end
 +
    end
 +
    local collection
 +
    local collectionKey = stringKey('collection', context, n)
 +
    if collectionKey then
 +
        local _, CollectionData = U.requireModule(collectionKey == 'Equipment' and 'Data/Equipment' or string.format("Collection/%s", collectionKey))
 +
        collection = U.icopy(CollectionData)
 +
    else
 +
        collection = U.icopy(CollectionEquipment)
 +
    end
 +
    local sortKey = stringKey('sort', context, n)
 +
    if sortKey then
 +
        table.sort(collection, function(a, b)
 +
            if a[sortKey] ~= b[sortKey] then
 +
                return a[sortKey] < b[sortKey]
 +
            else
 +
                return a._id < b._id
 +
            end
 +
        end)
 +
    end
 +
    local i = 1
 +
    local current = nil
 +
    local preFlag = true
 +
    local nCollection = nItems == true and #collection or nItems or #collection
 +
    return {
 +
        next = function()
 +
            for _ = i, nCollection do
 +
                local e
 +
                if nItems then
 +
                    e = U.ifindBy(collection, function(e) return e._id == i end) or { _id = i }
 +
                else
 +
                    e = collection[i]
 +
                end
 +
                if pred and not pred(e, i) or pred2 and not pred2(e, i) then
 +
                    i = i + 1
 +
                else
 +
                    if pre and preFlag then
 +
                        local value = pre(e, i)
 +
                        if value then
 +
                            current = value
 +
                            preFlag = false
 +
                            return true
 +
                        end
 +
                    end
 +
                    current = e._name
 +
                    if nItems and not current then
 +
                        current = '-'
 +
                    end
 +
                    i = i + 1
 +
                    preFlag = true
 +
                    return true
 +
                end
 +
            end
 +
            current = nil
 +
            return false
 +
        end,
 +
        current = function()
 +
            return current
 +
        end,
 +
    }
 +
end
 +
 
 +
function Iterator.equipmentById(context, n)
 +
    local from = numberKey('from', context, n, 1)
 +
    local to = numberKey('to', context, n, 1500)
 +
    return Iterator.equipmentBy(context, n, function(e)
 +
        return e._id >= from and e._id <= to
 +
    end)
 +
end
 +
 
 +
function Iterator.equipmentByIdWithHeaders(context, n)
 +
    local from = numberKey('from', context, n, 1)
 +
    local to = numberKey('to', context, n, 1500)
 +
    local prevMod = 0
 +
    return Iterator.equipmentBy(
 +
        context, n,
 +
        function(e)
 +
            return e._id >= from and e._id <= to
 +
        end,
 +
        function(e)
 +
            local currentMod = (e._id - 1) % 10
 +
            if currentMod <= prevMod then
 +
                prevMod = currentMod
 +
                local title = string.format("No. %s - %s", U.pad(e._id - currentMod, 3, "0"), U.pad(e._id - currentMod + 9, 3, "0"))
 +
                return string.format("!#[[Equipment#%s|%s]]", title, title)
 +
            else
 +
                prevMod = currentMod
 +
                return false
 +
            end
 +
        end
 +
    )
 +
end
 +
 
 +
function Iterator.equipmentByIdWithEmptyWithHeaders(context, n)
 +
    local from = numberKey('from', context, n, 1)
 +
     local to = numberKey('to', context, n, 1500)
 +
    local prevMod = 0
 +
    local nItems = (math.floor(U.ilast(CollectionEquipment)._id / 10) + 1) * 10
 +
    return Iterator.equipmentBy(
 +
        context, n,
 +
        function(e)
 +
            return e._id >= from and e._id <= to
 +
        end,
 +
        function(e)
 +
            local currentMod = (e._id - 1) % 10
 +
            if currentMod <= prevMod then
 +
                prevMod = currentMod
 +
                local title = string.format("No. %s - %s", U.pad(e._id - currentMod, 3, "0"), U.pad(e._id - currentMod + 9, 3, "0"))
 +
                return string.format("!#[[Equipment#%s|%s]]", title, title)
 +
            else
 +
                prevMod = currentMod
 +
                return false
 +
            end
 +
        end,
 +
        nItems
 +
    )
 +
end
 +
 
 +
function Iterator.equipmentByType(context, n)
 +
    local type = numberKey('type', context, n)
 +
    return Iterator.equipmentBy(context, n, function(e)
 +
        return e._type == type
 +
    end)
 +
end
 +
 
 +
function Iterator.equipmentByIcon(context, n)
 +
    return Iterator.equipmentBy(context, n, function(e)
 +
        return e._icon == numberKey('icon', context, n)
 +
    end)
 +
end
 +
 
 +
function Iterator.equipmentByTypeAndIcon(context, n)
 +
    local type = numberKey('type', context, n)
 +
    local icon = numberKey('icon', context, n)
 +
    return Iterator.equipmentBy(context, n, function(e)
 +
        return e._type == type and e._icon == icon
 +
    end)
 +
end
 +
 
 +
-- * Enemy iterators.
 +
 
 +
function Iterator.enemiesBy(context, n, pred, pre)
 +
    local collection = U.imap(CollectionEnemy, function(name)
 +
        return EnemyShip(name)
 +
    end)
 +
    table.sort(collection, function(a, b)
 +
        local ai = U.ifind(CollectionEnemy, a:base_name())
 +
        local bi = U.ifind(CollectionEnemy, b:base_name())
 +
        if ai and bi and ai ~= bi then
 +
            return ai < bi
 +
        elseif a._id and b._id and a._id ~= b._id then
 +
            return a._id < b._id
 +
        else
 +
        return false
 +
        end
 +
    end)
 
     local i = 1
 
     local i = 1
     local current
+
     local current = nil
 +
    local preFlag = true
 
     return {
 
     return {
 
         next = function()
 
         next = function()
 
             for _ = i, #collection do
 
             for _ = i, #collection do
                 if i >= from and i <= to then
+
                local e = collection[i]
                     current = collection[i].name
+
                 if pred(e, i) then
 +
                    if pre and preFlag then
 +
                        local value = pre(e, i)
 +
                        if value then
 +
                            current = value
 +
                            preFlag = false
 +
                            return true
 +
                        end
 +
                    end
 +
                     current = e:lua_name()
 
                     i = i + 1
 
                     i = i + 1
 +
                    preFlag = true
 
                     return true
 
                     return true
 
                 end
 
                 end
Line 40: Line 401:
 
end
 
end
  
--[[
+
function Iterator.enemiesByType(context, n)
 +
    local type = numberKey('type', context, n)
 +
    return Iterator.enemiesBy(context, n, function(e)
 +
        return e:type() == type
 +
    end)
 +
end
 +
 
 +
function Iterator.enemiesByTypeAndInstallationAndBoss(context, n)
 +
    local type = numberKey('type', context, n, 0)
 +
   
 +
    local selectInstallation = stringKey('installation', context, n, ''):lower()
 +
    local predInstallation = selectInstallation == 'yes' and
 +
        function(e) return e._speed == 0 end
 +
        or selectInstallation == 'no' and
 +
        function(e) return e._speed ~=0 end
 +
        or
 +
        function(e) return true end
 +
   
 +
    local selectBoss = stringKey('boss', context, n, ''):lower()
 +
    -- treat unknwon _back as bosses? why it is unknwon?
 +
    local predBoss = selectBoss == 'yes' and
 +
        function(e) return (e._back or -11) <= -11 end
 +
        or selectBoss == 'no' and
 +
        function(e) return (e._back or -11) >= -10 end
 +
        or
 +
        function(e) return true end
 +
   
 +
    return Iterator.enemiesBy(context, n, function(e)
 +
        return e._hp -- skip unimplemented units
 +
        and (e:type() == type
 +
            or type == 0
 +
            or type < 0 and e:type() ~= -type) -- skip SS-typed bosses
 +
        and predInstallation(e)
 +
        and predBoss(e)
 +
    end)
 +
end
 +
 
 +
-- * Tests.
 +
 
 
function Iterator.test()
 
function Iterator.test()
     local iterator = Iterator.equipmentById({ from = "11", to = "20" })
+
     function testIterator(name, args)
    while iterator.next() do
+
        local iterator = Iterator[name](args)
        mw.log(iterator.current())
+
        mw.log(name)
 +
        while iterator.next() do
 +
            mw.log('  ' .. (iterator.current() or '?'))
 +
        end
 +
        mw.log()
 
     end
 
     end
 +
   
 +
    testIterator('shipsByType', { type = '11', sort = '_class' , listBase = 'true'})
 +
    testIterator('shipsByType', { sort = '_name'})
 +
    testIterator('shipsByTrueId', { sort = '_id', from = '1', to = '20', listBase = 'true'})
 +
    testIterator('shipsBy', { pred = 'is_auxiliary' , sort = '_name'})
 +
   
 +
    testIterator('equipmentById', { from = '11', to = '20' })
 +
    testIterator('equipmentByIdWithHeaders', { from = '1', to = '30' })
 +
    testIterator('equipmentByType', { type = '2' })
 +
    testIterator('equipmentByType', { type = '1', sort = '_icon' })
 +
    testIterator('equipmentByTypeAndIcon', { type = '1', icon = '16' })
 +
    testIterator('equipmentBy', { pred = 'is_large_caliber_main_gun', sort = '_type' })
 +
   
 +
    testIterator('enemiesByType', { type = '2' })
 
end
 
end
 
-- p.test()
 
-- p.test()
]]--
 
  
 
return Iterator
 
return Iterator

Latest revision as of 15:02, 30 April 2023

Documentation for this module may be created at Module:Iterator/doc

-- [[Category:Todo]]:
-- more generic interface and compositions (filtering, grouping, mapping, sorting, etc.)
-- prevent clients from infinite loops
-- load modules more lazily?
-- generalize equipment/enemy iterators

local U = require('Module:Core')
local Equipment = require('Module:Equipment')
local Ship = require('Module:Ship')
local CollectionShips = require('Module:Collection/Ships')
local CollectionEquipment = require('Module:Data/Equipment')
local EnemyShip = require('Module:EnemyShip')
local CollectionEnemy = require('Module:Collection/EnemyShips')

local Iterator = {}

-- legacy, not a proper iterator
function Iterator.array(arr, i, n)
    i = i or 0
    n = n and math.min(#arr, n) or #arr
    function step(n, i)
        if i < n then
            i = i + 1
            return i, arr[i]
        end
    end
    return function()
        return step, n, i
    end
end

function stringKey(name, context, n, default)
    return context and context[name .. (n and tostring(n) or '')] or default
end

function numberKey(name, context, n, default)
    return context and tonumber(context[name .. (n and tostring(n) or '')]) or default
end

-- * Ship iterators.

function Iterator.shipsByNo(context)
    local extra = stringKey('extra', context)
    local function prefix(i)
        if extra then
            i = i + 1300
        end
        if (i - 1) % 10 == 0 then
            local key = 'key' .. i
            context['custom_row_' .. key .. '_content'] = string.format('==No.%d-%d==', i, i - 1 + 10)
            return '!' .. key
        end
    end
    local collection = U.ifilter(require('Module:Collection/ShipsByNo'), function(e)
        if extra then
            return e.no >= 1301
        else
            return e.no < 1301
        end
    end)
    local i = 0
    local current
    local prefixFlag = true
    return {
        next = function()
            i = i + 1
            local e = collection[i]
            if e then
                local prefixValue = prefix(i)
                if prefixFlag and prefixValue then
                    i = i - 1
                    current = prefixValue
                    prefixFlag = false
                else
                    current = e.name or '-'
                    prefixFlag = true
                end
                return true
            end
            return false
        end,
        current = function()
            return current
        end,
    }
end

function Iterator.shipsBy(context, n, pred, pre, nItems)
    local predKey = stringKey('pred', context, n)
    local pred2
    if predKey then
        pred2 = function(e)
            local obj = Ship(e._name)
            return obj[predKey](obj)
        end
    end
    local preCollection
    local collectionKey = stringKey('collection', context, n)
    if collectionKey then
        local _, CollectionData = U.requireModule(string.format("Collection/%s", collectionKey))
        preCollection = U.icopy(CollectionData)
    else
        preCollection = U.icopy(CollectionShips)
    end
    
    local allowedRemodels = context['allowedRemodels']
    local listBase = (context['listBase'] == 'true')
    local collection = {}
    local index = 1
    for i = 1, #preCollection do
    	local _, CollectionData = U.requireModule(string.format("Data/Ship/%s", preCollection[i]))
    	if _ and CollectionData then
    		for j,v in pairs(CollectionData) do
    			if v._suffix then
    				if (allowedRemodels == nil or allowedRemodels[v._suffix]) then
    					local fullName = string.format("%s/%s", v._name, v._suffix)
    					collection[index] = v
    					collection[index]._fullName = fullName
    					index = index + 1
					end
    			elseif v._id then
					if v._remodel_from or (v._name and listBase) then
    					collection[index] = v
    					collection[index]._fullName = v._name
    					index = index + 1
    				end
    			end
			end
    	end
	end
    
    local sortKey = stringKey('sort', context, n)
    if sortKey then
        table.sort(collection, function(a, b)
            if a[sortKey] and b[sortKey] and a[sortKey] ~= b[sortKey] then
                return a[sortKey] < b[sortKey]
            elseif a._id == b._id then
            	return a._api_id < b._api_id
            else
                return a._id < b._id
            end
        end)
    end
		
    local i = 1
    local current = nil
    local preFlag = true
    local nCollection = nItems == true and #collection or nItems or #collection
    return {
        next = function()
            for _ = i, nCollection do
                local e
                if nItems then
                    e = U.ifindBy(collection, function(e) return e._id == i end) or { _id = i }
                else
                    e = collection[i]
                end
                if (pred and not pred(e, i)) or (pred2 and not pred2(e, i)) then
                    i = i + 1
                else
                    if pre and preFlag then
                        local value = pre(e, i)
                        if value then
                            current = value
                            preFlag = false
                            return true
                        end
                    end
                    current = e._fullName
                    if nItems and not current then
                        current = '-'
                    end
                    i = i + 1
                    preFlag = true
                    return true
                end
            end
            current = nil
            return false
        end,
        current = function()
            return current
        end,
    }
end

function Iterator.shipsByType(context, n)
    local type = numberKey('type', context, n)
    return Iterator.shipsBy(context, n, function(e)
        return type == nil or e._type == type
    end)
end

function Iterator.shipsByTrueId(context, n)
    local from = numberKey('from', context, n, 1)
    local to = numberKey('to', context, n, 1500)
    return Iterator.shipsBy(context, n, function(e)
        if e._true_id then
            return e._true_id >= from and e._true_id <= to
        else
            return e._id >= from and e._id <= to
        end
    end)
end

-- * Equipment iterators.

function Iterator.equipmentBy(context, n, pred, pre, nItems)
    local predKey = stringKey('pred', context, n)
    local pred2
    if predKey then
        pred2 = function(e)
            local obj = Equipment(e._name)
            return obj[predKey](obj)
        end
    end
    local collection
    local collectionKey = stringKey('collection', context, n)
    if collectionKey then
        local _, CollectionData = U.requireModule(collectionKey == 'Equipment' and 'Data/Equipment' or string.format("Collection/%s", collectionKey))
        collection = U.icopy(CollectionData)
    else
        collection = U.icopy(CollectionEquipment)
    end
    local sortKey = stringKey('sort', context, n)
    if sortKey then
        table.sort(collection, function(a, b)
            if a[sortKey] ~= b[sortKey] then
                return a[sortKey] < b[sortKey]
            else
                return a._id < b._id
            end
        end)
    end
    local i = 1
    local current = nil
    local preFlag = true
    local nCollection = nItems == true and #collection or nItems or #collection
    return {
        next = function()
            for _ = i, nCollection do
                local e
                if nItems then
                    e = U.ifindBy(collection, function(e) return e._id == i end) or { _id = i }
                else
                    e = collection[i]
                end
                if pred and not pred(e, i) or pred2 and not pred2(e, i) then
                    i = i + 1
                else
                    if pre and preFlag then
                        local value = pre(e, i)
                        if value then
                            current = value
                            preFlag = false
                            return true
                        end
                    end
                    current = e._name
                    if nItems and not current then
                        current = '-'
                    end
                    i = i + 1
                    preFlag = true
                    return true
                end
            end
            current = nil
            return false
        end,
        current = function()
            return current
        end,
    }
end

function Iterator.equipmentById(context, n)
    local from = numberKey('from', context, n, 1)
    local to = numberKey('to', context, n, 1500)
    return Iterator.equipmentBy(context, n, function(e)
        return e._id >= from and e._id <= to
    end)
end

function Iterator.equipmentByIdWithHeaders(context, n)
    local from = numberKey('from', context, n, 1)
    local to = numberKey('to', context, n, 1500)
    local prevMod = 0
    return Iterator.equipmentBy(
        context, n,
        function(e)
            return e._id >= from and e._id <= to
        end,
        function(e)
            local currentMod = (e._id - 1) % 10
            if currentMod <= prevMod then
                prevMod = currentMod
                local title = string.format("No. %s - %s", U.pad(e._id - currentMod, 3, "0"), U.pad(e._id - currentMod + 9, 3, "0"))
                return string.format("!#[[Equipment#%s|%s]]", title, title)
            else
                prevMod = currentMod
                return false
            end
        end
    )
end

function Iterator.equipmentByIdWithEmptyWithHeaders(context, n)
    local from = numberKey('from', context, n, 1)
    local to = numberKey('to', context, n, 1500)
    local prevMod = 0
    local nItems = (math.floor(U.ilast(CollectionEquipment)._id / 10) + 1) * 10
    return Iterator.equipmentBy(
        context, n,
        function(e)
            return e._id >= from and e._id <= to
        end,
        function(e)
            local currentMod = (e._id - 1) % 10
            if currentMod <= prevMod then
                prevMod = currentMod
                local title = string.format("No. %s - %s", U.pad(e._id - currentMod, 3, "0"), U.pad(e._id - currentMod + 9, 3, "0"))
                return string.format("!#[[Equipment#%s|%s]]", title, title)
            else
                prevMod = currentMod
                return false
            end
        end,
        nItems
    )
end

function Iterator.equipmentByType(context, n)
    local type = numberKey('type', context, n)
    return Iterator.equipmentBy(context, n, function(e)
        return e._type == type
    end)
end

function Iterator.equipmentByIcon(context, n)
    return Iterator.equipmentBy(context, n, function(e)
        return e._icon == numberKey('icon', context, n)
    end)
end

function Iterator.equipmentByTypeAndIcon(context, n)
    local type = numberKey('type', context, n)
    local icon = numberKey('icon', context, n)
    return Iterator.equipmentBy(context, n, function(e)
        return e._type == type and e._icon == icon
    end)
end

-- * Enemy iterators.

function Iterator.enemiesBy(context, n, pred, pre)
    local collection = U.imap(CollectionEnemy, function(name)
        return EnemyShip(name)
    end)
    table.sort(collection, function(a, b)
        local ai = U.ifind(CollectionEnemy, a:base_name())
        local bi = U.ifind(CollectionEnemy, b:base_name())
        if ai and bi and ai ~= bi then
            return ai < bi
        elseif a._id and b._id and a._id ~= b._id then
            return a._id < b._id
        else
        	return false
        end
    end)
    local i = 1
    local current = nil
    local preFlag = true
    return {
        next = function()
            for _ = i, #collection do
                local e = collection[i]
                if pred(e, i) then
                    if pre and preFlag then
                        local value = pre(e, i)
                        if value then
                            current = value
                            preFlag = false
                            return true
                        end
                    end
                    current = e:lua_name()
                    i = i + 1
                    preFlag = true
                    return true
                end
                i = i + 1
            end
            current = nil
            return false
        end,
        current = function()
            return current
        end,
    }
end

function Iterator.enemiesByType(context, n)
    local type = numberKey('type', context, n)
    return Iterator.enemiesBy(context, n, function(e)
        return e:type() == type
    end)
end

function Iterator.enemiesByTypeAndInstallationAndBoss(context, n)
    local type = numberKey('type', context, n, 0)
    
    local selectInstallation = stringKey('installation', context, n, ''):lower()
    local predInstallation = selectInstallation == 'yes' and
        function(e) return e._speed == 0 end
        or selectInstallation == 'no' and
        function(e) return e._speed ~=0 end
        or
        function(e) return true end
    
    local selectBoss = stringKey('boss', context, n, ''):lower()
    -- treat unknwon _back as bosses? why it is unknwon?
    local predBoss = selectBoss == 'yes' and
        function(e) return (e._back or -11) <= -11 end
        or selectBoss == 'no' and
        function(e) return (e._back or -11) >= -10 end
        or
        function(e) return true end
    
    return Iterator.enemiesBy(context, n, function(e)
        return e._hp -- skip unimplemented units
        and (e:type() == type
            or type == 0
            or type < 0 and e:type() ~= -type) -- skip SS-typed bosses
        and predInstallation(e)
        and predBoss(e)
    end)
end

-- * Tests.

function Iterator.test()
    function testIterator(name, args)
        local iterator = Iterator[name](args)
        mw.log(name)
        while iterator.next() do
            mw.log('  ' .. (iterator.current() or '?'))
        end
        mw.log()
    end
    
    testIterator('shipsByType', { type = '11', sort = '_class' , listBase = 'true'})
    testIterator('shipsByType', { sort = '_name'})
    testIterator('shipsByTrueId', { sort = '_id', from = '1', to = '20', listBase = 'true'})
    testIterator('shipsBy', { pred = 'is_auxiliary' , sort = '_name'})
    
    testIterator('equipmentById', { from = '11', to = '20' })
    testIterator('equipmentByIdWithHeaders', { from = '1', to = '30' })
    testIterator('equipmentByType', { type = '2' })
    testIterator('equipmentByType', { type = '1', sort = '_icon' })
    testIterator('equipmentByTypeAndIcon', { type = '1', icon = '16' })
    testIterator('equipmentBy', { pred = 'is_large_caliber_main_gun', sort = '_type' })
    
    testIterator('enemiesByType', { type = '2' })
end
-- p.test()

return Iterator