【问题标题】:Procedurally generating pagination程序生成分页
【发布时间】:2017-06-24 22:20:44
【问题描述】:

我正在尝试使用 Go 创建分页,但我有点困惑。这是我第一次创建分页,因为我在使用 PHP 时曾经使用 Laravel 的辅助类。

我尝试做类似的事情:

var totalPages = int(math.Ceil(float64(totalRecords) / float64(recordsPerPage)))

for i := 0; i < totalPages; i++ {
    pages[i] = fmt.Sprintf(`<li><a href="?page=%d&limit=%d">%d</a></li>`, i+1, limit, i+1)
}

这显示了所有页面,我想创建一些看起来像这样的东西:

< 1 2 ... 20 24 25 26 27 ... 200 201 >

25 为当前页,201 为最后一页。

我还尝试了类似以下的方法,但在某些情况下很奇怪,例如页面是否接近开头或结尾:

// pages[0] = fmt.Sprintf(`<li><a href="?page=%d&limit=%d">%d</a></li>`, 1, limit, 1)
// pages[1] = fmt.Sprintf(`<li><a href="?page=%d&limit=%d">%d</a></li>`, 2, limit, 2)
// pages[2] = fmt.Sprintf(`<li><a href="?page=%d&limit=%d">%d</a></li>`, 3, limit, 3)
// pages[3] = `<li><a class="more">&hellip;</a></li>`
// pages[4] = fmt.Sprintf(`<li><a href="?page=%d&limit=%d">%d</a></li>`, page, limit, page)
// pages[5] = fmt.Sprintf(`<li><a href="?page=%d&limit=%d">%d</a></li>`, page+1, limit, page+1)
// pages[6] = fmt.Sprintf(`<li><a href="?page=%d&limit=%d">%d</a></li>`, page+2, limit, page+2)
// pages[7] = fmt.Sprintf(`<li><a href="?page=%d&limit=%d">%d</a></li>`, page+3, limit, page+3)
// pages[8] = fmt.Sprintf(`<li><a href="?page=%d&limit=%d">%d</a></li>`, page+4, limit, page+4)
// pages[9] = fmt.Sprintf(`<li><a href="?page=%d&limit=%d">%d</a></li>`, page+5, limit, page+5)
// pages[10] = fmt.Sprintf(`<li><a href="?page=%d&limit=%d">%d</a></li>`, page+6, limit, page+6)
// pages[11] = `<li><a class="more">&hellip;</a></li>`
// pages[12] = fmt.Sprintf(`<li><a href="?page=%d&limit=%d">%d</a></li>`, totalPages-1, limit, totalPages-1)
// pages[13] = fmt.Sprintf(`<li><a href="?page=%d&limit=%d">%d</a></li>`, totalPages, limit, totalPages)

所以问题是,我该如何实现呢?有图书馆吗?正确的逻辑是什么?

【问题讨论】:

    标签: html go pagination


    【解决方案1】:

    这是一个简短的实现,它返回您需要渲染的页码:

    func pages(cur, max, around int) (r []int) {
        for i := cur - around; i <= cur+around; i++ {
            if i >= 1 && i <= max {
                r = append(r, i)
            }
        }
        for i := 1; i <= around; i++ {
            if i < cur-around {
                r = append(r, i)
            }
            if max+1-i > cur+around {
                r = append(r, max+1-i)
            }
        }
        sort.Ints(r)
        return
    }
    

    您需要传递当前页面 (cur)、最大页码 (max) 以及要在当前页面和列表末尾列出的邻居数 (around)

    如果相邻的2个页码相差>1,还需要在它们之间渲染...

    测试它:

    fmt.Println(pages(1, 1, 2))
    fmt.Println(pages(1, 2, 2))
    fmt.Println(pages(1, 3, 2))
    fmt.Println(pages(1, 4, 2))
    fmt.Println(pages(1, 5, 2))
    fmt.Println(pages(1, 9, 3))
    fmt.Println(pages(25, 201, 2))
    
    ps := pages(25, 201, 3)
    for i, page := range ps {
        if i > 0 && ps[i-1]+1 < page {
            fmt.Print("... ")
        }
        fmt.Print(page, " ")
    }
    

    输出(在Go Playground 上试试):

    [1]
    [1 2]
    [1 2 3]
    [1 2 3 4]
    [1 2 3 4 5]
    [1 2 3 4 7 8 9]
    [1 2 23 24 25 26 27 200 201]
    1 2 3 ... 22 23 24 25 26 27 28 ... 199 200 201 
    

    消除sort.Ints()

    sort.Ints() 的目的是按递增顺序返回页码。之所以需要它,是因为第二个循环无序添加数字。

    如果我们可以改变它,让第二个循环保持顺序,那么就不再需要排序了。

    第二个循环负责从列表的末尾(开始和结束)添加页码。追加 end 很好(只需要向上),我们会将开头添加到另一个切片中,其余部分将追加到该切片中。

    这里是:

    func pages(cur, max, around int) (r []int) {
        for i := cur - around; i <= cur+around; i++ {
            if i >= 1 && i <= max {
                r = append(r, i)
            }
        }
        r2 := make([]int, 0, len(r)+4)
        for i := 1; i <= around; i++ {
            if i < cur-around {
                r2 = append(r2, i)
            }
            if max-around+i > cur+around {
                r = append(r, max-around+i)
            }
        }
        return append(r2, r...)
    }
    

    测试和输出是一样的。在Go Playground 上试试这个变体。

    同时在末尾指定页数

    如果你想在末尾有不同数量的页面,你可以添加 1 个额外的参数并使用它并添加 0 复杂度:

    func pages(cur, max, around, edge int) (r []int) {
        for i := cur - around; i <= cur+around; i++ {
            if i >= 1 && i <= max {
                r = append(r, i)
            }
        }
        r2 := make([]int, 0, len(r)+2*edge)
        for i := 1; i <= edge; i++ {
            if i < cur-around {
                r2 = append(r2, i)
            }
            if max-around+i > cur+around {
                r = append(r, max-around+i)
            }
        }
        return append(r2, r...)
    }
    

    Go Playground 上试试这个变体。

    【讨论】:

    • 获得 10 行,“如果”在循环中,排序。值得吗?
    • 而现在,只有 1 行不同。
    • @programaths 我的变体对我来说更容易理解(可能只是我,但没关系)。使用 sort 只是一次迭代中的尝试,在没有增加复杂性的情况下被淘汰了。第二,我的解决方案将选择页码的逻辑与使用它的逻辑分开,因此它更有用。第三,您的解决方案对至少 1 个有效输入组合产生错误输出(也包括您的第二个版本)。为了能够比较我们的解决方案,首先要让它们正常工作。
    • 您没有检查第一个版本是否已更正。现在已添加绑定检查。
    • 另外,为什么在不需要的时候创建数组?为什么要双重检查?另外,呼叫站点必须处理“...”,同时可以简化使用。添加“前缀”参数并查看。
    【解决方案2】:

    分解你的问题,你会得到它。

    你想要的是计算邻居。

    package main
    
    import (
        "fmt"
    )
    
    func main() {
        pages(8, 2, 13)
    }
    
    func pages(n int, around int, count int) {
        first := n - around
        if first < 1 {
            first = 1
        }
        last := n + around
        if last > count {
            last = count
        }
    
        if first > 1 {
            for i := 1; i <= 2 && i < first; i++ {
                fmt.Println(i)
            }
            if 3 < first {
                fmt.Println("...")
            }
        }
    
        for i := first; i <= last; i++ {
            fmt.Println(i)
        }
    
        if last < count {
            if last <= count-3 {
                fmt.Println("...")
            }
            end := count - 1
            if end <= last {
                end = last + 1
            }
            for i := end; i <= count; i++ {
                fmt.Println(i)
            }
    
        }
    }
    

    改进:使“前缀”和“后缀”页面的数量可变;-)

    见:https://play.golang.org/p/wOOO9GmpNV

    添加了一个缩短的版本:

    package main
    
    import (
        "fmt"
    )
    
    func main() {
        pages(10, 3, 20)
    }
    
    func pages(n int, around int, count int) {
        var i int
        for i = 1; i <= 2 && i<=count; i++ {
            fmt.Println(i)
        }
    
        if i < n-around {
            fmt.Println("...")
            i = n - around
        }
    
        for ; i <= n+around && i<=count; i++ {
            fmt.Println(i)
        }
    
        if i < count-1 {
            fmt.Println("...")
            i = count - 1
        }
    
        for ; i <= count; i++ {
            fmt.Println(i)
        }
    }
    

    我们可以通过提供回调来轻松地进行包装。请注意,频道非常慢。

    package main
    
    import (
        "fmt"
    )
    
    func main() {
        pages(10, 3, 20, func(i int) {
            if i < 0 {
                fmt.Println("...")
                return
            }
            fmt.Println(i)
        })
    }
    
    func pages(n int, around int, count int, render func(int)) {
        var i int
        for i = 1; i <= 2 && i <= count; i++ {
            render(i)
        }
    
        if i < n-around {
            render(-1)
            i = n - around
        }
    
        for ; i <= n+around && i <= count; i++ {
            render(i)
        }
    
        if i < count-1 {
            render(-1)
            i = count - 1
        }
    
        for ; i <= count; i++ {
            render(i)
        }
    }
    

    包含所有内容的最新版本(除非错误):

    主包

    import (
        "fmt"
    )
    
    func main() {
        pages(10, 3, 21,4,4, func(i int) {
            if i < 0 {
                fmt.Println("...")
                return
            }
            fmt.Println(i)
        })
    }
    
    func pages(n int, around int, count int,start int, end int, render func(int)) {
        var i int
        for i = 1; i <= start && i <= count; i++ {
            render(i)
        }
    
        if i < n-around {
            render(-1)
            i = n - around
        }
    
        for ; i <= n+around && i <= count; i++ {
            render(i)
        }
    
        if i < count-end+1 {
            render(-1)
            i = count - end+1
        }
    
        for ; i <= count; i++ {
            render(i)
        }
    }
    

    见:https://play.golang.org/p/KfTORNuHY_

    这有多好?

    • 占用的内存不超过参数和当前页面所需的内存。
    • 每项检查都进行一次。
    • 它遵循人类如果被要求写下分页会做什么。 (自然逻辑)
    • 调用方只需关注当前页面或分隔符的呈现。
    • 快!瓶颈是回调。

    注意,如果需要一个数组,可以通过闭包来实现。与频道相同。

    【讨论】:

    • 是的。修复。添加绑定检查。
    猜你喜欢
    • 2016-05-01
    • 1970-01-01
    • 2013-10-18
    • 1970-01-01
    • 2011-12-01
    • 1970-01-01
    • 2016-07-18
    • 2016-08-30
    • 1970-01-01
    相关资源
    最近更新 更多