Skip to main content
  1. Posts/

Heap datastructure with Slices/Arrays in Golang

··840 words·4 mins
Author
Hairizuan Noorazman
Software engineering experiments, implementation notes, and lessons learned.

Part of the software engineer journey is to learn data structures - especially if one were to go for the software interviews. Surprising, data structures knowledge and familiarity with it becomes somewhat important in them - with knowledge with certain data strucutre, certain problems become somewhat easier (also, sometimes, all one can do is simply stare in wonder at the algorithms and data structures that people in the past created)

One relatively important data structure that kind of come up during my study of data strucutres is the heap data structure. It is often mentioned that one can utilize the heap data structures “max” values pretty easily.

The following page would show the heap data structure in better detail with diagrams etc:
https://www.geeksforgeeks.org/heap-data-structure/

In most cases, the heap data structures - it is often represented or imagined as tress -> which is why, my initial test implementation of it is to somewhat build something close to this - like a tree:

type Node struct {
	value int
	left  *Node
	right *Node
}

func Heapify(n *Node) *Node {
	if n.left == nil && n.right == nil {
		return n
	}

	if n.left != nil {
		n.left = Heapify(n.left)
	}

	if n.right != nil {
		n.right = Heapify(n.right)
	}

	if n.left != nil {
		if n.value < n.left.value {
			tempLeft := n.left.left
			tempRight := n.left.right
			currentRight := n.right
			currentLeft := n.left
			currentLeft.right = currentRight
			currentLeft.left = n
			n.left = tempLeft
			n.right = tempRight
			n = currentLeft
		}

	}

	if n.right != nil {
		if n.value < n.right.value {
			tempLeft := n.right.left
			tempRight := n.right.right
			currentRight := n.right
			currentLeft := n.left
			currentRight.right = n
			currentRight.left = currentLeft
			n.left = tempLeft
			n.right = tempRight
			n = currentRight
		}

	}
	return n
}

func Printer(n *Node) {
	if n.left != nil {
		Printer(n.left)
	}
	fmt.Println(n.value)
	if n.right != nil {
		Printer(n.right)
	}
}

We have a node data struct, which we would use as nodes in a tree. We can then simply keep calling heapify to get the “max” value and bubble it to the top. Unfortunately, the node struct version is way harder to build out within a coding interview session - there is too much code to handle for this.

We can test the above node struct version of a heap by running the following function:

func nodeImplementation() {
	leftz := Node{value: 3}
	leftLeftz := Node{value: 4}
	rightz := Node{value: 2, right: &leftLeftz}
	topz := Node{value: 1, left: &leftz, right: &rightz}
	Printer(&topz)
	aa := Heapify(&topz)
	fmt.Println("after")
	Printer(aa)
	fmt.Println(aa.value)
}

Unfortunately, it is a bit harder to implemnent other useful and important functionality for a heap such as adding values to a heap or removing values from a heap. The code for making that is harder than expected.

Interestingly enough, there is a slice/array implementation of heaps. We would simply imagine the array laid out across the tree:

      0
    /   \
  1       2
 / \     / \
3   4   5   6

The above tree representation would show the index numbers of where it would be if it were to be represented in a slice/array.

The below code would be the implementation for the heap data structure used as an array. Important bit would be the formulas:

  • Left side node: 2n + 1
  • Right side node: 2n + 2
  • Current node: n
  • Parent node: (n-1)/2

The above formulas are to calculate the index-es of the other “nodes” on the array. Let’s demonstrate by giving an example:

For node 1, the left node is 3 and 4. By using the formula for left side node -> 2 x 1 + 1 = 3; it shows that the calculation is right. It would be the side for the right side node as well. For the parent of 1 (which is 0…). We can use the calculation for it as well: (1-1)/2 = 0 -> which is also correct; the parent of “node 1” is 0.

The golang code for building a heap is the following:

func ArrHeapify(nums []int, node int) {
	lhsIdx := 2*node + 1
	rhsIdx := 2*node + 2
	largestIdx := node

	if lhsIdx < len(nums) {
		if nums[lhsIdx] > nums[largestIdx] {
			largestIdx = lhsIdx
		}
	}

	if rhsIdx < len(nums) {
		if nums[rhsIdx] > nums[largestIdx] {
			largestIdx = rhsIdx
		}
	}

	if largestIdx != node {
		tempVal := nums[node]
		nums[node] = nums[largestIdx]
		nums[largestIdx] = tempVal
		ArrHeapify(nums, largestIdx)
	}
}

We can build the following driver code to test out our implementation:

func main() {
	a := []int{1, 3, 5, 3, 6, 13, 10, 9, 8, 15, 17}
	fmt.Println(a)

	for i := (len(a) - 1) / 2; i >= 0; i-- {
		ArrHeapify(a, i)
	}
	fmt.Println(a)

	a = append(a, 90)
	for i := (len(a) - 1) / 2; i >= 0; i-- {
		ArrHeapify(a, i)
	}
	fmt.Println(a)
}

Naturally, the following piece of code is not perfect - but then again, for software interviews, it’ll be something that we eventually have to be build without even thinking too much about it. Maybe I’ll write a future blog post that will cover more details with it.

Related

Nginx as API Gateway - focusing on auth_request directive

··1245 words·6 mins
On virtual machine How to “protect” api requests https://www.nginx.com/blog/deploying-nginx-plus-as-an-api-gateway-part-1/ Mostly is the auth_request directive Microservices are a software architectural style that structures an application as a collection of loosely coupled, independently deployable services. Each service in a microservices architecture represents a specific business capability and communicates with other services through well-defined APIs (Application Programming Interfaces). These services are designed to be small, focused, and can be developed, deployed, and scaled independently. Its a somewhat common architectural pattern that many companies go to when it comes to scaling out their development teams to build out their product.

Multiple Database Support - MySQL and SQLite support

··722 words·4 mins
I intend to try out the Turso service in order to see if there is any other potential serverless database that would have pretty decent type of billing for small projects. There isn’t a proper SQL based database that can be billed in a similar way to the Cloud Run product - it’ll be great if the billing of the database product would be along the amount of data being stored or amount of read/write requests done for the data instead of the usual charged based on how long the instance being run (based on how Cloud SQL is billed).

Serverless Applications with Cloud Run with Serverless MySQL from PlanetScale

··806 words·4 mins
Serverless computing, as seen in platforms like Cloud Run or AWS Lambda, allows developers to run code without managing the underlying infrastructure. This is achieved by automatically scaling the resources based on the incoming requests, and users are billed based on the actual execution time and resources consumed during each function or container invocation.