officekit
Version:
A toolkit to represent and visualize office spaces. It also provides functionality to simulate usage of offices, and calculate a score given a fitness function. It also have functionatlity for automatic allocation of offices using a genetic algorithm.
1,648 lines (1,550 loc) • 244 kB
JavaScript
class World {
constructor() {
// console.log('World created');
}
}
// Reservations
class Reservations {
// reservations: Reservation[] = [];
map = new Map();
people = new Map();
teams = new Map();
flex = [];
constructor() { }
add(reservation) {
this.map.set(reservation.seatId, reservation);
if (reservation.people.length === 0 && reservation.teams.length === 0) {
this.flex.push(reservation);
}
if (reservation.people.length > 0) {
reservation.people.forEach((person) => {
if (!this.people.has(person.id)) {
this.people.set(person.id, []);
}
this.people.get(person.id)?.push(reservation);
});
}
if (reservation.teams.length > 0) {
reservation.teams.forEach((team) => {
if (!this.teams.has(team.id)) {
this.teams.set(team.id, []);
}
this.teams.get(team.id)?.push(reservation);
});
}
}
getAllFlex() {
return this.flex;
}
getByPerson(person) {
return this.people.get(person.id) || [];
}
getByTeam(team) {
let teamRes = this.teams.get(team.id) || [];
return teamRes.filter((res) => res.people.length === 0);
}
hasSeatId(id) {
return this.map.has(id);
}
getBySeatId(id) {
return this.map.get(id);
}
}
class OfficeBuilding {
world;
label = '';
width;
height;
floors = new Map();
reservations;
constructor(world, label = '', width = 20, height = 5) {
this.world = world;
this.width = width;
this.height = height;
this.label = label;
this.reservations = new Reservations();
}
getSeat(id) {
for (const floor of this.floors.values()) {
const seat = floor.getSeat(id);
if (seat) {
return seat;
}
}
return null;
}
getUniqueSeats(count) {
let allSeats = this.getAllSeats();
let seats = [];
while (seats.length < count && allSeats.length > 0) {
const randomIndex = Math.floor(Math.random() * allSeats.length);
const seat = allSeats.splice(randomIndex, 1)[0];
seats.push(seat);
}
return seats;
}
getUniqueSeatOtherThan(otherSeats, thisSeat, radius) {
if (radius) {
const seatsWithinRadius = thisSeat
.allSeatsWithinRadius(radius)
.filter((seat) => !seat.equalsOneOf(otherSeats));
const randomIndex = Math.floor(Math.random() * seatsWithinRadius.length);
return seatsWithinRadius[randomIndex];
}
let allSeats = this.getAllSeats().filter((seat) => !seat.equalsOneOf(otherSeats));
const randomIndex = Math.floor(Math.random() * allSeats.length);
return allSeats[randomIndex];
}
getAllSeats() {
let seats = [];
for (const floor of this.floors.values()) {
seats = seats.concat(floor.seats);
}
return seats;
}
distanceBetweenSeats(seat1, seat2) {
return Math.sqrt(Math.pow(seat1.x - seat2.x, 2) + Math.pow(seat1.y - seat2.y, 2));
}
*seatsIterator() {
for (const floor of this.floors.values()) {
for (const seat of floor.seats) {
yield seat;
}
}
}
addFloor(floor) {
this.floors.set(floor.id, floor);
if (floor.height > this.height) {
this.height = floor.height;
}
if (floor.width > this.width) {
this.width = floor.width;
}
}
}
// DistanceCache.ts
class DistanceCache {
static instance;
cache;
constructor() {
this.cache = new Map();
}
// Singleton instance accessor
static getInstance() {
if (!DistanceCache.instance) {
DistanceCache.instance = new DistanceCache();
}
return DistanceCache.instance;
}
// Generate a unique key for a pair of seats
generateKey(seatId1, seatId2) {
// Ensure consistent ordering to avoid duplicate keys
return seatId1 < seatId2 ? `${seatId1}_${seatId2}` : `${seatId2}_${seatId1}`;
}
// Get distance from cache
getDistance(seatId1, seatId2) {
const key = this.generateKey(seatId1, seatId2);
return this.cache.get(key);
}
// Set distance in cache
setDistance(seatId1, seatId2, distance) {
const key = this.generateKey(seatId1, seatId2);
this.cache.set(key, distance);
}
// Optional: Method to clear the cache
clearCache() {
this.cache.clear();
}
}
// Cache to store distances between points
const distanceCache = new Map();
class Point {
x;
y;
constructor(x, y) {
this.x = x;
this.y = y;
}
// Generates a unique key for a pair of points
static getCacheKey(point1, point2) {
const key1 = `${point1.x},${point1.y}`;
const key2 = `${point2.x},${point2.y}`;
return [key1, key2].sort().join('-');
}
distanceTo(point) {
const cacheKey = Point.getCacheKey(this, point);
// Check if the distance is already cached
if (distanceCache.has(cacheKey)) {
return distanceCache.get(cacheKey);
}
// Calculate the distance if not cached
const distance = Math.sqrt(Math.pow(this.x - point.x, 2) + Math.pow(this.y - point.y, 2));
// Store the calculated distance in the cache
distanceCache.set(cacheKey, distance);
return distance;
}
}
class Seat extends Point {
id;
floor;
constructor(floor, id, x, y) {
super(x, y); // Initialize x and y from Point
this.floor = floor;
this.id = id;
}
equals(other) {
return this.id === other.id;
}
equalsOneOf(others) {
for (let other of others) {
if (this.equals(other)) {
return true;
}
}
return false;
}
distanceToPoint(point) {
return super.distanceTo(point);
}
findNearestAvailable() {
let allSeats = this.floor.building.getAllSeats();
let minDistance = Infinity;
let nearestSeat = null;
for (let seat of allSeats) {
if (seat.equals(this))
continue; // Skip the current seat
if (this.floor.building.reservations.getBySeatId(seat.id))
continue; // Skip the reserved seats
let distance = this.distanceTo(seat);
if (distance < minDistance) {
minDistance = distance;
nearestSeat = seat;
}
}
return nearestSeat;
}
nearestDistanceTo(seats) {
let minDistance = Infinity;
for (let seat of seats) {
let distance = this.distanceTo(seat);
if (distance < minDistance) {
minDistance = distance;
}
}
return minDistance;
}
allSeatsWithinRadius(radius) {
const seats = this.floor.building.getAllSeats();
const seatsWithinRadius = [];
for (let seat of seats) {
if (this.equals(seat))
continue; // Skip the current seat
const distance = this.distanceTo(seat);
if (distance <= radius) {
seatsWithinRadius.push(seat);
}
}
return seatsWithinRadius;
}
getSeatsWithinRadiusButFurtherAwayFrom(otherSeats, currentRadius) {
let seatsWithinRadius = this.allSeatsWithinRadius(currentRadius);
let minDistance = this.nearestDistanceTo(otherSeats);
let newCandidate = null;
for (let seat of seatsWithinRadius) {
let nearestDistanceToOthers = seat.nearestDistanceTo(otherSeats);
if (nearestDistanceToOthers > minDistance) {
minDistance = nearestDistanceToOthers;
newCandidate = seat;
}
}
return newCandidate;
}
distanceTo(seat) {
const cache = DistanceCache.getInstance();
const cachedDistance = cache.getDistance(this.id, seat.id);
if (cachedDistance !== undefined) {
return cachedDistance;
}
// Calculate distance as per existing logic
let distance;
// Flip the question to minimize cache storage
if (this.floor.id > seat.floor.id) {
distance = seat.distanceTo(this);
}
else if (seat.floor.equals(this.floor)) {
distance = super.distanceTo(seat);
}
else {
let exits = this.floor.getExitsTo(seat.floor);
let minDistance = Infinity;
console.log('Considering exits', exits);
for (let exit of exits) {
// exit structure: [number, Point, OfficeFloor, Point]
let linkDistance = exit[0];
let point1 = exit[1];
let point2 = exit[3];
let calculatedDistance = this.distanceToPoint(point1) + seat.distanceToPoint(point2) + linkDistance;
if (calculatedDistance < minDistance) {
minDistance = calculatedDistance;
}
}
distance = minDistance;
}
// Store the calculated distance in the cache
cache.setDistance(this.id, seat.id, distance);
return distance;
}
teamLabel() {
const res = this.floor.building.reservations;
if (res) {
const tres = res.getBySeatId(this.id);
if (!tres)
return 'Flex';
return tres.teams.length > 0 ? tres.teams[0].id : 'Flex';
}
return 'Flex';
}
personLabel() {
const res = this.floor.building.reservations;
if (res) {
const tres = res.getBySeatId(this.id);
if (!tres)
return '';
return tres.people.length > 0 ? tres.people[0].getShortLabel() : '';
}
return '';
}
isInRange(ranges) {
for (let i = 0; i < ranges.length; i++) {
const [start, end] = ranges[i];
if (this.id >= start && this.id <= end) {
return true;
}
}
return false;
}
}
// export type SeatType = {
// x: number;
// y: number;
// id: number;
// };
class OfficeFloor {
building;
label = '';
lines = [];
seats = [];
id;
nextId;
width;
height;
exits = [];
constructor(building, label = '', startId, width = 20, height = 5) {
this.building = building;
this.label = label;
this.id = startId;
this.nextId = startId;
this.width = width;
this.height = height;
building.addFloor(this);
}
equals(floor) {
return this.id === floor.id;
}
addExit(fromId, from, toFloor, to) {
this.exits.push([fromId, from, toFloor, to]);
// console.log('Exists', this.exits);
return this;
}
getExitsTo(floor) {
return this.exits.filter((e) => e[2].equals(floor));
}
getLabel(includeBuilding = false) {
return includeBuilding ? `${this.building.label} - ${this.label}` : this.label;
}
getId() {
return this.nextId++;
}
addLine(line) {
let maxY = line.coord.reduce((max, coord) => (coord[1] > max ? coord[1] : max), 0);
// console.log('Max Y', maxY);
if (maxY >= this.height) {
this.height = maxY + 1;
}
this.lines.push(line);
// console.log('Line added');
return this;
}
addBox(x1, y1, x2, y2) {
return this.addLine({
type: 'inner',
coord: [
[x1, y1],
[x2, y1],
[x2, y2],
[x1, y2]
]
});
}
getSeat(id) {
return this.seats.find((seat) => seat.id === id) || null;
}
addSeats(c1, c2) {
for (let x = c1[0]; x <= c2[0]; x++) {
for (let y = c1[1]; y <= c2[1]; y++) {
// this.seats.push({ x, y, id: this.getId() });
this.seats.push(new Seat(this, this.getId(), x, y));
if (x >= this.width) {
this.width = x + 1;
}
if (y >= this.height) {
this.height = y + 1;
}
}
}
return this;
}
}
class Person {
id;
name = '';
teams = [];
p = 0;
_shortLabel; // private property to store the cached short label
constructor(id, name, p) {
this.id = id;
this.name = name || id;
this.p = p || 0;
}
randomPresence() {
return Math.random() < this.p / 100;
}
getAllocation(overhead) {
return Math.max(1, this.p * overhead);
}
getShortLabel() {
// If the short label has already been calculated, return the cached value
if (this._shortLabel) {
return this._shortLabel;
}
const nameParts = this.name.split(' ');
let shortLabel = '';
// Handle first name (first 2 characters)
if (nameParts.length > 0) {
shortLabel += nameParts[0].slice(0, 2);
}
// Handle middle names (1 character each)
// for (let i = 1; i < nameParts.length - 1; i++) {
// shortLabel += nameParts[i].slice(0, 1);
// }
// Handle last name (first 2 characters)
if (nameParts.length > 1) {
shortLabel += nameParts[nameParts.length - 1].slice(0, 2);
}
// Cache the calculated short label
this._shortLabel = shortLabel;
return shortLabel;
}
setTeams(teams) {
this.teams = teams;
}
getOneTeam() {
if (this.teams.length === 0) {
return null;
}
return this.teams[0];
}
}
/** Detect free variable `global` from Node.js. */
var freeGlobal = typeof global == 'object' && global && global.Object === Object && global;
/** Detect free variable `self`. */
var freeSelf = typeof self == 'object' && self && self.Object === Object && self;
/** Used as a reference to the global object. */
var root$1 = freeGlobal || freeSelf || Function('return this')();
/** Built-in value references. */
var Symbol$1 = root$1.Symbol;
/** Used for built-in method references. */
var objectProto$5 = Object.prototype;
/** Used to check objects for own properties. */
var hasOwnProperty$3 = objectProto$5.hasOwnProperty;
/**
* Used to resolve the
* [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
* of values.
*/
var nativeObjectToString$1 = objectProto$5.toString;
/** Built-in value references. */
var symToStringTag$1 = Symbol$1 ? Symbol$1.toStringTag : undefined;
/**
* A specialized version of `baseGetTag` which ignores `Symbol.toStringTag` values.
*
* @private
* @param {*} value The value to query.
* @returns {string} Returns the raw `toStringTag`.
*/
function getRawTag(value) {
var isOwn = hasOwnProperty$3.call(value, symToStringTag$1),
tag = value[symToStringTag$1];
try {
value[symToStringTag$1] = undefined;
var unmasked = true;
} catch (e) {}
var result = nativeObjectToString$1.call(value);
if (unmasked) {
if (isOwn) {
value[symToStringTag$1] = tag;
} else {
delete value[symToStringTag$1];
}
}
return result;
}
/** Used for built-in method references. */
var objectProto$4 = Object.prototype;
/**
* Used to resolve the
* [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
* of values.
*/
var nativeObjectToString = objectProto$4.toString;
/**
* Converts `value` to a string using `Object.prototype.toString`.
*
* @private
* @param {*} value The value to convert.
* @returns {string} Returns the converted string.
*/
function objectToString(value) {
return nativeObjectToString.call(value);
}
/** `Object#toString` result references. */
var nullTag = '[object Null]',
undefinedTag = '[object Undefined]';
/** Built-in value references. */
var symToStringTag = Symbol$1 ? Symbol$1.toStringTag : undefined;
/**
* The base implementation of `getTag` without fallbacks for buggy environments.
*
* @private
* @param {*} value The value to query.
* @returns {string} Returns the `toStringTag`.
*/
function baseGetTag(value) {
if (value == null) {
return value === undefined ? undefinedTag : nullTag;
}
return (symToStringTag && symToStringTag in Object(value))
? getRawTag(value)
: objectToString(value);
}
/**
* Checks if `value` is object-like. A value is object-like if it's not `null`
* and has a `typeof` result of "object".
*
* @static
* @memberOf _
* @since 4.0.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is object-like, else `false`.
* @example
*
* _.isObjectLike({});
* // => true
*
* _.isObjectLike([1, 2, 3]);
* // => true
*
* _.isObjectLike(_.noop);
* // => false
*
* _.isObjectLike(null);
* // => false
*/
function isObjectLike(value) {
return value != null && typeof value == 'object';
}
/**
* A specialized version of `_.map` for arrays without support for iteratee
* shorthands.
*
* @private
* @param {Array} [array] The array to iterate over.
* @param {Function} iteratee The function invoked per iteration.
* @returns {Array} Returns the new mapped array.
*/
function arrayMap(array, iteratee) {
var index = -1,
length = array == null ? 0 : array.length,
result = Array(length);
while (++index < length) {
result[index] = iteratee(array[index], index, array);
}
return result;
}
/**
* Checks if `value` is classified as an `Array` object.
*
* @static
* @memberOf _
* @since 0.1.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is an array, else `false`.
* @example
*
* _.isArray([1, 2, 3]);
* // => true
*
* _.isArray(document.body.children);
* // => false
*
* _.isArray('abc');
* // => false
*
* _.isArray(_.noop);
* // => false
*/
var isArray = Array.isArray;
/**
* Checks if `value` is the
* [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types)
* of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
*
* @static
* @memberOf _
* @since 0.1.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is an object, else `false`.
* @example
*
* _.isObject({});
* // => true
*
* _.isObject([1, 2, 3]);
* // => true
*
* _.isObject(_.noop);
* // => true
*
* _.isObject(null);
* // => false
*/
function isObject(value) {
var type = typeof value;
return value != null && (type == 'object' || type == 'function');
}
/** `Object#toString` result references. */
var asyncTag = '[object AsyncFunction]',
funcTag$1 = '[object Function]',
genTag = '[object GeneratorFunction]',
proxyTag = '[object Proxy]';
/**
* Checks if `value` is classified as a `Function` object.
*
* @static
* @memberOf _
* @since 0.1.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a function, else `false`.
* @example
*
* _.isFunction(_);
* // => true
*
* _.isFunction(/abc/);
* // => false
*/
function isFunction(value) {
if (!isObject(value)) {
return false;
}
// The use of `Object#toString` avoids issues with the `typeof` operator
// in Safari 9 which returns 'object' for typed arrays and other constructors.
var tag = baseGetTag(value);
return tag == funcTag$1 || tag == genTag || tag == asyncTag || tag == proxyTag;
}
/**
* Copies the values of `source` to `array`.
*
* @private
* @param {Array} source The array to copy values from.
* @param {Array} [array=[]] The array to copy values to.
* @returns {Array} Returns `array`.
*/
function copyArray(source, array) {
var index = -1,
length = source.length;
array || (array = Array(length));
while (++index < length) {
array[index] = source[index];
}
return array;
}
/** Used as references for various `Number` constants. */
var MAX_SAFE_INTEGER$1 = 9007199254740991;
/** Used to detect unsigned integer values. */
var reIsUint = /^(?:0|[1-9]\d*)$/;
/**
* Checks if `value` is a valid array-like index.
*
* @private
* @param {*} value The value to check.
* @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index.
* @returns {boolean} Returns `true` if `value` is a valid index, else `false`.
*/
function isIndex(value, length) {
var type = typeof value;
length = length == null ? MAX_SAFE_INTEGER$1 : length;
return !!length &&
(type == 'number' ||
(type != 'symbol' && reIsUint.test(value))) &&
(value > -1 && value % 1 == 0 && value < length);
}
/** Used as references for various `Number` constants. */
var MAX_SAFE_INTEGER = 9007199254740991;
/**
* Checks if `value` is a valid array-like length.
*
* **Note:** This method is loosely based on
* [`ToLength`](http://ecma-international.org/ecma-262/7.0/#sec-tolength).
*
* @static
* @memberOf _
* @since 4.0.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a valid length, else `false`.
* @example
*
* _.isLength(3);
* // => true
*
* _.isLength(Number.MIN_VALUE);
* // => false
*
* _.isLength(Infinity);
* // => false
*
* _.isLength('3');
* // => false
*/
function isLength(value) {
return typeof value == 'number' &&
value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER;
}
/**
* Checks if `value` is array-like. A value is considered array-like if it's
* not a function and has a `value.length` that's an integer greater than or
* equal to `0` and less than or equal to `Number.MAX_SAFE_INTEGER`.
*
* @static
* @memberOf _
* @since 4.0.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is array-like, else `false`.
* @example
*
* _.isArrayLike([1, 2, 3]);
* // => true
*
* _.isArrayLike(document.body.children);
* // => true
*
* _.isArrayLike('abc');
* // => true
*
* _.isArrayLike(_.noop);
* // => false
*/
function isArrayLike(value) {
return value != null && isLength(value.length) && !isFunction(value);
}
/** Used for built-in method references. */
var objectProto$3 = Object.prototype;
/**
* Checks if `value` is likely a prototype object.
*
* @private
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a prototype, else `false`.
*/
function isPrototype(value) {
var Ctor = value && value.constructor,
proto = (typeof Ctor == 'function' && Ctor.prototype) || objectProto$3;
return value === proto;
}
/**
* The base implementation of `_.times` without support for iteratee shorthands
* or max array length checks.
*
* @private
* @param {number} n The number of times to invoke `iteratee`.
* @param {Function} iteratee The function invoked per iteration.
* @returns {Array} Returns the array of results.
*/
function baseTimes(n, iteratee) {
var index = -1,
result = Array(n);
while (++index < n) {
result[index] = iteratee(index);
}
return result;
}
/** `Object#toString` result references. */
var argsTag$1 = '[object Arguments]';
/**
* The base implementation of `_.isArguments`.
*
* @private
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is an `arguments` object,
*/
function baseIsArguments(value) {
return isObjectLike(value) && baseGetTag(value) == argsTag$1;
}
/** Used for built-in method references. */
var objectProto$2 = Object.prototype;
/** Used to check objects for own properties. */
var hasOwnProperty$2 = objectProto$2.hasOwnProperty;
/** Built-in value references. */
var propertyIsEnumerable = objectProto$2.propertyIsEnumerable;
/**
* Checks if `value` is likely an `arguments` object.
*
* @static
* @memberOf _
* @since 0.1.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is an `arguments` object,
* else `false`.
* @example
*
* _.isArguments(function() { return arguments; }());
* // => true
*
* _.isArguments([1, 2, 3]);
* // => false
*/
var isArguments = baseIsArguments(function() { return arguments; }()) ? baseIsArguments : function(value) {
return isObjectLike(value) && hasOwnProperty$2.call(value, 'callee') &&
!propertyIsEnumerable.call(value, 'callee');
};
/**
* This method returns `false`.
*
* @static
* @memberOf _
* @since 4.13.0
* @category Util
* @returns {boolean} Returns `false`.
* @example
*
* _.times(2, _.stubFalse);
* // => [false, false]
*/
function stubFalse() {
return false;
}
/** Detect free variable `exports`. */
var freeExports$1 = typeof exports == 'object' && exports && !exports.nodeType && exports;
/** Detect free variable `module`. */
var freeModule$1 = freeExports$1 && typeof module == 'object' && module && !module.nodeType && module;
/** Detect the popular CommonJS extension `module.exports`. */
var moduleExports$1 = freeModule$1 && freeModule$1.exports === freeExports$1;
/** Built-in value references. */
var Buffer = moduleExports$1 ? root$1.Buffer : undefined;
/* Built-in method references for those with the same name as other `lodash` methods. */
var nativeIsBuffer = Buffer ? Buffer.isBuffer : undefined;
/**
* Checks if `value` is a buffer.
*
* @static
* @memberOf _
* @since 4.3.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a buffer, else `false`.
* @example
*
* _.isBuffer(new Buffer(2));
* // => true
*
* _.isBuffer(new Uint8Array(2));
* // => false
*/
var isBuffer = nativeIsBuffer || stubFalse;
/** `Object#toString` result references. */
var argsTag = '[object Arguments]',
arrayTag = '[object Array]',
boolTag = '[object Boolean]',
dateTag = '[object Date]',
errorTag = '[object Error]',
funcTag = '[object Function]',
mapTag = '[object Map]',
numberTag = '[object Number]',
objectTag = '[object Object]',
regexpTag = '[object RegExp]',
setTag = '[object Set]',
stringTag = '[object String]',
weakMapTag = '[object WeakMap]';
var arrayBufferTag = '[object ArrayBuffer]',
dataViewTag = '[object DataView]',
float32Tag = '[object Float32Array]',
float64Tag = '[object Float64Array]',
int8Tag = '[object Int8Array]',
int16Tag = '[object Int16Array]',
int32Tag = '[object Int32Array]',
uint8Tag = '[object Uint8Array]',
uint8ClampedTag = '[object Uint8ClampedArray]',
uint16Tag = '[object Uint16Array]',
uint32Tag = '[object Uint32Array]';
/** Used to identify `toStringTag` values of typed arrays. */
var typedArrayTags = {};
typedArrayTags[float32Tag] = typedArrayTags[float64Tag] =
typedArrayTags[int8Tag] = typedArrayTags[int16Tag] =
typedArrayTags[int32Tag] = typedArrayTags[uint8Tag] =
typedArrayTags[uint8ClampedTag] = typedArrayTags[uint16Tag] =
typedArrayTags[uint32Tag] = true;
typedArrayTags[argsTag] = typedArrayTags[arrayTag] =
typedArrayTags[arrayBufferTag] = typedArrayTags[boolTag] =
typedArrayTags[dataViewTag] = typedArrayTags[dateTag] =
typedArrayTags[errorTag] = typedArrayTags[funcTag] =
typedArrayTags[mapTag] = typedArrayTags[numberTag] =
typedArrayTags[objectTag] = typedArrayTags[regexpTag] =
typedArrayTags[setTag] = typedArrayTags[stringTag] =
typedArrayTags[weakMapTag] = false;
/**
* The base implementation of `_.isTypedArray` without Node.js optimizations.
*
* @private
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a typed array, else `false`.
*/
function baseIsTypedArray(value) {
return isObjectLike(value) &&
isLength(value.length) && !!typedArrayTags[baseGetTag(value)];
}
/**
* The base implementation of `_.unary` without support for storing metadata.
*
* @private
* @param {Function} func The function to cap arguments for.
* @returns {Function} Returns the new capped function.
*/
function baseUnary(func) {
return function(value) {
return func(value);
};
}
/** Detect free variable `exports`. */
var freeExports = typeof exports == 'object' && exports && !exports.nodeType && exports;
/** Detect free variable `module`. */
var freeModule = freeExports && typeof module == 'object' && module && !module.nodeType && module;
/** Detect the popular CommonJS extension `module.exports`. */
var moduleExports = freeModule && freeModule.exports === freeExports;
/** Detect free variable `process` from Node.js. */
var freeProcess = moduleExports && freeGlobal.process;
/** Used to access faster Node.js helpers. */
var nodeUtil = (function() {
try {
// Use `util.types` for Node.js 10+.
var types = freeModule && freeModule.require && freeModule.require('util').types;
if (types) {
return types;
}
// Legacy `process.binding('util')` for Node.js < 10.
return freeProcess && freeProcess.binding && freeProcess.binding('util');
} catch (e) {}
}());
/* Node.js helper references. */
var nodeIsTypedArray = nodeUtil && nodeUtil.isTypedArray;
/**
* Checks if `value` is classified as a typed array.
*
* @static
* @memberOf _
* @since 3.0.0
* @category Lang
* @param {*} value The value to check.
* @returns {boolean} Returns `true` if `value` is a typed array, else `false`.
* @example
*
* _.isTypedArray(new Uint8Array);
* // => true
*
* _.isTypedArray([]);
* // => false
*/
var isTypedArray = nodeIsTypedArray ? baseUnary(nodeIsTypedArray) : baseIsTypedArray;
/** Used for built-in method references. */
var objectProto$1 = Object.prototype;
/** Used to check objects for own properties. */
var hasOwnProperty$1 = objectProto$1.hasOwnProperty;
/**
* Creates an array of the enumerable property names of the array-like `value`.
*
* @private
* @param {*} value The value to query.
* @param {boolean} inherited Specify returning inherited property names.
* @returns {Array} Returns the array of property names.
*/
function arrayLikeKeys(value, inherited) {
var isArr = isArray(value),
isArg = !isArr && isArguments(value),
isBuff = !isArr && !isArg && isBuffer(value),
isType = !isArr && !isArg && !isBuff && isTypedArray(value),
skipIndexes = isArr || isArg || isBuff || isType,
result = skipIndexes ? baseTimes(value.length, String) : [],
length = result.length;
for (var key in value) {
if ((hasOwnProperty$1.call(value, key)) &&
!(skipIndexes && (
// Safari 9 has enumerable `arguments.length` in strict mode.
key == 'length' ||
// Node.js 0.10 has enumerable non-index properties on buffers.
(isBuff && (key == 'offset' || key == 'parent')) ||
// PhantomJS 2 has enumerable non-index properties on typed arrays.
(isType && (key == 'buffer' || key == 'byteLength' || key == 'byteOffset')) ||
// Skip index properties.
isIndex(key, length)
))) {
result.push(key);
}
}
return result;
}
/**
* Creates a unary function that invokes `func` with its argument transformed.
*
* @private
* @param {Function} func The function to wrap.
* @param {Function} transform The argument transform.
* @returns {Function} Returns the new function.
*/
function overArg(func, transform) {
return function(arg) {
return func(transform(arg));
};
}
/* Built-in method references for those with the same name as other `lodash` methods. */
var nativeKeys = overArg(Object.keys, Object);
/** Used for built-in method references. */
var objectProto = Object.prototype;
/** Used to check objects for own properties. */
var hasOwnProperty = objectProto.hasOwnProperty;
/**
* The base implementation of `_.keys` which doesn't treat sparse arrays as dense.
*
* @private
* @param {Object} object The object to query.
* @returns {Array} Returns the array of property names.
*/
function baseKeys(object) {
if (!isPrototype(object)) {
return nativeKeys(object);
}
var result = [];
for (var key in Object(object)) {
if (hasOwnProperty.call(object, key) && key != 'constructor') {
result.push(key);
}
}
return result;
}
/**
* Creates an array of the own enumerable property names of `object`.
*
* **Note:** Non-object values are coerced to objects. See the
* [ES spec](http://ecma-international.org/ecma-262/7.0/#sec-object.keys)
* for more details.
*
* @static
* @since 0.1.0
* @memberOf _
* @category Object
* @param {Object} object The object to query.
* @returns {Array} Returns the array of property names.
* @example
*
* function Foo() {
* this.a = 1;
* this.b = 2;
* }
*
* Foo.prototype.c = 3;
*
* _.keys(new Foo);
* // => ['a', 'b'] (iteration order is not guaranteed)
*
* _.keys('hi');
* // => ['0', '1']
*/
function keys(object) {
return isArrayLike(object) ? arrayLikeKeys(object) : baseKeys(object);
}
/**
* The base implementation of `_.values` and `_.valuesIn` which creates an
* array of `object` property values corresponding to the property names
* of `props`.
*
* @private
* @param {Object} object The object to query.
* @param {Array} props The property names to get values for.
* @returns {Object} Returns the array of property values.
*/
function baseValues(object, props) {
return arrayMap(props, function(key) {
return object[key];
});
}
/**
* Creates an array of the own enumerable string keyed property values of `object`.
*
* **Note:** Non-object values are coerced to objects.
*
* @static
* @since 0.1.0
* @memberOf _
* @category Object
* @param {Object} object The object to query.
* @returns {Array} Returns the array of property values.
* @example
*
* function Foo() {
* this.a = 1;
* this.b = 2;
* }
*
* Foo.prototype.c = 3;
*
* _.values(new Foo);
* // => [1, 2] (iteration order is not guaranteed)
*
* _.values('hi');
* // => ['h', 'i']
*/
function values(object) {
return object == null ? [] : baseValues(object, keys(object));
}
/* Built-in method references for those with the same name as other `lodash` methods. */
var nativeFloor = Math.floor,
nativeRandom = Math.random;
/**
* The base implementation of `_.random` without support for returning
* floating-point numbers.
*
* @private
* @param {number} lower The lower bound.
* @param {number} upper The upper bound.
* @returns {number} Returns the random number.
*/
function baseRandom(lower, upper) {
return lower + nativeFloor(nativeRandom() * (upper - lower + 1));
}
/**
* A specialized version of `_.shuffle` which mutates and sets the size of `array`.
*
* @private
* @param {Array} array The array to shuffle.
* @param {number} [size=array.length] The size of `array`.
* @returns {Array} Returns `array`.
*/
function shuffleSelf(array, size) {
var index = -1,
length = array.length,
lastIndex = length - 1;
size = size === undefined ? length : size;
while (++index < size) {
var rand = baseRandom(index, lastIndex),
value = array[rand];
array[rand] = array[index];
array[index] = value;
}
array.length = size;
return array;
}
/**
* A specialized version of `_.shuffle` for arrays.
*
* @private
* @param {Array} array The array to shuffle.
* @returns {Array} Returns the new shuffled array.
*/
function arrayShuffle(array) {
return shuffleSelf(copyArray(array));
}
/**
* The base implementation of `_.shuffle`.
*
* @private
* @param {Array|Object} collection The collection to shuffle.
* @returns {Array} Returns the new shuffled array.
*/
function baseShuffle(collection) {
return shuffleSelf(values(collection));
}
/**
* Creates an array of shuffled values, using a version of the
* [Fisher-Yates shuffle](https://en.wikipedia.org/wiki/Fisher-Yates_shuffle).
*
* @static
* @memberOf _
* @since 0.1.0
* @category Collection
* @param {Array|Object} collection The collection to shuffle.
* @returns {Array} Returns the new shuffled array.
* @example
*
* _.shuffle([1, 2, 3, 4]);
* // => [4, 1, 3, 2]
*/
function shuffle(collection) {
var func = isArray(collection) ? arrayShuffle : baseShuffle;
return func(collection);
}
/**
* Represents a collection of people that can be managed and queried.
* Provides functionality for adding, retrieving, and analyzing people and their team associations.
* Implements Iterator pattern to allow iteration over the collection.
*/
class People {
map = new Map();
constructor() { }
/**
* Adds a person to the collection.
* @param person - The person object to add to the collection.
*/
add(person) {
this.map.set(person.id, person);
}
/**
* Checks if the collection has a person with the specified ID.
* @param id - The ID of the person to check for.
* @returns True if a person with the specified ID exists, false otherwise.
*/
has(id) {
return this.map.has(id);
}
/**
* Retrieves a person from the collection by their ID.
* @param id - The ID of the person to retrieve.
* @returns The person object if found, undefined otherwise.
*/
get(id) {
return this.map.get(id);
}
getRandom() {
const values = Array.from(this.map.values());
let chosen = values.filter((person) => person.randomPresence());
return shuffle(chosen);
}
getTeamStats() {
const teamMap = new Map();
for (const person of this.map.values()) {
let teamKey = '_no_team';
if (person.teams.length > 0) {
teamKey = person.teams[0].id;
}
if (!teamMap.has(teamKey)) {
teamMap.set(teamKey, 0);
}
teamMap.set(teamKey, teamMap.get(teamKey) + 1);
}
return teamMap;
}
getAllocations() {
const teamMap = new Map();
for (const person of this.map.values()) {
if (person.teams.length > 0) {
let teamKey = person.teams[0].id;
if (!teamMap.has(teamKey)) {
teamMap.set(teamKey, 0);
}
teamMap.set(teamKey, teamMap.get(teamKey) + person.getAllocation(1.2));
}
}
for (const [key, value] of teamMap.entries()) {
teamMap.set(key, Math.round((value / 100) * 0.7));
}
return teamMap;
}
[Symbol.iterator]() {
let index = 0;
const values = Array.from(this.map.values());
return {
next: () => {
if (index < values.length) {
return { value: values[index++], done: false };
}
else {
return { done: true };
}
}
};
}
}
class Team {
id;
name = '';
constructor(id, name) {
this.id = id;
this.name = name || id;
}
}
// Reservations
class Teams {
// reservations: Reservation[] = [];
map = new Map();
constructor() { }
add(team) {
this.map.set(team.id, team);
}
has(id) {
return this.map.has(id);
}
get(id) {
return this.map.get(id);
}
size() {
return this.map.size;
}
[Symbol.iterator]() {
let index = 0;
const values = Array.from(this.map.values());
return {
next: () => {
if (index < values.length) {
return { value: values[index++], done: false };
}
else {
return { done: true };
}
}
};
}
}
class Reservation {
seatId;
people = [];
teams = [];
// division: Division | null = null;
constructor(seatId) {
this.seatId = seatId;
}
}
class SeatingStats {
items = [];
teams = new Map();
seats = new Map();
constructor() { }
add(fitness) {
this.items.push(fitness);
}
addTeam(teamId, fitness) {
if (!this.teams.has(teamId)) {
this.teams.set(teamId, []);
}
this.teams.get(teamId).push(fitness);
}
addSeat(seatId, fitness) {
this.seats.set(seatId, fitness);
}
getSeat(seatId) {
return this.seats.get(seatId);
}
getAverage(teamId) {
if (teamId) {
return this.teams.get(teamId).reduce((a, b) => a + b, 0) / this.teams.get(teamId).length;
}
return this.items.reduce((a, b) => a + b, 0) / this.items.length;
}
debug() {
console.log(' ---- Stats (' + this.items.length + ' users) ----');
console.log('Overall fitness ', this.getAverage());
for (let teamId of this.teams.keys()) {
console.log('Team ', teamId, ' fitness ', this.getAverage(teamId));
}
}
}
// import type { Team } from './Team';
class SeatedPerson {
person;
seatReservation;
constructor(person, seatReservation) {
this.person = person;
this.seatReservation = seatReservation;
}
}
class SeatingSimulation {
building;
people;
remainingSeats = new Map();
seatedPeople = new Map();
unSeatedPeople = [];
detailedStats = false;
constructor(building, people, detailedStats = false) {
this.building = building;
for (let seat of this.building.seatsIterator()) {
this.remainingSeats.set(seat.id, seat);
}
this.people = people;
this.detailedStats = detailedStats;
}
seatAll() {
for (const person of this.people) {
this.seatPerson(person);
}
console.log('Remaining seats', this.remainingSeats);
console.log('Seated people', this.seatedPeople);
console.log('Unseated people', this.unSeatedPeople);
}
getSeatedByTeams() {
const teamMap = new Map();
for (const seatedPersonEntry of this.seatedPeople.values()) {
const team = seatedPersonEntry.person.getOneTeam();
if (team) {
if (!teamMap.has(team)) {
teamMap.set(team, []);
}
teamMap.get(team).push(seatedPersonEntry);
}
}
return teamMap;
}
calculateFitness() {
// Helper to calculate distance between two seats
// const calculateDistance = (seat1: Seat, seat2: Seat): number => {
// return seat1.distanceTo(seat2); // Assuming Seat has a distanceTo method
// };
let teamMap = this.getSeatedByTeams();
console.log('Team Map:', teamMap);
let stats = new SeatingStats();
console.error('Seated people', this.seatedPeople);
for (const seatedPersonEntry of this.seatedPeople.values()) {
const seatedPerson = seatedPersonEntry.person;
const currentSeat = this.building.getSeat(seatedPersonEntry.seatReservation.seatId);
// let averageDistance = 0;
let team = seatedPersonEntry.person.getOneTeam();
console.log("Seated person's team", seatedPerson, team);
if (team) {
let teamMembers = teamMap.get(team);
let otherTeamMembers = teamMembers.filter((entry) => entry.person.id !== seatedPerson.id);
if (otherTeamMembers.length > 0) {
let otherTeamMembersSeats = otherTeamMembers.map((entry) => this.building.getSeat(entry.seatReservation.seatId));
// Calculate the average distance
let totalDistance = otherTeamMembersSeats.reduce((sum, teamMemberSeat) => sum + currentSeat.distanceTo(teamMemberSeat), 0);
let averageDistance = totalDistance / otherTeamMembersSeats.length; // Compute average
stats.add(averageDistance);
if (this.detailedStats) {
stats.addTeam(team.id, averageDistance);
stats.addSeat(currentSeat.id, averageDistance);
}
console.log('Average distance ', seatedPersonEntry.person.getShortLabel(), averageDistance, ' of other team members', otherTeamMembers.length, seatedPersonEntry, otherTeamMembers);
}
}
// totalFitness += averageDistance;
// seatedPersonCount++;
}
stats.debug();
// Calculate overall fitness as the average of all seated persons' average distances
// const averageFitness = seatedPersonCount > 0 ? totalFitness / seatedPersonCount : 0;
// console.log('Average Fitness:', averageFitness);
return stats;
}
*seat() {
for (const person of this.people) {
this.seatPerson(person);
yield;
}
console.log('Remaining seats', this.remainingSeats);
console.log('Seated people', this.seatedPeople);
console.log('Unseated people', this.unSeatedPeople);
}
seatPerson(person) {
// console.log('About to seat', person);
// Find seat allocated for person
// console.log(' ------ ', person);
let seatedForMe = this.filterAvailable(this.building.reservations.getByPerson(person));
if (seatedForMe.length > 0) {
// console.log('Seating person', person, seatedForMe);
this.remainingSeats.delete(seatedForMe[0].seatId);
this.seatedPeople.set(seatedForMe[0].seatId, new SeatedPerson(person, seatedForMe[0]));
return true;
}
// Find all seats for the team that is available.
// Find all seats for the team that is taken
let otherTeamReservations = [];
if (person.teams.length > 0) {
let allTeamSeats = this.building.reservations.getByTeam(person.teams[0]);
let availableTeamReservations = this.filterAvailable(allTeamSeats);
otherTeamReservations = this.filterUnavailable(allTeamSeats);
// let otherTeamReservations = unavailableTeamReservations.map((res: Reservation) => res.seatId);
if (availableTeamReservations.length > 0) {
console.log('About to iterate through availableTeamSeats', availableTeamReservations);
console.log('About to iterate through unavailableTeamReservations', otherTeamReservations);
// Iterate through all available seats allocated for the team, and take the nearest one..
let nearestReservation = this.nearestDistanceToOneOf(otherTeamReservations, availableTeamReservations);
console.log('NEarest reservation was', nearestReservation);
if (nearestReservation) {
console.log('Seat', person, ' at ', nearestReservation);
this.remainingSeats.delete(nearestReservation.seatId);
this.seatedPeople.set(nearestReservation.seatId, new SeatedPerson(person, nearestReservation));
return true;
}
}
}
// Find all remaining flex seats..
let availableFlexReservations = this.filterAvailable(this.building.reservations.getAllFlex());
console.log('Looking through flex reservations', availableFlexReservations);
if (availableFlexReservations.length > 0) {
if (otherTeamReservations.length > 0) {
let nearestReservation = this.nearestDistanceToOneOf(otherTeamReservations, availableFlexReservations);
if (nearestReservation) {
this.remainingSeats.delete(nearestReservation.seatId);
this.seatedPeople.set(nearestReservation.seatId, new SeatedPerson(person, nearestReservation));
return true;
}
}
else {
let chosenReservation = availableFlexReservations[Math.floor(Math.random() * availableFlexReservations.length)];
this.remainingSeats.delete(chosenReservation.seatId);
this.seatedPeople.set(chosenReservation.seatId, new SeatedPerson(person, chosenReservation));
return true;
}
}
this.unSeatedPeople.push(person);
return false;
}
filterAvailable(reservations) {
return reservations.filter((res) => this.remainingSeats.has(res.seatId));
}
filterUnavailable(reservations) {
return reservations.filter((res) => !this.remainingSeats.has(res.seatId));
}
nearestDistanceToOneOf(targetReservations, availableReservations) {
if (targetReservations.length === 0) {
return availableReservations[Math.floor(Math.random() * availableReservations.length)];
}
let targetSeats = targetReservations
.map((res) => this.building.getSeat(res.seatId))
.filter((d) => d !== null);
let nearestReservations = [];
let nearestDistance = -1;
for (let checkReservation of availableReservations) {
let checkSeat = this.building.getSeat(checkReservation.seatId);
if (!checkSeat)
continue;
let distance = checkSeat.nearestDistanceTo(targetSeats);
if (nearestDistance < 0 || distance < nearestDistance) {
// Found a closer distance, update nearestDistance and reset nearestReservations array
nearestDistance = distance;
nearestReservations = [checkReservation];
}
else if (distance === nearestDistance) {
// Same distance as the nearest, add to the list
nearestReservations.push