生成分页的过程化方法

huangapple go评论73阅读模式
英文:

Procedurally generating pagination

问题

我正在尝试使用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&amp;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&amp;limit=%d">%d</a></li>`, 1, limit, 1)
// pages[1] = fmt.Sprintf(`<li><a href="?page=%d&amp;limit=%d">%d</a></li>`, 2, limit, 2)
// pages[2] = fmt.Sprintf(`<li><a href="?page=%d&amp;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&amp;limit=%d">%d</a></li>`, page, limit, page)
// pages[5] = fmt.Sprintf(`<li><a href="?page=%d&amp;limit=%d">%d</a></li>`, page+1, limit, page+1)
// pages[6] = fmt.Sprintf(`<li><a href="?page=%d&amp;limit=%d">%d</a></li>`, page+2, limit, page+2)
// pages[7] = fmt.Sprintf(`<li><a href="?page=%d&amp;limit=%d">%d</a></li>`, page+3, limit, page+3)
// pages[8] = fmt.Sprintf(`<li><a href="?page=%d&amp;limit=%d">%d</a></li>`, page+4, limit, page+4)
// pages[9] = fmt.Sprintf(`<li><a href="?page=%d&amp;limit=%d">%d</a></li>`, page+5, limit, page+5)
// pages[10] = fmt.Sprintf(`<li><a href="?page=%d&amp;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&amp;limit=%d">%d</a></li>`, totalPages-1, limit, totalPages-1)
// pages[13] = fmt.Sprintf(`<li><a href="?page=%d&amp;limit=%d">%d</a></li>`, totalPages, limit, totalPages)

所以问题是,我该如何实现这个效果?是否有相关的库可用?正确的逻辑是什么?

英文:

I'm trying to create pagination with Go but I'm a bit confused. It's my first time to creating pagination as I used to use laravel's helper class when I was still using PHP.

I tried doing something like:

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

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

And that shows all the pages, I want to create something that would look like:

&lt; 1 2 ... 20 24 25 26 27 ... 200 201 &gt;

25 being current page and 201 being the last page.

I also experimented with something like the following but was quirky on some cases like if the page is close to the start or the end:

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

So the question is, how do I achieve this? Is there a library? What is the correct logic?

答案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)
	}
}

最后一个版本(除非有错误),包括所有内容:

package main

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_

这个实现有多好?

  • 占用的内存不超过参数和当前页面所需的内存。
  • 每个检查只执行一次。
  • 它遵循如果要求写下分页的话,人类会做的事情(自然逻辑)。
  • 调用方只需关注当前页面或分隔符的渲染。
  • 快!瓶颈在于回调函数。

注意,如果需要数组,可以通过闭包实现。通道也是一样的。

英文:

Break down your problem and you will get it.

What you want is to compute the neighbors.

package main

import (
	&quot;fmt&quot;
)

func main() {
	pages(8, 2, 13)
}

func pages(n int, around int, count int) {
	first := n - around
	if first &lt; 1 {
		first = 1
	}
	last := n + around
	if last &gt; count {
		last = count
	}

	if first &gt; 1 {
		for i := 1; i &lt;= 2 &amp;&amp; i &lt; first; i++ {
			fmt.Println(i)
		}
		if 3 &lt; first {
			fmt.Println(&quot;...&quot;)
		}
	}

	for i := first; i &lt;= last; i++ {
		fmt.Println(i)
	}

	if last &lt; count {
		if last &lt;= count-3 {
			fmt.Println(&quot;...&quot;)
		}
		end := count - 1
		if end &lt;= last {
			end = last + 1
		}
		for i := end; i &lt;= count; i++ {
			fmt.Println(i)
		}

	}
}

Improvement : Make number of "prefix" and "suffix" pages variable 生成分页的过程化方法

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

Added a shortened version :

package main

import (
	&quot;fmt&quot;
)

func main() {
	pages(10, 3, 20)
}

func pages(n int, around int, count int) {
	var i int
	for i = 1; i &lt;= 2 &amp;&amp; i&lt;=count; i++ {
		fmt.Println(i)
	}

	if i &lt; n-around {
		fmt.Println(&quot;...&quot;)
		i = n - around
	}

	for ; i &lt;= n+around &amp;&amp; i&lt;=count; i++ {
		fmt.Println(i)
	}

	if i &lt; count-1 {
		fmt.Println(&quot;...&quot;)
		i = count - 1
	}

	for ; i &lt;= count; i++ {
		fmt.Println(i)
	}
}

We can easily wrap things around by providing a callback. Note that channels are very slow.

package main

import (
	&quot;fmt&quot;
)

func main() {
	pages(10, 3, 20, func(i int) {
		if i &lt; 0 {
			fmt.Println(&quot;...&quot;)
			return
		}
		fmt.Println(i)
	})
}

func pages(n int, around int, count int, render func(int)) {
	var i int
	for i = 1; i &lt;= 2 &amp;&amp; i &lt;= count; i++ {
		render(i)
	}

	if i &lt; n-around {
		render(-1)
		i = n - around
	}

	for ; i &lt;= n+around &amp;&amp; i &lt;= count; i++ {
		render(i)
	}

	if i &lt; count-1 {
		render(-1)
		i = count - 1
	}

	for ; i &lt;= count; i++ {
		render(i)
	}
}

Last version (unless bugs) which includes everything :

package main

import (
    &quot;fmt&quot;
)

func main() {
    pages(10, 3, 21,4,4, func(i int) {
        if i &lt; 0 {
            fmt.Println(&quot;...&quot;)
            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 &lt;= start &amp;&amp; i &lt;= count; i++ {
        render(i)
    }

    if i &lt; n-around {
        render(-1)
        i = n - around
    }

    for ; i &lt;= n+around &amp;&amp; i &lt;= count; i++ {
        render(i)
    }

    if i &lt; count-end+1 {
        render(-1)
        i = count - end+1
    }

    for ; i &lt;= count; i++ {
        render(i)
    }
}

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

How good is this ?

  • Take no more memory than required for parameters and current page.
  • Each check is done once.
  • It follows what a human would do if asked to write down pagination. (natural logic)
  • Caller side only has to focus on rendering of current page or separator.
  • Fast ! The bottleneck is the callback.

Note, if an array is needed, one can do it through a closure. Same with channels.

答案2

得分: 2

这是一个简短的实现,它返回需要渲染的页面编号:

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)。

如果相邻的两个页面编号之间的差值大于 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() 的目的是按递增顺序返回页面编号。它是必需的,因为第二个循环会按照不同的顺序添加数字。

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

第二个循环负责添加列表的开头和结尾的页面编号。添加结尾是没问题的(只需向上递增),我们将把开头添加到另一个切片中,然后将其余部分附加到该切片。

这是修改后的代码:

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 上尝试这个变体。

还可以指定列表末尾的页面数量

如果你想要在列表末尾有不同数量的页面,你可以添加一个额外的参数,并在不增加复杂性的情况下使用它:

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 上尝试这个变体。

英文:

Here's a short implementation, which returns the page numbers you need to render:

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

You need to pass the current page (cur), the max page number (max), and how many neighbors (around) you want to list around current and at the ends of the list

If 2 page numbers next to each other have difference > 1, you also need to render ... between them.

Testing it:

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 &gt; 0 &amp;&amp; ps[i-1]+1 &lt; page {
		fmt.Print(&quot;... &quot;)
	}
	fmt.Print(page, &quot; &quot;)
}

Output (try it on the 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 

Eliminating sort.Ints()

The purpose of sort.Ints() is to return page numbers in increasing order. It is needed because the 2nd loop adds numbers out of order.

If we can change it so that the 2nd loop keeps the order, sorting won't be needed anymore.

The 2nd loop is responsible to add page numbers from the ends of the list (beginning and end). Appending end is fine (just have to go upward), and we'll add the beginning to another slice, to which the rest will be appended.

Here it is:

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

Testing and output is the same. Try this variant on the Go Playground.

Also specifying number of pages at the ends

If you want different number of pages at the ends, you can add 1 additional parameter and use it with 0 complexity added:

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

Try this variant on the Go Playground.

huangapple
  • 本文由 发表于 2017年2月7日 23:34:48
  • 转载请务必保留本文链接:https://go.coder-hub.com/42093811.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定