I was trying to implement a graph-like data structure in typescript and was type safing it, with difficulty, after many solutions I arrived at this:
interface Cities {
lisbon: any
porto: any
faro: any
}
class Graph<T> {
adjacencyList: {
[K in keyof T]?: (keyof T)[]
} = {}
addVertex(value: keyof T) {
if (!this.adjacencyList[value]) this.adjacencyList[value] = []
}
addEdge(vertex1: keyof T, vertex2: keyof T) {
this.adjacencyList[vertex1]?.push(vertex2)
this.adjacencyList[vertex2]?.push(vertex1)
}
}
is there a more elegant way of doing it? I wanted to use generics in order to be more versatile. Is something like this possible?
enum Cities {
"lisbon",
"porto",
"faro"
}
class Graph<T> {
adjacencyList: {
[K in T]?: T[]
} = {}
addVertex(value: T) {
if (!this.adjacencyList[value]) this.adjacencyList[value] = []
}
addEdge(vertex1: T, vertex2: T) {
this.adjacencyList[vertex1]?.push(vertex2)
this.adjacencyList[vertex2]?.push(vertex1)
}
}
says T is not assignable to type symbol...
That's because typescript has no guarantee that all the values in T
will actually be strings, numbers, or symbols (the only valid object keys as far as typescript is concerned). Think about what would happen if you fed number[]
as T
. So, add a clause that it has to extend string | number | symbol
:
class Graph<T extends string | number | symbol> {
adjacencyList: {
[K in T]?: T[]
} = {}
addVertex(value: T) {
if (!this.adjacencyList[value]) this.adjacencyList[value] = []
}
addEdge(vertex1: T, vertex2: T) {
this.adjacencyList[vertex1]?.push(vertex2)
this.adjacencyList[vertex2]?.push(vertex1)
}
}
Then, to feed an enum to it, use keyof typeof Enum
to get the union type consisting of all possible strings:
const foo = new Graph<keyof typeof Cities>();