Skip to content

Instantly share code, notes, and snippets.

@andybrackley
Last active May 13, 2024 17:42
Show Gist options
  • Save andybrackley/d3d924b0a9586094bbc77846f7fe74ee to your computer and use it in GitHub Desktop.
Save andybrackley/d3d924b0a9586094bbc77846f7fe74ee to your computer and use it in GitHub Desktop.
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