/* Module "maputil.wren" */
import "./check" for Check
/* MapUtil supplements the Map class with some other operations on maps. */
class MapUtil {
// Creates a new Map from lists of keys and values
// which must be of the same length.
static create(keys, values) {
Check.list("keys", keys)
var count = keys.count
Check.list("values", values, count, count)
var m = {}
for (i in 0...count) m[keys[i]] = values[i]
return m
}
// Creates a new Map from a sequence of keys and assigns
// the same value to all of them.
static createSame(keys, value) {
Check.seq("keys", keys)
var m = {}
for (key in keys) m[key] = value
return m
}
// Adds entries to an existing Map from lists of keys and values
// which must be of the same length.
// Keys which are already present in the Map have their values replaced
// if 'replace' is true but not otherwise.
// Returns the Map after the additions.
static addAll(map, keys, values, replace) {
Check.map("map", map)
Check.list("keys", keys)
var count = keys.count
Check.list("values", values, count, count)
Check.bool("replace", replace)
for (i in 0...count) {
if (!replace && map.containsKey(key[i])) continue
map[keys[i]] = values[i]
}
return map
}
// Adds entries to an existing Map from a sequence of keys and assigns
// the same value to all of them.
// Keys which are already present in the Map have their values replaced
// if 'replace' is true but not otherwise.
// Returns the Map after the additions.
static addAllSame(map, keys, value, replace) {
Check.map("map", map)
Check.seq("keys", keys)
Check.bool("replace", replace)
for (key in keys) {
if (!replace && map.containsKey(key)) continue
map[key] = value
}
return map
}
// Merges all the elements of map2 into map1.
// Keys which are already present in map1 have their values replaced
// if 'replace' is true but not otherwise.
// Returns map1 after the additions.
// A specialized version of 'addAll'.
static merge(map1, map2, replace) {
Check.map("map1", map)
Check.map("map2", map)
for (key in map2.keys) {
if (!replace && map1.containsKey(key)) continue
map1[key] = map2[key]
}
return map
}
// Returns true if all keys in the 'keys' sequence are keys of an existing Map
// or false otherwise.
static containsAll(map, keys) {
Check.map("map", map)
Check.seq("keys", keys)
for (key in keys) {
if (!map.containsKey(key)) return false
}
return true
}
// Returns true if any key in the 'keys' sequence is a key of an existing Map
// or false otherwise.
static containsAny(map, keys) {
Check.map("map", map)
Check.seq("keys", keys)
for (key in keys) {
if (map.containsKey(key)) return true
}
return false
}
// Returns true if no key in the 'keys' sequence is a key of an existing Map
// or false otherwise.
static containsNone(map, keys) { !containsAny(map, keys) }
// Removes all keys in the 'keys' sequence from an existing Map
// and returns a list of the associated values removed.
static removeAll(map, keys) {
Check.map("map", map)
if (map.isEmpty) return []
Check.seq("keys", keys)
var removals = []
for (key in keys) {
if (map.containsKey(key)) removals.add(map.remove(key))
}
return removals
}
// Removes all entries from 'map' whose key satisfies the predicate
// function 'fn' and returns a list of the associated values removed.
static removeBy(map, fn) {
Check.map("map", map)
Check.func("fn", fn, 1)
if (map.isEmpty) return []
var removals = []
for (key in map.keys) {
if (fn.call(key)) removals.add(map.remove(key))
}
return removals
}
// Copies the elements of 'map' to a new Map object.
static copy(map) {
Check.map("map", map)
var newMap = {}
for (key in map.keys) newMap[key] = map[key]
return newMap
}
// Copies all elements from map1 and then map2 to a new Map object.
// Keys in map1 which are also present in map2 have their values replaced
// if 'replace' is true but not otherwise.
// Returns the new Map.
static union(map1, map2, replace) {
Check.map("map1", map1)
Check.map("map2", map2)
Check.bool("replace", replace)
var newMap = {}
for (key in map1.keys) newMap[key] = map1[key]
for (key in map2.keys) {
if (!replace && map1.containsKey(key)) continue
newMap[key] = map2[key]
}
return newMap
}
// Copies all elements for which map1 and map2 have keys in common to a new Map object.
// Keys in map1 have their values replaced if 'replace' is true but not otherwise.
// Returns the new Map.
static intersect(map1, map2, replace) {
Check.map("map1", map1)
Check.map("map2", map2)
Check.bool("replace", replace)
var newMap = {}
for (key in map1.keys) {
if (!map2.containsKey(key)) continue
newMap[key] = replace ? map2[key] : map1[key]
}
return newMap
}
// Copies all elements of map1 which do not have keys in common with map2 to a new Map object.
static except(map1, map2) {
Check.map("map1", map1)
Check.map("map2", map2)
var newMap = {}
for (key in map1.keys) {
if (!map2.containsKey(key)) newMap[key] = map1[key]
}
return newMap
}
// Copies all elements which are in map1 or map2, but not both, to a new Map object.
static symDiff(map1, map2) {
Check.map("map1", map1)
Check.map("map2", map2)
return union(except(map1, map2), except(map2, map1), false)
}
// Returns whether or not map1 is a submap of map2.
static isSubmap(map1, map2) {
Check.map("map1", map1)
Check.map("map2", map2)
if (map1.count > map2.count) return false
for (key in map1.keys) {
if (!map2.containsKey(key)) return false
}
return true
}
// Returns whether or not map1 is a proper submap of map2.
static isProperSubmap(map1, map2) {
Check.map("map1", map1)
Check.map("map2", map2)
if (map1.count >= map2.count) return false
for (key in map1.keys) {
if (!map2.containsKey(key)) return false
}
return true
}
// Returns whether or not map1 is a supermap of map2.
// A specialized version of 'containsAll'.
static isSupermap(map1, map2) {
Check.map("map1", map1)
Check.map("map2", map2)
if (map1.count <= map2.count) return false
for (key in map2.keys) {
if (!map1.containsKey(key)) return false
}
return true
}
// Finds the maximum value when the function 'fn' is applied to each key in 'map'
// and returns the first key found which corresponds to that maximum.
// Only works for function return types supporting the '>' operator unless
// there's 1 element (returns it's key) or no elements (returns null).
static max(map, fn) {
Check.map("map", map)
Check.func("fn", fn, 1)
var maxKey
var maxVal
var first = true
for (k in map.keys) {
val = fn.call(k)
if (first || val > maxVal) {
maxKey = k
maxVal = val
first = false
}
}
return maxKey
}
// Finds the minimum value when the function 'fn' is applied to each key in 'map'
// and returns the first key found which corresponds to that minimum.
// Only works for function return types supporting the '<' operator unless
// there's 1 element (returns it's key) or no elements (returns null).
static min(map, fn) {
Check.map("map", map)
Check.func("fn", fn, 1)
var minKey
var minVal
var first = true
for (k in map.keys) {
var val = fn.call(k)
if (first || val < minVal) {
minKey = k
minVal = val
if (first) first = false
}
}
return minKey
}
// Returns whether two maps, map1 and map2, are the same length
// and contain 'equal' elements, keys and values, using the '==' operator.
static areEqual(map1, map2) {
Check.map("map1", map1)
Check.map("map2", map2)
if (map1.count != map2.count) return false
for (key in map1.keys) {
if (!map2.containsKey(key) || map1[key] != map2[key]) return false
}
return true
}
// Returns whether two maps, map1 and map2, are the same length
// and contain the same keys but not necessarily the same corresponding values.
static sameKeys(map1, map2) {
Check.map("map1", map1)
Check.map("map2", map2)
if (map1.count != map2.count) return false
for (key in map1.keys) {
if (!map2.containsKey(key)) return false
}
return true
}
// Sorts copies of the MapEntries of 'map' into key order using the default comparer
// function: {|a, b| a < b } and returns a sequence (not list) of them.
// Doesn't affect 'map' itself but enables it to be iterated in sorted order.
static sort(map) {
Check.map("map", map)
return map.keys.toList.sort().map { |k| MapEntry.new(k, map[k]) }
}
// Sorts copies of the MapEntries of 'map' into key order using a
// comparison function 'comparer' and returns a sequence (not list) of them.
// Doesn't affect 'map' itself but enables it to be iterated in sorted order.
static sort(map, comparer) {
Check.map("map", map)
return map.keys.toList.sort(comparer).map { |k| MapEntry.new(k, map[k]) }
}
}
/* MultiSet treats a Map as if it were a Bag.*/
class MultiSet {
// If 'key' exists in 'map' increases its value by the positive integer 'inc'.
// Otherwise creates a new entry with that key and a value of 1.
// Map values must be numeric. Returns 'map' after the change.
static add(map, key, inc) {
Check.map("map", map)
Check.posInt("inc", inc)
if (map.containsKey(key)) {
map[key] = map[key] + inc
} else {
map[key] = 1
}
return map
}
// If 'key' exists in 'map' decreases its value by the positive integer 'dec'.
// If the resulting value is <= 0, the entry is removed from the map.
// Map values must be numeric. Returns 'map' after the change.
static sub(map, key, dec) {
Check.map("map", map)
Check.posInt("dec", dec)
if (map.containsKey(key)) {
if (map[key] <= dec) {
map.remove(key)
} else {
map[key] = map[key] - dec
}
}
return map
}
// Convenience versions of the above methods where inc/dec is always 1.
static add(map, key) { add(map, key, 1) }
static sub(map, key) { sub(map, key, 1) }
// Returns the total number of members (as opposed to elements) in 'map'.
static count(map) {
Check.map("map", map)
var total = 0
for (key in map.keys) total = total + map[key]
return total
}
// Returns whether or not all values of 'map' are equal to 1
static allDistinct(map) {
Check.map("map", map)
return map.values.all { |v| v == 1 }
}
}