object/object_maths.js

{
	if (!global.Object)
		/**
		 * The namespace for all UF/Object utility functions, typically for static methods.
		 *
		 * @namespace Object
		 */
		global.Object = {};
	
	/**
	 * Adds a value to an object, recursively.
	 * @alias Object.add
	 *
	 * @param {Object} arg0_object - The object to pass.
	 * @param {number} arg1_value - The value to add to each variable in the object.
	 *
	 * @returns {Object}
	 */
	Object.add = function (arg0_object, arg1_value) {
		//Convert from parameters
		let object = arg0_object;
		let value = arg1_value;
		
		//Return statement
		return Object.operate(object, `n + ${value}`);
	};
	
	/**
	 * Adds values between two objects, recursively.
	 * @alias Object.addObjects
	 *
	 * @param {Object} arg0_object - The 1st object to pass.
	 * @param {Object} arg1_object - The 2nd object to pass.
	 * @param {Object} [arg2_options]
	 *  @param {boolean} [arg2_options.recursive=true] - Whether the operation is recursive.
	 *
	 * @returns {Object}
	 */
	Object.addObjects = function (arg0_object, arg1_object, arg2_options) {
		//Convert from parameters
		let object = arg0_object;
		let ot_object = arg1_object;
		let options = (arg2_options) ? arg2_options : {};
		
		//Return statement
		return Object.operateObjects(object, ot_object, `i = i + x;`, options);
	};
	
	/**
	 * Changes object ranges, non-recursively, for a given key.
	 * @alias Object.changeRange
	 *
	 * @param {Object} arg0_object - The object to pass.
	 * @param {string} arg1_key - The key corresponding to the range to modify.
	 * @param {string} [arg2_min_max_argument="both"] - Either 'minimum'/'maximum'/'both'.
	 * @param {number} arg3_value - The number to change these ranges by.
	 *
	 * @returns {Object}
	 */
	Object.changeRange = function (arg0_object, arg1_key, arg2_min_max_argument, arg3_value) {
		//Convert from parameters
		let object = arg0_object;
		let key = arg1_key;
		let min_max_argument = arg2_min_max_argument;
		let value = Math.round(Math.returnSafeNumber(arg3_value));
		
		//Add to object
		if (object[key]) {
			if (min_max_argument === "minimum") {
				object[key][0] += value;
			} else if (min_max_argument === "maximum") {
				object[key][1] += value;
			} else {
				object[key][0] += value;
				object[key][1] += value;
			}
		} else {
			if (min_max_argument === "minimum") {
				object[key] = [value, 0];
			} else if (min_max_argument === "maximum") {
				object[key] = [0, value];
			} else {
				object[key] = [value, value];
			}
		}
		
		//Return statement
		return object;
	};
	
	/**
	 * Divides an object by a value, recursively.
	 * @alias Object.divide
	 *
	 * @param {Object} arg0_object - The object to pass.
	 * @param {number} arg1_value - The value to divide each variable in the object by.
	 *
	 * @returns {Object}
	 */
	Object.divide = function (arg0_object, arg1_value) {
		//Convert from parameters
		let object = arg0_object;
		let value = arg1_value;
		
		//Return statement
		return Object.operate(object, `n/${value}`);
	};
	
	/**
	 * Divides two objects, recursively.
	 * @alias Object.divideObjects
	 *
	 * @param {Object} arg0_object - The 1st object to pass.
	 * @param {Object} arg1_object - The 2nd object to pass.
	 * @param {Object} [arg2_options]
	 *  @param {boolean} [arg2_options.recursive=true] - Whether the operation is recursive.
	 *
	 * @returns {Object}
	 */
	Object.divideObjects = function (arg0_object, arg1_object, arg2_options) {
		//Convert from parameters
		let object = arg0_object;
		let ot_object = arg1_object;
		let options = (arg2_options) ? arg2_options : {};
		
		//Return statement
		return Object.operateObjects(object, ot_object, `i = i/x`, options);
	};
	
	/**
	 * Expands a target value to its natural [min, max] boundaries, i.e. zero-values.
	 * @alias Object.expandValue
	 * 
	 * @param {Object<number>} arg0_object
	 * @param {Object} [arg1_options]
	 *  @param {boolean} [arg1_options.expand_max=true]
	 *  @param {boolean} [arg1_options.expand_min=true]
	 *  @param {number} [arg1_options.value=0] - The value to expand.
	 */
	Object.expandValue = function (arg0_object, arg1_options) {
		//Convert from parameters
		let object = (arg0_object) ? arg0_object : {};
		let options = (arg1_options) ? arg1_options : {};
		
		if (options.expand_max === undefined) options.expand_max = true;
		if (options.expand_min === undefined) options.expand_min = true;
		options.value = Math.returnSafeNumber(options.value);
		
		//Declare local instance variables
		let all_keys = Object.keys(object).map(Number).sort((a, b) => a - b);
		let return_obj = { ...object };
		
		//Iterate over all keys and expand options.value
		for (let i = 0; i < all_keys.length; i++)
			if (object[all_keys[i]] === options.value) {
				//Expand left: look for a neighbour that is not the target value
				if (options.expand_min && i > 0) {
					let left_key = all_keys[i - 1];
					
					if (object[left_key] !== options.value)
						return_obj[left_key + 1] = options.value;
				}
				
				//Expand right: look for a neighbour that is not the target value
				if (options.expand_max && i < all_keys.length - 1) {
					let right_key = all_keys[i + 1];
					
					if (object[right_key] !== options.value)
						return_obj[right_key - 1] = options.value;
				}
			}
		
		//Return statement
		return return_obj;
	};
	
	/**
	 * Calculates the average deviation of one object from another based on domain.
	 * @alias Object.getAverageDeviationFromObject
	 *
	 * @param {Object} arg0_object
	 * @param {Object} arg1_object
	 *
	 * @returns {number}
	 */
	Object.getAverageDeviationFromObject = function (arg0_object, arg1_object) {
		//Convert from parameters
		let object = arg0_object;
		let ot_object = arg1_object;
		
		//Declare local instance variables
		let all_object_keys = Object.keys(object);
		let all_ot_object_keys = Object.keys(ot_object);
		let average_deviations = [];
		let object_domain = [
			parseInt(all_object_keys[0]),
			parseInt(all_object_keys[all_object_keys.length - 1])
		];
		
		//Iterate over all_ot_object_keys
		for (let i = 0; i < all_ot_object_keys.length; i++) {
			let local_value = ot_object[all_ot_object_keys[i]];
			
			let closest_object_key = Array.findClosestPointInDomain(object_domain, local_value);
			let closest_object_value = object[closest_object_key];
			
			average_deviations.push(Math.returnSafeNumber(local_value)/Math.returnSafeNumber(closest_object_value, 1));
		}
		
		//Return statement
		return Array.getAverage(average_deviations);
	};
	
	/**
	 * Fetches the maximum value within an object.
	 * @alias Object.getMaximum
	 *
	 * @param {Object} arg0_object - The object to pass.
	 * @param {Object} [arg1_options]
	 *  @param {boolean} [arg1_options.include_ranges=false] - Whether to include ranges.
	 *  @param {boolean} [arg1_options.recursive=true] - Whether function is recursive.
	 *
	 * @returns {number}
	 */
	Object.getMaximum = function (arg0_object, arg1_options) {
		//Convert from parameters
		let object = arg0_object;
		let options = (arg1_options) ? arg1_options : {};
		
		//Initialise options
		if (options.recursive !== false) options.recursive = true;
		
		//Declare local instance variables
		let all_object_keys = Object.keys(object);
		let local_maximum = -Infinity;
		
		//Iterate over all_object_keys
		for (let i = 0; i < all_object_keys.length; i++) {
			let local_value = object[all_object_keys[i]];
			
			if (typeof local_value == "number")
				local_maximum = (local_maximum === undefined) ? local_value : Math.max(local_value, local_maximum);
			
			//Ranges handler
			if (options.include_ranges && Array.isArray(local_value))
				if (local_value.length === 2 && Array.isType(local_value, "number"))
					for (let x = 0; x < local_value.length; x++)
						local_maximum = (local_maximum === undefined) ? local_value[x] : Math.max(local_value[x], local_maximum);
			
			//Object handler
			if (options.recursive)
				if (typeof local_value == "object" && !Array.isArray(local_value)) {
					let subobject_maximum = Object.getMaximum(local_value, options);
					local_maximum = (local_maximum === undefined) ? subobject_maximum : Math.max(local_maximum, subobject_maximum);
				}
		}
		
		//Return statement
		return local_maximum;
	};
	
	/**
	 * Fetches the minimum value within an object.
	 * @alias Object.getMinimum
	 *
	 * @param {Object} arg0_object - The object to pass.
	 * @param {Object} [arg1_options]
	 *  @param {boolean} [arg1_options.include_ranges=false] - Whether to include ranges.
	 *  @param {boolean} [arg1_options.recursive=true] - Whether function is recursive.
	 *
	 * @returns {number}
	 */
	Object.getMinimum = function (arg0_object, arg1_options) {
		//Convert from parameters
		let object = arg0_object;
		let options = (arg1_options) ? arg1_options : {};
		
		//Declare local instance variables
		let all_object_keys = Object.keys(object);
		let local_minimum = Infinity;
		
		//Iterate over all_object_keys
		for (let i = 0; i < all_object_keys.length; i++) {
			let local_value = object[all_object_keys[i]];
			
			if (typeof local_value == "number")
				local_minimum = (local_minimum === undefined) ? local_value : Math.min(local_value, local_minimum);
			
			//Ranges handler
			if (options.include_ranges && Array.isArray(local_value))
				if (local_value.length === 2 && Array.isType(local_value, "number"))
					for (let x = 0; x < local_value.length; x++)
						local_minimum = (local_minimum === undefined) ? local_value[x] : Math.min(local_value[x], local_minimum);
			
			//Object handler
			if (options.recursive !== false)
				if (typeof local_value == "object" && !Array.isArray(local_value)) {
					let subobject_minimum = Object.getMinimum(local_value, options);
					local_minimum = (local_minimum === undefined) ? subobject_minimum : Math.min(local_minimum, subobject_minimum);
				}
		}
		
		//Return statement
		return local_minimum;
	};
	
	/**
	 * Fetches the object sum, recursively.
	 * @alias Object.getSum
	 *
	 * @param {Object} arg0_object - The object to pass.
	 * @param {Object} [arg1_options]
	 *  @param {boolean} [arg1_options.recursive=true] - Whether to sum recursively.
	 *
	 * @returns {number}
	 */
	Object.getSum = function (arg0_object, arg1_options) {
		//Convert from parameters
		let object = arg0_object;
		let options = (arg1_options) ? arg1_options : {};
		
		//Initialise options
		if (options.recursive !== false) options.recursive = true;
		
		//Declare local instance variables
		let all_object_keys = Object.keys(object);
		let total_sum = 0;
		
		//Iterate over all_object_keys
		for (let i = 0; i < all_object_keys.length; i++) {
			let local_value = object[all_object_keys[i]];
			
			if (typeof local_value == "number") {
				total_sum += local_value;
			} else if (typeof local_value == "object" && !Array.isArray(local_value)) {
				//Recursively call function
				total_sum += Object.getSum(local_value, options);
			}
		}
		
		//Return statement
		return total_sum;
	};
	
	/**
	 * Interpolates and normalises data given an absolute Ground Truth (GT) and an absolute, but relativistic rates map.
	 * @alias Object.interpolateGT
	 *
	 * @param {Object<number>} arg0_object - The Ground Truth (GT) map
	 * @param {Object<number>} arg1_object - The Relativistic Rates (RR) map
	 */
	//[QUARANTINE]
	Object.interpolateGT = function (arg0_object, arg1_object) {
		// Convert from parameters
		let object = { ...arg0_object };
		let ot_object = arg1_object;
	
		// Declare local instance variables
		let object_keys = Object.keys(object)
			.map(Number)
			.sort((a, b) => a - b);
		let ot_object_keys = Object.keys(ot_object)
			.map(Number)
			.sort((a, b) => a - b);
		let return_obj = {};
	
		// Create a sorted union of all unique keys from both objects
		let union_keys = [...new Set([...object_keys, ...ot_object_keys])].sort(
			(a, b) => a - b
		);
	
		// Handle extraneous values:
		// If RR contains years outside the GT range, add them to our GT anchors
		let min_gt = object_keys[0];
		let max_gt = object_keys[object_keys.length - 1];
	
		for (let i = 0; i < ot_object_keys.length; i++) {
			let current_rr_year = ot_object_keys[i];
			if (current_rr_year < min_gt || current_rr_year > max_gt) {
				object[current_rr_year] = ot_object[current_rr_year];
			}
		}
	
		// Re-evaluate object keys after adding extraneous RR values
		let all_anchor_keys = Object.keys(object)
			.map(Number)
			.sort((a, b) => a - b);
	
		let getRRValue = (ot_key) => {
			if (ot_object[ot_key] !== undefined) return ot_object[ot_key];
			if (ot_key <= ot_object_keys[0]) return ot_object[ot_object_keys[0]];
			if (ot_key >= ot_object_keys[ot_object_keys.length - 1])
				return ot_object[ot_object_keys[ot_object_keys.length - 1]];
	
			let lower_key = ot_object_keys.findLast((local_key) => local_key < ot_key);
			let upper_key = ot_object_keys.find((local_key) => local_key > ot_key);
			let weight = (ot_key - lower_key) / (upper_key - lower_key);
	
			return (
				ot_object[lower_key] +
				weight * (ot_object[upper_key] - ot_object[lower_key])
			);
		};
	
		// Iterate through GT segments (including augmented RR anchors)
		for (let i = 0; i < all_anchor_keys.length - 1; i++) {
			let start_key = all_anchor_keys[i];
			let end_key = all_anchor_keys[i + 1];
	
			let start_value_gt = object[start_key];
			let end_value_gt = object[end_key];
	
			let start_value_rr = getRRValue(start_key);
			let end_value_rr = getRRValue(end_key);
	
			// Calculate the correction ratios at anchor points
			// Safety check: if RR is 0, we treat the ratio as 0 if GT is 0, otherwise 1
			let start_ratio =
				start_value_rr === 0 ? (start_value_gt === 0 ? 0 : 1) : start_value_gt / start_value_rr;
			let end_ratio =
				end_value_rr === 0 ? (end_value_gt === 0 ? 0 : 1) : end_value_gt / end_value_rr;
	
			// Filter union_keys to only those that fall within this segment
			let segment_keys = union_keys.filter((k) => k >= start_key && k <= end_key);
	
			for (let x of segment_keys) {
				// If we already processed this year in a previous segment, skip it
				if (x === start_key && i > 0) continue;
	
				let current_rr = getRRValue(x);
				let progress = (x - start_key) / (end_key - start_key);
	
				// Linearly interpolate the discrepancy ratio
				let interpolated_ratio =
					start_ratio + progress * (end_ratio - start_ratio);
	
				return_obj[x] = current_rr * interpolated_ratio;
			}
		}
	
		// Return statement
		return return_obj;
	};
	
	/**
	 * Interpolates negative values by carrying over the last positive value found.
	 * @alias Object.interpolateNegatives
	 *
	 * @param {Object} arg0_object
	 *
	 * @returns {Object}
	 */
	Object.interpolateNegatives = function (arg0_object) {
		//Convert from parameters
		let object = arg0_object;
		
		//Declare local instance variables
		let all_object_keys = Object.keys(object)
			.map(Number).sort((a, b) => a - b);
		let last_positive = null;
		
		for (let local_key of all_object_keys) {
			let local_value = object[local_key];
			
			if (local_value >= 0) {
				last_positive = local_value;
			} else if (local_value < 0) {
				object[local_key] = last_positive;
			}
		}
		
		//Return object
		return object;
	};
	
	/**
	 * Inverts a fraction object, fetching the reciprocal of percentage values (1 - n).
	 * @alias Object.invertFraction
	 *
	 * @param {Object} arg0_object - The object to pass.
	 *
	 * @returns {Object}
	 */
	Object.invertFraction = function (arg0_object) {
		//Convert from parameters
		let object = JSON.parse(JSON.stringify(arg0_object));
		
		//Declare local instance variables
		let all_object_keys = Object.keys(object);
		
		//Iterate over all_object_keys
		for (let i = 0; i < all_object_keys.length; i++) {
			let local_value = object[all_object_keys[i]];
			
			object[all_object_keys[i]] = 1 - local_value;
		}
		
		//Return statement
		return object;
	};
	
	/**
	 * linearInterpolationObject() - Performs a linear interpolation operation on an object.
	 * @param {Object} arg0_object - The object to perform the linear interpolation on.
	 * @param {Object} [arg1_options]
	 *  @param {boolean} [arg1_options.all_years=false] - Optional. Whether to interpolate for every single year in the domain.
	 *  @param {Array<number>} [arg1_options.years] - Optional. The years to interpolate over if possible.
	 *
	 * @return {Object}
	 */
	Object.linearInterpolation = function (arg0_object, arg1_options) {
		// Convert from parameters
		let object = arg0_object;
		let options = arg1_options ? arg1_options : {};
		
		// Declare local instance variables
		let sorted_indices = Object.sortYearValues(object);
		let values = sorted_indices.values;
		let years = sorted_indices.years;
		
		// Guard clause if there are less than 2 years
		if (years.length < 2) return object;
		
		// Initialise target years for interpolation
		let target_years = options.years ? Array.toArray(options.years) : years;
		
		if (options.all_years) {
			var object_domain = Object.getDomain(object);
			target_years = [];
			
			// Iterate between object_domain[0] and object_domain[1]
			for (var i = object_domain[0]; i <= object_domain[1]; i++)
				target_years.push(i);
		}
		
		// Iterate over all requested years
		for (let i = 0; i < target_years.length; i++) {
			let current_year = target_years[i];
			let min_year = Math.returnSafeNumber(years[0]);
			let max_year = Math.returnSafeNumber(years[years.length - 1]);
			
			if (current_year >= min_year && current_year <= max_year) {
				// Find the two bounding points for linear interpolation
				let left_index = 0;
				
				for (let j = 0; j < years.length - 1; j++)
					if (current_year >= years[j] && current_year <= years[j + 1]) {
						left_index = j;
						break;
					}
				
				let pair_x = [years[left_index], years[left_index + 1]];
				let pair_y = [values[left_index], values[left_index + 1]];
				
				object[current_year] = Array.linearInterpolation(
					pair_x,
					pair_y,
					current_year,
				);
			}
		}
		
		// Return statement
		return object;
	};
	
	/**
	 * Modifies ranges in an object recursively, by operating on objects.
	 * @alias Object.modifyRanges
	 *
	 * @param {Object} arg0_object - The object to pass.
	 * @param {number} arg1_value - The value to modify ranges by.
	 * @param {Object} [arg2_options]
	 *  @param {boolean} [arg2_options.include_numbers=true] - Whether to include single numbers.
	 *
	 * @returns {Object}
	 */
	Object.modifyRanges = function (arg0_object, arg1_value, arg2_options) {
		//Convert from parameters
		let object = arg0_object;
		let value = Math.returnSafeNumber(arg1_value);
		let options = (arg2_options) ? arg2_options : {};
		
		//Initialise options
		options.include_numbers = (options.include_numbers !== false);
		
		//Declare local instance variables
		let all_object_keys = Object.keys(object);
		
		//Iterate over all_object
		for (let i = 0; i < all_object_keys.length; i++) {
			let local_value = object[all_object_keys[i]];
			
			//Check if local_value is a range
			if (Array.isArray(local_value))
				if (local_value.length === 2)
					if (Array.isType(local_value, "number")) {
						local_value[0] += value;
						local_value[1] += value;
					}
			//Check if local_value is a number
			if (options.include_numbers && typeof local_value === "number")
				object[all_object_keys[i]] += value;
		}
		
		//Return statement
		return object;
	};
	
	/**
	 * Multiplies an object by a value, recursively.
	 * @alias Object.multiply
	 *
	 * @param {Object} arg0_object - The object to pass.
	 * @param {number} arg1_value - The value to multiply each variable by.
	 *
	 * @returns {Object}
	 */
	Object.multiply = function (arg0_object, arg1_value) {
		//Convert from parameters
		let object = arg0_object;
		let value = arg1_value;
		
		//Return statement
		return Object.operate(object, `n*${value}`);
	};
	
	/**
	 * Multiplies an object recursively.
	 * @alias Object.multiplyObjects
	 *
	 * @param {Object} arg0_object - The 1st object to pass.
	 * @param {Object} arg1_object - The 2nd object to pass.
	 * @param {Object} [arg2_options]
	 *  @param {boolean} [arg2_options.recursive=true] - Whether the operation is recursive.
	 *
	 * @returns {Object}
	 */
	Object.multiplyObjects = function (arg0_object, arg1_object, arg2_options) {
		//Convert from parameters
		let object = arg0_object;
		let ot_object = arg1_object;
		let options = (arg2_options) ? arg2_options : {};
		
		//Return statement
		return Object.operate(object, ot_object, `i = i*x`, options);
	};
	
	/**
	 * Performs an operation on a single object, recursively.
	 * @alias Object.operate
	 *
	 * @param {Object} arg0_object - The object to pass.
	 * @param {string} arg1_equation - The string literal to use as an equation ('n' = current value).
	 * @param {Object} [arg2_options]
	 *  @param {boolean} [arg2_options.log_errors=false] - Whether to log errors.
	 *  @param {boolean} [arg2_options.only_numbers=true] - Whether only numbers can be operated on.
	 *  @param {boolean} [arg2_options.recursive=true] - Whether the operation is recursive.
	 *
	 * @returns {Object}
	 */
	Object.operate = function (arg0_object, arg1_equation, arg2_options) {
		//Convert from parameters
		let object = arg0_object;
		let equation = arg1_equation;
		let options = (arg2_options) ? arg2_options : {};
		
		//Declare local instance variables
		let all_object_keys = Object.keys(object);
		let log_error_string = (options.log_errors) ? `console.log(e);` : "";
		let only_numbers = (options.only_numbers !== false);
		
		let equation_expression = `
			try { return ${equation}; } catch (e) {${log_error_string}};
		`;
		let equation_function = new Function("n", equation_expression);
		let processed_object = {};
		
		//Calculate the operation recursively
		for (let i = 0; i < all_object_keys.length; i++) {
			let local_value = object[all_object_keys[i]];
			
			if (typeof local_value == "object" && !Array.isArray(local_value)) {
				if (options.recursive !== false)
					processed_object[all_object_keys[i]] = Object.operate(local_value, equation, options);
			} else {
				if ((only_numbers && typeof local_value == "number") || !only_numbers) {
					processed_object[all_object_keys[i]] = equation_function(local_value);
				} else {
					processed_object[all_object_keys[i]] = local_value;
				}
			}
		}
		
		//Return statement
		return processed_object;
	};
	
	/**
	 * Performs an operation on two objects together, recursively.
	 * @alias Object.operateObjects
	 *
	 * @param {Object} arg0_object - The 1st object to pass.
	 * @param {Object} arg1_object - The 2nd object to pass.
	 * @param {string} arg2_equation - The string literal ('i' = 1st obj, 'x' = 2nd obj).
	 * @param {Object} [arg3_options]
	 *  @param {boolean} [arg3_options.log_errors=false] - Whether to log errors.
	 *  @param {boolean} [arg3_options.only_numbers=true] - Whether only numbers can be operated on.
	 *  @param {boolean} [arg3_options.recursive=true] - Whether the operation is recursive.
	 *
	 * @returns {{object: Object, ot_object: Object}}
	 */
	Object.operateObjects = function (arg0_object, arg1_object, arg2_equation, arg3_options) {
		//Convert from parameters
		let object = arg0_object;
		let ot_object = arg1_object;
		let equation = arg2_equation;
		let options = (arg3_options) ? arg3_options : {};
		
		//Declare local instance variables
		let all_object_keys = Object.keys(object);
		let all_ot_object_keys = Object.keys(ot_object);
		let only_numbers = (options.only_numbers !== false);
		
		let equation_expression = `
			try { ${equation} } catch (e) {};
			return { object: i, ot_object: x };
		`;
		let equation_function = new Function("i", "x", equation_expression);
		let processed_object = {};
		let processed_ot_object = {};
		
		//Combine keys from both objects to ensure full coverage
		let combined_keys = Array.from(new Set([...all_object_keys, ...all_ot_object_keys]));
		
		for (let key of combined_keys) {
			let i_value = object[key];
			let x_value = ot_object[key];
			
			//Defaults
			if (i_value === undefined) i_value = 0;
			if (x_value === undefined) x_value = 0;
			
			if (typeof i_value === "object" && !Array.isArray(i_value)) {
				if (options.recursive !== false) {
					let local_return_values = Object.operateObjects(i_value, x_value, equation, options);
					processed_object[key] = local_return_values.object;
					processed_ot_object[key] = local_return_values.ot_object;
				}
			} else {
				let local_return_values = equation_function(i_value, x_value);
				
				if ((only_numbers && typeof i_value === "number" && typeof x_value === "number") || !only_numbers) {
					processed_object[key] = local_return_values.object;
					processed_ot_object[key] = local_return_values.ot_object;
				} else {
					processed_object[key] = i_value;
					processed_ot_object[key] = x_value;
				}
			}
		}
		
		//Return statement
		return {
			object: processed_object,
			ot_object: processed_ot_object
		};
	};
	
	/**
	 * Standardises the object to maximum = 1, adjusting all other values proportionally.
	 * @alias Object.standardiseFraction
	 *
	 * @param {Object} arg0_object - The object to pass.
	 *
	 * @returns {Object}
	 */
	Object.standardiseFraction = function (arg0_object) {
		//Convert from parameters
		let object = JSON.parse(JSON.stringify(arg0_object));
		
		//Declare local instance variables
		let all_object_keys = Object.keys(object);
		let object_maximum = Object.getMaximum(object);
		
		//Iterate over all_object_keys
		for (let i = 0; i < all_object_keys.length; i++) {
			let local_value = object[all_object_keys[i]];
			
			if (object_maximum === 0) {
				object[all_object_keys[i]] = 0;
			} else {
				object[all_object_keys[i]] = local_value/object_maximum;
			}
		}
		
		//Return statement
		return object;
	};
	
	/**
	 * Standardises the object to a given total.
	 * @alias Object.standardisePercentage
	 *
	 * @param {Object} arg0_object - The object to pass.
	 * @param {number} [arg1_total=1] - The total figure to adjust the object to.
	 * @param {boolean} [arg2_round=false] - Whether to force rounding.
	 *
	 * @returns {Object}
	 */
	Object.standardisePercentage = function (arg0_object, arg1_total, arg2_round) {
		//Convert from parameters
		let object = JSON.parse(JSON.stringify(arg0_object));
		let total = (arg1_total) ? arg1_total : 1;
		let round = arg2_round;
		
		//Declare local instance variables
		let all_object_keys = Object.keys(object);
		let object_total = 0;
		
		//Fetch object_total
		for (let i = 0; i < all_object_keys.length; i++)
			object_total += Math.returnSafeNumber(object[all_object_keys[i]]);
		
		//Standardise to object_total
		for (let i = 0; i < all_object_keys.length; i++) {
			let local_value = object[all_object_keys[i]];
			
			//Set local_value to % value
			local_value = local_value/object_total;
			
			//Multiply % value by total
			object[all_object_keys[i]] = (round) ?
				Math.ceil(local_value*total) : local_value * total;
		}
		
		//Return statement
		return object;
	};
	
	/**
	 * Subtracts a value from an object, recursively.
	 * @alias Object.subtract
	 *
	 * @param {Object} arg0_object - The object to pass.
	 * @param {number} arg1_value - The value to subtract.
	 *
	 * @returns {Object}
	 */
	Object.subtract = function (arg0_object, arg1_value) {
		//Convert from parameters
		let object = arg0_object;
		let value = arg1_value;
		
		//Return statement
		return Object.operate(object, `n - ${value}`);
	};
	
	/**
	 * Subtracts one object from another, recursively.
	 * @alias Object.subtractObjects
	 *
	 * @param {Object} arg0_object - The 1st object.
	 * @param {Object} arg1_object - The 2nd object.
	 * @param {Object} [arg2_options]
	 *  @param {boolean} [arg2_options.recursive=true] - Whether recursive.
	 *
	 * @returns {{object: Object, ot_object: Object}}
	 */
	Object.subtractObjects = function (arg0_object, arg1_object, arg2_options) {
		//Convert from parameters
		let object = arg0_object;
		let ot_object = arg1_object;
		let options = (arg2_options) ? arg2_options : {};
		
		//Return statement
		return Object.operateObjects(object, ot_object, `i = i - x`, options);
	};
	
	/**
	 * Trims a specific value from the start and/or end of an object map.
	 * @alias Object.trimValue
	 *
	 * @param {Object<number>} arg0_object
	 * @param {Object} [arg1_options]
	 *  @param {boolean} [arg1_options.trim_start=true] - Remove leading target values.
	 *  @param {boolean} [arg1_options.trim_end=true] - Remove trailing target values.
	 *  @param {number} [arg1_options.value=0] - The value to trim.
	 */
	Object.trimValue = function (arg0_object, arg1_options) {
		// Convert from parameters
		let object = arg0_object ? arg0_object : {};
		let options = arg1_options ? arg1_options : {};
		
		if (options.trim_start === undefined) options.trim_start = true;
		if (options.trim_end === undefined) options.trim_end = true;
		options.value =
			typeof Math.returnSafeNumber === "function"
				? Math.returnSafeNumber(options.value)
				: options.value || 0;
		
		// Declare local instance variables
		let all_keys = Object.keys(object)
		.map(Number)
		.sort((a, b) => a - b);
		let return_obj = {};
		
		let start_index = 0;
		let end_index = all_keys.length - 1;
		
		// Find the first index that is NOT the target value
		if (options.trim_start) {
			while (
				start_index < all_keys.length &&
				object[all_keys[start_index]] === options.value
				) {
				start_index++;
			}
		}
		
		// Find the last index that is NOT the target value
		if (options.trim_end) {
			while (
				end_index >= start_index &&
				object[all_keys[end_index]] === options.value
				) {
				end_index--;
			}
		}
		
		// Build the new object from the sliced range
		for (let i = start_index; i <= end_index; i++) {
			let current_key = all_keys[i];
			return_obj[current_key] = object[current_key];
		}
		
		return return_obj;
	};
}