Last active
May 13, 2024 17:42
-
-
Save andybrackley/d3d924b0a9586094bbc77846f7fe74ee to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
type KeyedLookupT<TKey, TValue> = { | |
get: (key: TKey) => TValue | undefined | |
set: (key: TKey, value: TValue) => KeyedLookupT<TKey, TValue> | |
entries: () => TValue[] | |
} | |
const KeyedLookup = { | |
create: <TKey extends object | string, TValue>() : KeyedLookupT<TKey, TValue> => { | |
type MapValueT = { type: 'TValue', value: TValue} | |
type InnerMapT<TKey, TValue> = { | |
[key: string]: InnerMapT<TKey, TValue> | MapValueT | |
} | |
const isValue = (value: any) : value is MapValueT => 'type' in value && value.type === 'TValue' | |
const isInnerMap = ( value: any ) : value is InnerMapT<TKey, TValue> => !isValue(value) | |
const getKeyArray = ( key: TKey ) : string[] => typeof key === 'string' ? [ key ] : typeof key === 'object' ? Object.values(key) : [`${key}`] | |
const get = (map: InnerMapT<TKey, TValue>, key: TKey) => { | |
let currentMap = map | |
const keys = getKeyArray(key).reverse() | |
while(true) { | |
const key = keys.pop() | |
if(!key) return undefined | |
if(!(key in currentMap)) return undefined | |
if(keys.length === 0) return (currentMap[key] as MapValueT).value | |
currentMap = currentMap[key] as InnerMapT<TKey, TValue> | |
} | |
} | |
const set = (map: InnerMapT<TKey, TValue>, key: TKey, value: TValue) => { | |
const keys = getKeyArray(key) | |
const recurse = (map: InnerMapT<TKey, TValue>, currentKeys: string[]) : InnerMapT<TKey, TValue> => { | |
const key = currentKeys.pop() | |
if(!key) return map | |
if(keys.length === 0) return { ...map, [key]: { type: 'TValue', value: value } } | |
const current = key in map ? map[key] as InnerMapT<TKey, TValue> : {} | |
const updated : InnerMapT<TKey, TValue> = { | |
...map, | |
[key]: recurse(current, currentKeys) | |
} | |
return updated | |
} | |
return recurse(map, keys.reverse()) | |
} | |
const curry = (map: InnerMapT<TKey, TValue>) : KeyedLookupT<TKey, TValue> => ({ | |
get: (k: TKey) => get(map, k), | |
set: (k: TKey, v: TValue) => { | |
const upd = set(map, k, v) | |
return curry(upd) | |
}, | |
entries: () => { | |
const getEntries = (map: InnerMapT<TKey, TValue>) : TValue[] => { | |
return Object.values(map).reduce((array, value) => { | |
if(isValue(value)) { | |
return array.concat([value.value]) | |
} | |
if(isInnerMap(value)) { | |
return array.concat(getEntries(value)) | |
} | |
return array | |
}, [] as TValue[]) | |
} | |
return getEntries(map) | |
} | |
}) | |
return curry( {} ) | |
} | |
} | |
/* | |
TEST CODE.... | |
*/ | |
type KeyT = { | |
house: string, | |
postcode: string, | |
} | |
type DataT = { | |
house: string, | |
postcode: string, | |
person: string | |
} | |
const toKey = (data: DataT): KeyT => ({ | |
postcode: data.postcode, | |
house: data.house, | |
}) | |
const data : DataT[] = [ | |
{ postcode: 'XX112YY', house: '1', person: 'person1' }, | |
{ postcode: 'XX112YY', house: '1', person: 'person2' }, | |
{ postcode: 'XX112YY', house: '1', person: 'person3' }, | |
{ postcode: 'XX112YY', house: '1', person: 'person4' }, | |
{ postcode: 'XX122YY', house: '2', person: 'person5' }, | |
{ postcode: 'XX113YY', house: '1', person: 'person6' } | |
] | |
const map = data.reduce((m, d) => m.set(toKey(d), d), KeyedLookup.create<KeyT, DataT>()) | |
console.log('Map Entries', map.entries()) | |
/* | |
// TESTING OF LOOKUP | |
const emptyHouse = map.get( { postcode: 'XX112YY', house: '10' }) | |
const emptyPostcode = map.get( { postcode: 'XX112Y5', house: '1'}) | |
const first = map.get( { postcode: 'XX112YY', house: '1' } ) | |
console.log('first', first) | |
console.log('gettingEntries') | |
const entries = map.entries() | |
console.log('Entries:', entries) | |
// END TESTING OF LOOKUP | |
*/ | |
/* | |
Groupings.... | |
*/ | |
const groupData = <TKey extends string | object, TValue, TGroup> (data: TValue[], toGroupKey: (value: TValue) => TKey, onItem: (group: TGroup | undefined, item: TValue ) => TGroup) => { | |
const lookup = KeyedLookup.create<TKey, TGroup>() | |
return data.reduce((m, d) => { | |
const key = toGroupKey(d) | |
const existing = m.get(key) | |
const group = onItem(existing, d) | |
return m.set(key, group) | |
}, lookup) | |
} | |
type ArrayGroupT<TValue> = { | |
array: TValue[] | |
} | |
type HouseGroupedT<TValue> = { | |
house: string, | |
postcode: string, | |
peopleInHouse: number, | |
} & ArrayGroupT<string> | |
const updateGroup = (existing: HouseGroupedT<DataT> | undefined, item: DataT) => ({ | |
house: item.house, | |
postcode: item.postcode, | |
peopleInHouse: existing ? existing.peopleInHouse + 1 : 1, | |
array: existing ? existing.array.concat([item.person]) : [ item.person ] | |
}) | |
const grouped = groupData<KeyT, DataT, HouseGroupedT<DataT>>(data, (row: DataT) => toKey(row), updateGroup) | |
const house1 = grouped.get( { postcode: 'XX112YY', house: '1'} ) | |
const house2 = grouped.get( { postcode: 'XX122YY', house: '2'} ) | |
console.log('Grouping::House1', house1) | |
console.log('Group Entries:', grouped.entries()) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment