-
Notifications
You must be signed in to change notification settings - Fork 21
/
Copy pathcompanions.js
173 lines (153 loc) · 4.5 KB
/
companions.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
/**
* TODO Most of this should be eventually integrated with the new companionship
* algorithm.
*
* @namespace shared
*/
const { Combinations } = require('./combinations.js');
/**
* Get other crops that are compatible with the given set of crops.
* The crops in the sum set are all compatible.
*
* TODO This doesn't scale at all with thousands of crops.
*
* @param {Object[]} allCrops
* @param {Object[]} relationships
* @param {Object[]} crops
* @return {Object[]} Array of compatible crops
*/
function getCompatibleCrops(allCrops, relationships, crops) {
assignRelationships(relationships, allCrops);
assignIsCompatible(allCrops, isCompanion);
const combinations = new Combinations(allCrops, crops.length + 1);
const initialCrops = crops.map(a => allCrops.find(b => a._id == b._id));
const compatibleCrops = combinations
.getLargestCombinationWithElements(initialCrops)
.filter(crop => !initialCrops.includes(crop));
compatibleCrops.forEach(crop => {
delete crop.relationships;
delete crop.isCompatible;
});
return compatibleCrops;
}
/**
* Divide the given crops into groups of compatible crops.
*
* @param {Object[]} relationships
* @param {Object[]} crops
* @return {Object[]} Array of crop groups
*/
function getCropGroups(relationships, crops) {
assignRelationships(relationships, crops);
let groups = [];
// Create groups of companion crops
assignIsCompatible(crops, isCompanion);
const companionCombinations = new Combinations(crops);
groups = groups.concat(
removeCropGroupsFromCombinations(
companionCombinations,
companionCombinations.getLargestCombinationSize()
)
);
// From the remaining crops, create small groups of neutral crops
const remainingCrops = companionCombinations.getElements();
assignIsCompatible(remainingCrops, isNeutral);
const neutralCombinations = new Combinations(remainingCrops);
if (neutralCombinations.getLargestCombinationSize() >= 2) {
groups = groups.concat(
removeCropGroupsFromCombinations(neutralCombinations, 2)
);
}
// From the remaining crops, create single crop groups
groups = groups.concat(neutralCombinations.getCombinations(1));
groups.forEach(group =>
group.forEach(crop => {
delete crop.relationships;
delete crop.isCompatible;
})
);
return groups;
}
/**
* Given all compatible combinations, get non-overlapping crop groups,
* and update the Combinations object by removing the crops of these
* groups.
*
* @param {Combinations} combinations
* @param {Number} maximumGroupSize
* @return {Array}
*/
function removeCropGroupsFromCombinations(combinations, maximumGroupSize) {
const groups = [];
while (maximumGroupSize >= 2) {
const cursorCombinations = combinations.getCombinations(maximumGroupSize);
if (cursorCombinations.length > 0) {
const combination = cursorCombinations[0];
combinations.removeElements(combination);
groups.push(combination);
} else {
maximumGroupSize--;
}
}
return groups;
}
function assignRelationships(relationships, crops) {
for (let index = 0; index < crops.length; index++) {
const crop = crops[index];
crop.relationships = relationships.filter(relationship =>
containsCrop(relationship, crop)
);
}
}
/**
* Assign isCompatible() for the given Crop documents for use with the
* Combinations class.
*
* @param {Crop[]} crops
* @param {Function} isCompatibleFunction
*/
function assignIsCompatible(crops, isCompatibleFunction) {
crops.forEach(crop => {
crop.isCompatible = isCompatibleFunction;
});
}
/**
* Implements isCompatible() for Crop Combinations. Check if the given
* other crop is compatible.
*
* @param {Crop} crop
* @return {Boolean}
*/
function isCompanion(crop) {
return this.relationships.some(
relationship =>
containsCrop(relationship, crop) && relationship.compatibility > 0
);
}
/**
* Implements isCompatible() for Crop Combinations. Check if the given
* other crop is neutral but not incompatible.
*
* @param {Crop} crop
* @return {Boolean}
*/
function isNeutral(crop) {
return this.relationships.some(
relationship =>
!containsCrop(relationship, crop) ||
(containsCrop(relationship, crop) && relationship.compatibility == 0)
);
}
/**
* @param {Object} relationship
* @param {Object} crop
* @return {Boolean}
*/
function containsCrop(relationship, crop) {
const id = crop._id;
return relationship.crop0 == id || relationship.crop1 == id;
}
module.exports = {
getCropGroups,
getCompatibleCrops
};