这应该是一个有趣的挑战。 我正在寻找一种尚不存在的算法(据我所知)
getFromDatabase(int page, int size)
。getRecords(int offset, int limit)
。我们必须以某种方式使用给定的
offset
和limit
来检索只能由page
和size
访问的匹配数据库记录。显然,偏移/限制并不总是映射到单个页面/大小。挑战在于找到一种算法,可以通过“理想”次数的 getFromDatabase
调用来检索所有记录。该算法应考虑几个因素:
getFromDatabase
都有一定的开销成本;尽量减少通话次数。我想出了以下算法:http://jsfiddle.net/mwvdlee/A7J9C/ (JS 代码,但算法与语言无关)。本质上它是以下伪代码:
do {
do {
try to convert (offset,limit) to (page,size)
if too much waste
lower limit by some amount
else
call `getDatabaseRecords()`
filter out waste records
increase offset to first record not yet retrieved
lower limit to last records not yet retrieved
} until some records were retrieved
} until all records are retrieved from database
该算法的关键在于确定
too much waste
和some amount
。但这个算法不是最优的,也不能保证它是完整的(很可能是,我只是无法证明它)。
有没有更好的(已知的?)算法,或者我可以做出的改进? 有人对如何解决这个问题有好的想法吗?
正如 @usr 所指出的,在这个问题的大多数变体中(无论是查询数据库、API 还是其他实体),最好尽可能减少调用次数,因为几乎总是返回一些额外的行比发出单独的呼叫便宜。以下 PageSizeConversion 算法将始终找到返回尽可能少的记录的单个调用(这正是它执行搜索的方式)。数据集的开头 (
headWaste
) 或结尾 (tailWaste
) 可能会返回一些额外的记录,需要将数据集容纳在单个页面中。该算法在这里用 Javascript 实现,但很容易移植到任何语言。
function PageSizeConversion(offset, limit) {
var window, leftShift;
for (window = limit; window <= offset + limit; window++) {
for (leftShift = 0; leftShift <= window - limit; leftShift++) {
if ((offset - leftShift) % window == 0) {
this.pageSize = window;
this.page = (offset - leftShift) / this.pageSize;
this.headWaste = leftShift;
this.tailWaste = ((this.page + 1) * this.pageSize) - (offset + limit);
return;
}
}
}
}
var testData = [
{"offset": 0,"limit": 10,"expectedPage": 0,"expectedSize": 10,"expectedHeadWaste": 0,"expectedTailWaste": 0},
{"offset": 2,"limit": 1,"expectedPage": 2,"expectedSize": 1,"expectedHeadWaste": 0,"expectedTailWaste": 0},
{"offset": 2,"limit": 2,"expectedPage": 1,"expectedSize": 2,"expectedHeadWaste": 0,"expectedTailWaste": 0},
{"offset": 5,"limit": 3,"expectedPage": 1,"expectedSize": 4,"expectedHeadWaste": 1,"expectedTailWaste": 0},
{"offset": 3,"limit": 5,"expectedPage": 0,"expectedSize": 8,"expectedHeadWaste": 3,"expectedTailWaste": 0},
{"offset": 7,"limit": 3,"expectedPage": 1,"expectedSize": 5,"expectedHeadWaste": 2,"expectedTailWaste": 0},
{"offset": 1030,"limit": 135,"expectedPage": 7,"expectedSize": 146,"expectedHeadWaste": 8,"expectedTailWaste": 3},
];
describe("PageSizeConversion Tests", function() {
testData.forEach(function(testItem) {
it("should return correct conversion for offset " + testItem.offset + " limit " + testItem.limit, function() {
conversion = new PageSizeConversion(testItem.offset, testItem.limit);
expect(conversion.page).toEqual(testItem.expectedPage);
expect(conversion.pageSize).toEqual(testItem.expectedSize);
expect(conversion.headWaste).toEqual(testItem.expectedHeadWaste);
expect(conversion.tailWaste).toEqual(testItem.expectedTailWaste);
});
});
});
// load jasmine htmlReporter
(function() {
var env = jasmine.getEnv();
env.addReporter(new jasmine.HtmlReporter());
env.execute();
}());
<script src="https://cdn.jsdelivr.net/jasmine/1.3.1/jasmine.js"></script>
<script src="https://cdn.jsdelivr.net/jasmine/1.3.1/jasmine-html.js"></script>
<link href="https://cdn.jsdelivr.net/jasmine/1.3.1/jasmine.css" rel="stylesheet" />
<title>Jasmine Spec Runner</title>
这可能不是完全@Martijn 正在寻找的,因为它偶尔会产生大量浪费的结果。但在大多数情况下,这似乎是解决一般问题的好方法。
偏移/限制页面/页面大小分页原则(精确,不浪费)
如果 page-size == limit 或 (offset % limit) == 0, page == (整数) 其他页面==(浮动)。
要在命令行运行类型“node ConvertOffsetToPage.js”
function convertOffsetToPage(offset, limit) {
const precision = 1000000;
const pageSize = limit;
let page = (offset + limit) / limit;
page = Math.round(page * precision) / precision;
return { page, pageSize };
}
function dbServiceSimulation(page, pageSize, items) {
const start = Math.round((page - 1) * pageSize);
const end = Math.round(page * pageSize);
return items.slice(start, end);
}
function getDataItems(itemCount) {
return Array.from(Array(itemCount), (_, x) => x);
}
const dataItems = getDataItems(1000000);
console.log('\ndata items: ', dataItems);
let offset = parseInt(process.argv[2], 10) || 0;
let limit = parseInt(process.argv[3], 10) || 1;
console.log('\ninput offset: ', offset);
console.log('\ninput limit: ', limit);
const { page, pageSize } = convertOffsetToPage(offset, limit);
console.log('\npage = ', page);
console.log('\npageSize = ', pageSize);
const result = dbServiceSimulation(page, pageSize, dataItems);
console.log('\nresult after core Service call');
console.log('\nresult: ', result)
console.log('\n');
我也遇到了这个问题。我的解决方案(用java)简述如下。首先,简单的案例被解决,然后是棘手的部分:)。它尝试找到最佳页面大小、页码并计算距页面开头的新偏移量。保留原始限制:
public static PageSizeOffsetLimit toPageSize(int offset, int limit) {
if (offset < limit) {
return new PageSizeOffsetLimit(0, offset + limit, offset, limit);
}
if (offset == limit) {
return new PageSizeOffsetLimit(1, limit, 0, limit);
}
for (int size = limit; size < 2 * limit; size++) {
int newOffset = offset % size;
if ((size - limit) >= newOffset) {
return new PageSizeOffsetLimit(offset / size, size, newOffset, limit);
}
}
throw new RuntimeException(String.format(
"Cannot determinate page and size from offset and limit (offset: %s, limit: %s)",
offset,
limit));
}
玩得开心:)
这似乎是先前提出的算法的更有效版本(其想法是使用每个窗口的 tailWaste 值作为退出条件而不是循环 leftShift):
static convert(offset: number, limit: number) {
if (offset < limit) {
return {
page: 0,
pageSize: offset + limit,
headWaste: offset,
tailWaste: 0
}
}
let window, page = 0, headWaste = 0, tailWaste = 0
for (window = limit; window <= offset + limit; window++) {
page = Math.floor(offset / window)
headWaste = offset % window
tailWaste = ((page + 1) * window) - (offset + limit);
if (tailWaste >= 0) {
return {
page,
pageSize: window,
headWaste,
tailWaste
}
}
}
}