假设我们想要从4套S1-S4中取出物品。集合在它们之间共享项目。一些示例集将是:
S1 = ['b1'];
S2 = ['b1', 'b2'];
S3 = ['b1', 'b2', 'b3'];
S4 = ['b1', 'b2', 'b3', 'b4'];
很明显,如果您从S1
(b1
)中选择一项,那么您只能选择S2
中的1项,S3
中的2项和来自S4
的3项S4
。
但是,如果您从S1
中选择一项,那么在下一步中,您可以从S2
中选择所有项目或来自S3
<的所有项目strong>或来自b4
的所有项目,只是因为算法必须足够聪明才能知道存在组合(例如,从S4
分配b4
),这将允许来自其他集合的最高免费元素数量较高(通过选择S3
S2
中的最大免费3,S1
中的2和S5
中的1
换句话说,算法,当你占用一个元素中的一个元素时,不应该占用一个特定的元素,而是足够聪明,知道有多少元素(最大数量)可以考虑到以聪明的方式挑选物品,可以从其他套装中占用。
演示它应该如何工作(绿色=可用,红色=占用,橙色=由于来自另一组的占用而被占用):
另一个示范:
(请注意,该算法发现S5有3个唯一元素,因此任何其他集合都是完全免费的)
还有一个:
(请注意,算法理解如果你从S5中选择全部4,那么你必须放弃其他类别中的一个)
另一个:
(如果您从S2
中选择3项,则必须放弃S2
中的1项,现在您最多只能从中拾取2个元素。请注意b1
中的已占用项目不是特定元素,它是b7
或nodejs
,它并不重要,我只对什么感兴趣我现在可以从中挑选的最大元素数量)
上述所有工作都适用于我的算法,但在其他情况下失败。
我到目前为止所尝试的内容(var allCategories = [];
const process = require('process');
Reset = '\x1b[0m'
FgRed = '\x1b[31m'
FgGreen = '\x1b[32m'
FgYellow = '\x1b[33m'
function getCommonCount(s1, s2) {
return s1.filter((n) => s2.includes(n)).length;
}
var categories = {};
var common = {};
function processCategories() {
categories = {};
for (var i in allCategories) {
categories[i] = {
total: allCategories[i].length,
occupied: 0,
dueTo: {},
totalDue: 0
};
common[i] = {};
for (var j in allCategories) {
if (i === j) {
continue;
}
common[i][j] = getCommonCount(allCategories[i], allCategories[j]);
}
}
}
function occupy(countToOccupy, categoryId) {
console.log('OCCUPY', countToOccupy, 'from', categoryId);
var category = categories[categoryId];
var free = category.total - category.occupied;
if (free < countToOccupy) {
console.log('Cannot occupy that many elements of type', categoryId);
return false;
}
category.occupied += countToOccupy;
for (var otherCategoryId in common[categoryId]) {
var commonCount = common[categoryId][otherCategoryId];
var atLeastThatMustOccupied = (category.occupied + commonCount) - category.total;
if (atLeastThatMustOccupied < 0) {
continue;
};
var otherCategory = categories[otherCategoryId];
if (otherCategory.occupied < atLeastThatMustOccupied) {
var countToOccupyOtherCategory = atLeastThatMustOccupied - otherCategory.occupied;
otherCategory.occupied += countToOccupyOtherCategory;
if (!otherCategory.dueTo.hasOwnProperty(categoryId)) {
otherCategory.dueTo[categoryId] = 0;
}
otherCategory.dueTo[categoryId] += countToOccupyOtherCategory;
otherCategory.totalDue += countToOccupyOtherCategory;
}
}
return true;
}
function deoccupy(countToDeoccupy, categoryId) {
console.log('DEOCCUPY', countToDeoccupy, 'from', categoryId);
var category = categories[categoryId];
var reallyOccupied = (category.occupied - category.totalDue);
if (reallyOccupied < countToDeoccupy) {
console.log('There are not that much really occupied thingies here so as to deoccupy them');
return false;
}
category.occupied -= countToDeoccupy;
for (var otherCategoryId in common[categoryId]) {
if (!categories[otherCategoryId].dueTo.hasOwnProperty(categoryId)) {
continue; // no elements set due to the parent category
}
var dueToNumber = categories[otherCategoryId].dueTo[categoryId];
var countToRemove = Math.min(countToDeoccupy, dueToNumber);
if (countToRemove === dueToNumber) {
// remove the dueTo field
delete categories[otherCategoryId].dueTo[categoryId];
} else {
categories[otherCategoryId].dueTo[categoryId] -= countToRemove;
}
categories[otherCategoryId].totalDue -= countToRemove;
categories[otherCategoryId].occupied -= countToRemove;
}
return true;
}
function visualizeCategories() {
process.stdout.write(Reset);
console.log()
for (let categoryId in categories) {
var category = categories[categoryId];
var reallyOccupied = (category.occupied - category.totalDue);
var free = category.total - category.occupied;
process.stdout.write(FgGreen + categoryId + ' ');
process.stdout.write(FgRed + '0'.repeat(reallyOccupied));
process.stdout.write(FgYellow + '0'.repeat(category.totalDue));
process.stdout.write(FgGreen + '0'.repeat(free));
console.log()
}
console.log(Reset);
}
allCategories = {
S1: ['b1'],
S2: ['b1', 'b2'],
S3: ['b1', 'b2', 'b3'],
S4: ['b1', 'b2', 'b3', 'b4'],
S5: ['b5', 'b1', 'b7', 'b8']
};
console.log('CATEGORIES:');
console.log(allCategories);
processCategories();
occupy(1, 'S1'); visualizeCategories();
occupy(3, 'S5'); visualizeCategories();
occupy(1, 'S2'); visualizeCategories();
occupy(1, 'S3'); visualizeCategories();
deoccupy(3, 'S5'); visualizeCategories();
deoccupy(1, 'S1'); visualizeCategories();
deoccupy(1, 'S3'); visualizeCategories();
deoccupy(1, 'S2'); visualizeCategories();
allCategories = {
S1: ['b1'],
S2: ['b1', 'b2'],
S3: ['b2']
};
console.log('CATEGORIES:');
console.log(allCategories);
processCategories();
occupy(1, 'S1', true); visualizeCategories();
occupy(1, 'S3', true); visualizeCategories();
代码,但欢迎使用任何语言解决方案):
processCategories
代码说明:
occupy
分析可视化类别并创建数据结构,用于确定deoccupy
和category = { // or "set" of items
total: // NUMBER: total entries of this set
occupied: // NUMBER: how many elements of this set are occupied, either directly or indirectly
dueTo: // OBJECT {categoryId -> number}: how many elements of this set are indirectly occupied and due to which category. (e.g. dueTo = {S1: 1, S3: 1} means 1 occupied indirectly through S1 and 1 indirectly through S3)
totalDue: 0 // NUMBER: sum of the dueTo entries
};
调用中需要占用和占用的内容。数据结构如下所示:
common
注意:
common['S1']['S2'] === 4
对象,它本质上是一个二维数组,它保存了类别有多少常见元素。 (例如occupied - totalDue
)var atLeastThatMustOccupied = (category.occupied + commonCount) - category.total;
给出了直接占用的(红色)条目。为了占用条目我使用
dueTo
在某些情况下似乎有效,但在其他情况下则无法正常工作。
SELECT id, name,
CASE
WHEN dob2 < current_date THEN dob2 + interval '1 year'
ELSE dob2
END
AS birthday
FROM people,
make_date(extract(year from current_date)::int, extract(month from dob)::int, extract(day from dob)::int) as dob2
WHERE is_active = true
ORDER BY birthday
LIMIT 5;
用于取消占用条目。在最糟糕的情况下,真实世界的场景中,这些集最多可以有300个项目,最多可以有10个集合,并且算法应该足够快(希望<1s in common cpus)来确定可以挑选多少项目从所有集。此外,该算法应该能够解除占用项目,例如做相反的事情 - 从集合中删除一个项目,并确定现在可以从其他集合中选择多少项目。
假设元素只有在直接从该集合占用时才能从集合中解除占用。 (例如,如果S3 = [&#39; b1&#39;,&#39; b2&#39;,&#39; b3&#39;]并且其所有元素都间接占用,则无法调用de-occupy从S3中删除元素