├── .github └── workflows │ └── ci.yml ├── LICENSE ├── README.md ├── go.mod ├── list ├── doc.go ├── list.go └── list_test.go ├── queue ├── doc.go ├── queue.go └── queue_test.go ├── stack ├── doc.go ├── stack.go └── stack_test.go └── tree ├── doc.go ├── htree.go ├── tree.go └── tree_test.go /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | lint: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Install Go 14 | uses: actions/setup-go@v2 15 | with: 16 | go-version: '1.18' 17 | - name: Install goimports 18 | run: go install golang.org/x/tools/cmd/goimports@latest 19 | - name: Checkout code 20 | uses: actions/checkout@v3 21 | - run: goimports -w . 22 | - run: go mod tidy 23 | - name: Verify no changes from goimports and go mod tidy 24 | run: | 25 | if [ -n "$(git status --porcelain)" ]; then 26 | exit 1 27 | fi 28 | test: 29 | runs-on: ubuntu-latest 30 | steps: 31 | - name: Install Go 32 | uses: actions/setup-go@v2 33 | with: 34 | go-version: '1.18' 35 | - name: Checkout code 36 | uses: actions/checkout@v2 37 | - name: Run tests 38 | run: go test ./... -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Shivam Mamgain 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ppds [![godoc](https://godoc.org/github.com/shivammg/ppds?status.svg)](https://godoc.org/github.com/shivamMg/ppds) ![Build](https://github.com/shivamMg/trie/actions/workflows/ci.yml/badge.svg?branch=master) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 2 | 3 | Pretty Print Data Structures 4 | 5 | Stacks, queues, trees and linked lists are data structures that you might find yourself working with quite often. This library lets you pretty print these with minimum effort. Certain assumptions can be made for each data structure, for instance, a stack or a queue will have methods synonymous to Push (insert) and Pop (remove), a tree node will have links to its child nodes, or a linked list node will have a link to its next adjacent node. This library utilises those assumptions, and exports interfaces and functions to pretty print them. 6 | 7 | The following data structures are supported: 8 | 9 | ### Trees 10 | [![tree](https://godoc.org/github.com/shivammg/ppds?status.svg)](https://godoc.org/github.com/shivamMg/ppds/tree) 11 | 12 | A type that satisfies the following interface can be printed using `tree.Print`. 13 | 14 | ``` 15 | type Node interface { 16 | Data() interface{} 17 | Children() []Node 18 | } 19 | ``` 20 | 21 | ``` 22 | Music 23 | ├──────────────────────────────────────────┐ 24 | Classical Pop/Rock 25 | ├────────────────────┐ ├───────────────────────────┐ 26 | Instrumental Vocal Organic Electronic 27 | ├──────┐ ├─────────────┐ ├────────────┐ ├────────────────┐ 28 | Piano Orchestra Opera Chorus Rock Country Pop Techno 29 | ├──────┐ ├─────┐ │ ├────────┐ ├─────────┐ ├────────────┐ 30 | Light Heavy Male Female Heavy metal Dancing Soft Late pop Disco Soft techno Hard techno 31 | ``` 32 | 33 | If branching factor is high or data lengths are large, it would make more sense to print the tree horizontally. That can be done using `tree.PrintHr` function. 34 | 35 | ``` 36 | Music ┬─ Classical ┬─ Instrumental ┬─ Piano 37 | │ │ └─ Orchestra ┬─ Light 38 | │ │ └─ Heavy 39 | │ └─ Vocal ┬─ Opera ┬─ Male 40 | │ │ └─ Female 41 | │ └─ Chorus 42 | └─ Pop/Rock ┬─ Organic ┬─ Rock ── Heavy metal 43 | │ └─ Country ┬─ Dancing 44 | │ └─ Soft 45 | └─ Electronic ┬─ Pop ┬─ Late pop 46 | │ └─ Disco 47 | └─ Techno ┬─ Soft techno 48 | └─ Hard techno 49 | ``` 50 | 51 | Inspired by [tree](http://mama.indstate.edu/users/ice/tree/), a popular directory listing command, `tree.PrintHrn` prints the tree in similar style. Every node is printed on a separate line making the output tree look less cluttered and more elegant. It does take up vertical space though. 52 | 53 | ``` 54 | Music 55 | ├─ Classical 56 | │ ├─ Instrumental 57 | │ │ ├─ Piano 58 | │ │ └─ Orchestra 59 | │ │ ├─ Light 60 | │ │ └─ Heavy 61 | │ └─ Vocal 62 | │ ├─ Opera 63 | │ │ ├─ Male 64 | │ │ └─ Female 65 | │ └─ Chorus 66 | └─ Pop/Rock 67 | ├─ Organic 68 | │ ├─ Rock 69 | │ │ └─ Heavy metal 70 | │ └─ Country 71 | │ ├─ Dancing 72 | │ └─ Soft 73 | └─ Electronic 74 | ├─ Pop 75 | │ ├─ Late pop 76 | │ └─ Disco 77 | └─ Techno 78 | ├─ Soft techno 79 | └─ Hard techno 80 | ``` 81 | 82 | ### Linked Lists 83 | [![list](https://godoc.org/github.com/shivammg/ppds?status.svg)](https://godoc.org/github.com/shivamMg/ppds/list) 84 | 85 | ``` 86 | type Node interface { 87 | Data() interface{} 88 | Next() Node 89 | } 90 | ``` 91 | 92 | A counter is also printed below each node so one doesn't have to manually count them by pointing a finger at the screen. Also, a list can contain a million elements, in which case counting by simply shifting finger on screen might not even be possible. 93 | 94 | ``` 95 | ┌───┐┌───┐┌───┐┌─┐┌────┐┌────┐┌────┐ 96 | │333├┤222├┤111├┤0├┤-111├┤-222├┤-333│ 97 | ├───┤├───┤├───┤├─┤├────┤├────┤├────┤ 98 | │ 1││ 2││ 3││4││ 5││ 6││ 7│ 99 | └───┘└───┘└───┘└─┘└────┘└────┘└────┘ 100 | ``` 101 | 102 | ### Stacks 103 | [![stack](https://godoc.org/github.com/shivammg/ppds?status.svg)](https://godoc.org/github.com/shivamMg/ppds/stack) 104 | 105 | ``` 106 | type Stack interface { 107 | Pop() (ele interface{}, ok bool) 108 | Push(ele interface{}) 109 | } 110 | ``` 111 | 112 | ``` 113 | │ factorial (1) │7│ 114 | │ factorial (2) │6│ 115 | │ factorial (3) │5│ 116 | │ factorial (4) │4│ 117 | │ factorial (5) │3│ 118 | │ factorial (6) │2│ 119 | │ factorial (7) │1│ 120 | └───────────────┴─┘ 121 | ``` 122 | 123 | ### Queues 124 | [![queue](https://godoc.org/github.com/shivammg/ppds?status.svg)](https://godoc.org/github.com/shivamMg/ppds/queue) 125 | 126 | ``` 127 | type Queue interface { 128 | Pop() (ele interface{}, ok bool) 129 | Push(ele interface{}) 130 | } 131 | ``` 132 | 133 | ``` 134 | ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ 135 | →│Dec│→│Nov│→│Oct│→│Sep│→│Aug│→│Jul│→│Jun│→│May│→│Apr│→│Mar│→│Feb│→│Jan│→ 136 | ├───┤ ├───┤ ├───┤ ├───┤ ├───┤ ├───┤ ├───┤ ├───┤ ├───┤ ├───┤ ├───┤ ├───┤ 137 | │ 1│ │ 2│ │ 3│ │ 4│ │ 5│ │ 6│ │ 7│ │ 8│ │ 9│ │ 10│ │ 11│ │ 12│ 138 | └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ 139 | ``` 140 | 141 | ## Notes 142 | 143 | Printing Stacks and Queues requires defining Pop and Push methods. Internally these methods are used to get all elements - first by popping all elements then pushing them back in correct order. Use it with caution since the actual data structure is being modified when printing. After printing it'll be back to its original state. I've discussed why I decided to keep it this way in this [reddit thread](https://redd.it/8pbvrd). If there is no simultaneous read/write while printing, for instance from a different go routine, then you don't need to worry about it. Also, this caveat is only for Stacks and Queues. 144 | 145 | ## Contribute 146 | 147 | Bug reports, PRs, feature requests, all are welcome. 148 | 149 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/shivamMg/ppds 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /list/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package list provides implementation to get and print formatted linked list. 3 | 4 | Example: 5 | 6 | import "github.com/shivamMg/ppds/list" 7 | 8 | // a linked list node. implements list.Node. 9 | type Node struct { 10 | data int 11 | next *Node 12 | } 13 | 14 | func (n *Node) Data() interface{} { 15 | return strconv.Itoa(n.data) 16 | } 17 | 18 | func (n *Node) Next() list.Node { 19 | return n.next 20 | } 21 | 22 | // n1 := Node{data: 11} 23 | // n2 := Node{12, &n1} 24 | // n3 := Node{13, &n2} 25 | // list.Print(&n3) 26 | 27 | */ 28 | package list 29 | -------------------------------------------------------------------------------- /list/list.go: -------------------------------------------------------------------------------- 1 | package list 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strings" 7 | "unicode/utf8" 8 | ) 9 | 10 | const ( 11 | BoxVer = "│" 12 | BoxHor = "─" 13 | BoxDownLeft = "┐" 14 | BoxDownRight = "┌" 15 | BoxUpLeft = "┘" 16 | BoxUpRight = "└" 17 | BoxVerLeft = "┤" 18 | BoxVerRight = "├" 19 | ) 20 | 21 | // Node represents a linked list node. 22 | type Node interface { 23 | // Data must return a value representing the node. 24 | Data() interface{} 25 | // Next must return a pointer to the next node. The last element 26 | // of the list must return a nil pointer. 27 | Next() Node 28 | } 29 | 30 | // Print prints the formatted linked list to standard output. 31 | func Print(head Node) { 32 | fmt.Print(Sprint(head)) 33 | } 34 | 35 | // Sprint returns the formatted linked list. 36 | func Sprint(head Node) (s string) { 37 | data := []string{} 38 | for reflect.ValueOf(head) != reflect.Zero(reflect.TypeOf(head)) { 39 | data = append(data, fmt.Sprintf("%v", head.Data())) 40 | head = head.Next() 41 | } 42 | 43 | widths, maxDigitWidth := []int{}, digitWidth(len(data)) 44 | for _, d := range data { 45 | w := utf8.RuneCountInString(d) 46 | if w > maxDigitWidth { 47 | widths = append(widths, w) 48 | } else { 49 | widths = append(widths, maxDigitWidth) 50 | } 51 | } 52 | 53 | for _, w := range widths { 54 | s += BoxDownRight + strings.Repeat(BoxHor, w) + BoxDownLeft 55 | } 56 | s += "\n" 57 | 58 | s += BoxVer 59 | for i, d := range data { 60 | s += fmt.Sprintf("%*s", widths[i], d) 61 | if i == len(data)-1 { 62 | s += BoxVer + "\n" 63 | } else { 64 | s += BoxVerRight + BoxVerLeft 65 | } 66 | } 67 | 68 | for _, w := range widths { 69 | s += BoxVerRight + strings.Repeat(BoxHor, w) + BoxVerLeft 70 | } 71 | s += "\n" 72 | 73 | s += BoxVer 74 | for i, w := range widths { 75 | s += fmt.Sprintf("%*d", w, i+1) 76 | if i == len(widths)-1 { 77 | s += BoxVer + "\n" 78 | } else { 79 | s += BoxVer + BoxVer 80 | } 81 | } 82 | 83 | for _, w := range widths { 84 | s += BoxUpRight + strings.Repeat(BoxHor, w) + BoxUpLeft 85 | } 86 | s += "\n" 87 | 88 | return 89 | } 90 | 91 | func digitWidth(d int) (w int) { 92 | for d != 0 { 93 | d = d / 10 94 | w++ 95 | } 96 | return 97 | } 98 | -------------------------------------------------------------------------------- /list/list_test.go: -------------------------------------------------------------------------------- 1 | package list_test 2 | 3 | import ( 4 | "strconv" 5 | "testing" 6 | 7 | "github.com/shivamMg/ppds/list" 8 | ) 9 | 10 | type Node struct { 11 | data int 12 | next *Node 13 | } 14 | 15 | func (n *Node) Data() interface{} { 16 | return strconv.Itoa(n.data) 17 | } 18 | 19 | func (n *Node) Next() list.Node { 20 | return n.next 21 | } 22 | 23 | func TestPrint(t *testing.T) { 24 | n1 := Node{935, nil} 25 | n2 := Node{-10324, &n1} 26 | n3 := Node{3000000, &n2} 27 | want := `┌───────┐┌──────┐┌───┐ 28 | │3000000├┤-10324├┤935│ 29 | ├───────┤├──────┤├───┤ 30 | │ 1││ 2││ 3│ 31 | └───────┘└──────┘└───┘ 32 | ` 33 | got := list.Sprint(&n3) 34 | if want != got { 35 | t.Errorf("Expected:\n%s\n\nGot:\n%s\n", want, got) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /queue/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package queue provides implementation to get and print formatted queue. 3 | 4 | Example: 5 | 6 | import "github.com/shivamMg/ppds/queue" 7 | 8 | // a queue implementation. 9 | type myQueue struct { 10 | elems []int 11 | } 12 | 13 | func (q *myQueue) push(ele int) { 14 | q.elems = append([]int{ele}, q.elems...) 15 | } 16 | 17 | func (q *myQueue) pop() (int, bool) { 18 | if len(q.elems) == 0 { 19 | return 0, false 20 | } 21 | e := q.elems[len(q.elems)-1] 22 | q.elems = q.elems[:len(q.elems)-1] 23 | return e, true 24 | } 25 | 26 | // myQueue implements queue.Queue. Notice that the receiver is of *myQueue 27 | // type - since Push and Pop are required to modify q. 28 | func (q *myQueue) Push(ele interface{}) { 29 | q.push(ele.(int)) 30 | } 31 | 32 | func (q *myQueue) Pop() (interface{}, bool) { 33 | return q.pop() 34 | } 35 | 36 | // q := myQueue{} 37 | // q.push(11) 38 | // q.push(12) 39 | // queue.Print(&q) 40 | 41 | */ 42 | package queue 43 | -------------------------------------------------------------------------------- /queue/queue.go: -------------------------------------------------------------------------------- 1 | package queue 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "unicode/utf8" 7 | ) 8 | 9 | const ( 10 | Arrow = "→" 11 | BoxVer = "│" 12 | BoxHor = "─" 13 | BoxDownLeft = "┐" 14 | BoxDownRight = "┌" 15 | BoxUpLeft = "┘" 16 | BoxUpRight = "└" 17 | BoxVerLeft = "┤" 18 | BoxVerRight = "├" 19 | ) 20 | 21 | // Queue represents a queue of elements. 22 | type Queue interface { 23 | // Pop must pop and return the first element out of the queue. If queue is 24 | // empty ok should be false, else true. 25 | Pop() (ele interface{}, ok bool) 26 | // Push must insert ele in the queue. Since ele is of interface type, type 27 | // assertion must be done before inserting in the queue. 28 | Push(ele interface{}) 29 | } 30 | 31 | // Print prints the formatted queue to standard output. 32 | func Print(q Queue) { 33 | fmt.Print(Sprint(q)) 34 | } 35 | 36 | // Sprint returns the formatted queue. 37 | func Sprint(q Queue) (s string) { 38 | elems := []interface{}{} 39 | e, ok := q.Pop() 40 | for ok { 41 | elems = append(elems, e) 42 | e, ok = q.Pop() 43 | } 44 | 45 | for _, e := range elems { 46 | q.Push(e) 47 | } 48 | 49 | maxDigitWidth := digitWidth(len(elems)) 50 | data, widths := []string{}, []int{} 51 | for i := len(elems) - 1; i >= 0; i-- { 52 | d := fmt.Sprintf("%v", elems[i]) 53 | data = append(data, d) 54 | w := utf8.RuneCountInString(d) 55 | if w > maxDigitWidth { 56 | widths = append(widths, w) 57 | } else { 58 | widths = append(widths, maxDigitWidth) 59 | } 60 | } 61 | 62 | for _, w := range widths { 63 | s += " " + BoxDownRight + strings.Repeat(BoxHor, w) + BoxDownLeft 64 | } 65 | s += "\n" 66 | 67 | s += Arrow + BoxVer 68 | for i, d := range data { 69 | s += fmt.Sprintf("%*s", widths[i], d) 70 | if i == len(data)-1 { 71 | s += BoxVer + Arrow + "\n" 72 | } else { 73 | s += BoxVer + Arrow + BoxVer 74 | } 75 | } 76 | 77 | for _, w := range widths { 78 | s += " " + BoxVerRight + strings.Repeat(BoxHor, w) + BoxVerLeft 79 | } 80 | s += "\n" 81 | 82 | s += " " + BoxVer 83 | for i, w := range widths { 84 | s += fmt.Sprintf("%*d", w, i+1) 85 | if i == len(widths)-1 { 86 | s += BoxVer + "\n" 87 | } else { 88 | s += BoxVer + " " + BoxVer 89 | } 90 | } 91 | 92 | for _, w := range widths { 93 | s += " " + BoxUpRight + strings.Repeat(BoxHor, w) + BoxUpLeft 94 | } 95 | s += "\n" 96 | 97 | return 98 | } 99 | 100 | func digitWidth(d int) (w int) { 101 | for d != 0 { 102 | d = d / 10 103 | w++ 104 | } 105 | return 106 | } 107 | -------------------------------------------------------------------------------- /queue/queue_test.go: -------------------------------------------------------------------------------- 1 | package queue_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/shivamMg/ppds/queue" 7 | ) 8 | 9 | type myQueue struct { 10 | elems []int 11 | } 12 | 13 | func (q *myQueue) push(ele int) { 14 | q.elems = append([]int{ele}, q.elems...) 15 | } 16 | 17 | func (q *myQueue) pop() (int, bool) { 18 | if len(q.elems) == 0 { 19 | return 0, false 20 | } 21 | e := q.elems[len(q.elems)-1] 22 | q.elems = q.elems[:len(q.elems)-1] 23 | return e, true 24 | } 25 | 26 | func (q *myQueue) Push(ele interface{}) { 27 | q.push(ele.(int)) 28 | } 29 | 30 | func (q *myQueue) Pop() (interface{}, bool) { 31 | return q.pop() 32 | } 33 | 34 | func TestSprint(t *testing.T) { 35 | q := myQueue{} 36 | q.push(10) 37 | q.push(11) 38 | q.push(12) 39 | q.push(13) 40 | want := ` ┌──┐ ┌──┐ ┌──┐ ┌──┐ 41 | →│13│→│12│→│11│→│10│→ 42 | ├──┤ ├──┤ ├──┤ ├──┤ 43 | │ 1│ │ 2│ │ 3│ │ 4│ 44 | └──┘ └──┘ └──┘ └──┘ 45 | ` 46 | got := queue.Sprint(&q) 47 | if got != want { 48 | t.Errorf("Expected:\n%s\n\nGot:\n%s\n", want, got) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /stack/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package stack provides implementation to get and print formatted stack. 3 | 4 | Example: 5 | 6 | import "github.com/shivamMg/ppds/stack" 7 | 8 | // a stack implementation. 9 | type myStack struct { 10 | elems []int 11 | } 12 | 13 | func (s *myStack) push(ele int) { 14 | s.elems = append(s.elems, ele) 15 | } 16 | 17 | func (s *myStack) pop() (int, bool) { 18 | l := len(s.elems) 19 | if l == 0 { 20 | return 0, false 21 | } 22 | ele := s.elems[l-1] 23 | s.elems = s.elems[:l-1] 24 | return ele, true 25 | } 26 | 27 | // myStack implements stack.Stack. Notice that the receiver is of *myStack 28 | // type - since Push and Pop are required to modify s. 29 | func (s *myStack) Pop() (interface{}, bool) { 30 | return s.pop() 31 | } 32 | 33 | func (s *myStack) Push(ele interface{}) { 34 | s.push(ele.(int)) 35 | } 36 | 37 | // s := myStack{} 38 | // s.push(11) 39 | // s.push(12) 40 | // stack.Print(&s) 41 | 42 | */ 43 | package stack 44 | -------------------------------------------------------------------------------- /stack/stack.go: -------------------------------------------------------------------------------- 1 | package stack 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "unicode/utf8" 7 | ) 8 | 9 | const ( 10 | BoxVer = "│" 11 | BoxHor = "─" 12 | BoxUpHor = "┴" 13 | BoxUpLeft = "┘" 14 | BoxUpRight = "└" 15 | ) 16 | 17 | // Stack represents a stack of elements. 18 | type Stack interface { 19 | // Pop must pop the top element out of the stack and return it. In case of 20 | // an empty stack, ok should be false, else true. 21 | Pop() (ele interface{}, ok bool) 22 | // Push must insert an element in the stack. Since ele is of interface type, 23 | // type assertion must be done before inserting in the stack. 24 | Push(ele interface{}) 25 | } 26 | 27 | // Print prints the formatted stack to standard output. 28 | func Print(s Stack) { 29 | fmt.Print(Sprint(s)) 30 | } 31 | 32 | // Sprint returns the formatted stack. 33 | func Sprint(s Stack) (str string) { 34 | elems := []interface{}{} 35 | e, ok := s.Pop() 36 | for ok { 37 | elems = append(elems, e) 38 | e, ok = s.Pop() 39 | } 40 | 41 | for i := len(elems) - 1; i >= 0; i-- { 42 | s.Push(elems[i]) 43 | } 44 | 45 | data, maxWidth := []string{}, 0 46 | for _, e := range elems { 47 | d := fmt.Sprintf("%v", e) 48 | data = append(data, d) 49 | w := utf8.RuneCountInString(d) 50 | if w > maxWidth { 51 | maxWidth = w 52 | } 53 | } 54 | // position column maxWidth 55 | posW := digitWidth(len(data)) 56 | 57 | for i, d := range data { 58 | str += fmt.Sprintf("%s %*s %s%*d%s\n", BoxVer, maxWidth, d, BoxVer, posW, len(data)-i, BoxVer) 59 | } 60 | str += fmt.Sprint(BoxUpRight, strings.Repeat("─", maxWidth+2), BoxUpHor, strings.Repeat("─", posW), BoxUpLeft, "\n") 61 | 62 | return 63 | } 64 | 65 | func digitWidth(d int) (w int) { 66 | for d != 0 { 67 | d = d / 10 68 | w++ 69 | } 70 | return 71 | } 72 | -------------------------------------------------------------------------------- /stack/stack_test.go: -------------------------------------------------------------------------------- 1 | package stack_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/shivamMg/ppds/stack" 7 | ) 8 | 9 | type myStack struct { 10 | elems []int 11 | } 12 | 13 | func (s *myStack) pop() (int, bool) { 14 | l := len(s.elems) 15 | if l == 0 { 16 | return 0, false 17 | } 18 | ele := s.elems[l-1] 19 | s.elems = s.elems[:l-1] 20 | return ele, true 21 | } 22 | 23 | func (s *myStack) push(ele int) { 24 | s.elems = append(s.elems, ele) 25 | } 26 | 27 | func (s *myStack) Pop() (interface{}, bool) { 28 | return s.pop() 29 | } 30 | 31 | func (s *myStack) Push(ele interface{}) { 32 | s.push(ele.(int)) 33 | } 34 | 35 | func TestSprint(t *testing.T) { 36 | s := myStack{} 37 | s.push(10) 38 | s.push(199) 39 | s.push(1111) 40 | s.push(1111) 41 | s.push(12111) 42 | s.push(-113333111) 43 | s.push(1111) 44 | s.push(19) 45 | s.push(199) 46 | s.push(199) 47 | s.push(121499) 48 | s.push(-19019) 49 | want := `│ -19019 │12│ 50 | │ 121499 │11│ 51 | │ 199 │10│ 52 | │ 199 │ 9│ 53 | │ 19 │ 8│ 54 | │ 1111 │ 7│ 55 | │ -113333111 │ 6│ 56 | │ 12111 │ 5│ 57 | │ 1111 │ 4│ 58 | │ 1111 │ 3│ 59 | │ 199 │ 2│ 60 | │ 10 │ 1│ 61 | └────────────┴──┘ 62 | ` 63 | got := stack.Sprint(&s) 64 | if got != want { 65 | t.Errorf("Expected:\n%s\n\nGot:\n%s\n", want, got) 66 | } 67 | } 68 | 69 | type myStack2 struct { 70 | elems []string 71 | } 72 | 73 | func (s *myStack2) pop() (string, bool) { 74 | l := len(s.elems) 75 | if l == 0 { 76 | return "", false 77 | } 78 | ele := s.elems[l-1] 79 | s.elems = s.elems[:l-1] 80 | return ele, true 81 | } 82 | 83 | func (s *myStack2) push(ele string) { 84 | s.elems = append(s.elems, ele) 85 | } 86 | 87 | func (s *myStack2) Pop() (interface{}, bool) { 88 | return s.pop() 89 | } 90 | 91 | func (s *myStack2) Push(ele interface{}) { 92 | s.push(ele.(string)) 93 | } 94 | 95 | func TestRunes(t *testing.T) { 96 | s := myStack2{} 97 | s.push("你好,世界") 98 | s.push("Hello world") 99 | s.push("Ciao mondo") 100 | s.push("नमस्ते दुनिया") 101 | want := `│ नमस्ते दुनिया │4│ 102 | │ Ciao mondo │3│ 103 | │ Hello world │2│ 104 | │ 你好,世界 │1│ 105 | └───────────────┴─┘ 106 | ` 107 | got := stack.Sprint(&s) 108 | if got != want { 109 | t.Errorf("Expected:\n%s\n\nGot:\n%s\n", want, got) 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /tree/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package tree provides implementation to get and print formatted tree. 3 | 4 | Example: 5 | 6 | import "github.com/shivamMg/ppds/tree" 7 | 8 | // a tree node. 9 | type Node struct { 10 | data int 11 | children []*Node 12 | } 13 | 14 | func (n *Node) Data() interface{} { 15 | return strconv.Itoa(n.data) 16 | } 17 | 18 | // cannot return n.children directly. 19 | // https://github.com/golang/go/wiki/InterfaceSlice 20 | func (n *Node) Children() (c []tree.Node) { 21 | for _, child := range n.children { 22 | c = append(c, tree.Node(child)) 23 | } 24 | return 25 | } 26 | 27 | // n1, n2 := Node{data: "b"}, Node{data: "c"} 28 | // n3 := Node{"a", []*Node{&n1, &n2}} 29 | // tree.Print(&n3) 30 | 31 | */ 32 | package tree 33 | -------------------------------------------------------------------------------- /tree/htree.go: -------------------------------------------------------------------------------- 1 | package tree 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "unicode/utf8" 7 | ) 8 | 9 | // PrintHr prints the horizontal formatted tree to standard output. 10 | func PrintHr(root Node) { 11 | fmt.Print(SprintHr(root)) 12 | } 13 | 14 | // SprintHr returns the horizontal formatted tree. 15 | func SprintHr(root Node) (s string) { 16 | for _, line := range lines(root) { 17 | // ignore runes before root node 18 | line = string([]rune(line)[2:]) 19 | s += strings.TrimRight(line, " ") + "\n" 20 | } 21 | return 22 | } 23 | 24 | func lines(root Node) (s []string) { 25 | data := fmt.Sprintf("%s %v ", BoxHor, root.Data()) 26 | l := len(root.Children()) 27 | if l == 0 { 28 | s = append(s, data) 29 | return 30 | } 31 | 32 | w := utf8.RuneCountInString(data) 33 | for i, c := range root.Children() { 34 | for j, line := range lines(c) { 35 | if i == 0 && j == 0 { 36 | if l == 1 { 37 | s = append(s, data+BoxHor+line) 38 | } else { 39 | s = append(s, data+BoxDownHor+line) 40 | } 41 | continue 42 | } 43 | 44 | var box string 45 | if i == l-1 && j == 0 { 46 | // first line of the last child 47 | box = BoxUpRight 48 | } else if i == l-1 { 49 | box = " " 50 | } else if j == 0 { 51 | box = BoxVerRight 52 | } else { 53 | box = BoxVer 54 | } 55 | s = append(s, strings.Repeat(" ", w)+box+line) 56 | } 57 | } 58 | return 59 | } 60 | 61 | // PrintHrn prints the horizontal-newline formatted tree to standard output. 62 | func PrintHrn(root Node) { 63 | fmt.Print(SprintHrn(root)) 64 | } 65 | 66 | // SprintHrn returns the horizontal-newline formatted tree. 67 | func SprintHrn(root Node) (s string) { 68 | return strings.Join(lines2(root), "\n") + "\n" 69 | } 70 | 71 | func lines2(root Node) (s []string) { 72 | s = append(s, fmt.Sprintf("%v", root.Data())) 73 | l := len(root.Children()) 74 | if l == 0 { 75 | return 76 | } 77 | 78 | for i, c := range root.Children() { 79 | for j, line := range lines2(c) { 80 | // first line of the last child 81 | if i == l-1 && j == 0 { 82 | s = append(s, BoxUpRight+BoxHor+" "+line) 83 | } else if j == 0 { 84 | s = append(s, BoxVerRight+BoxHor+" "+line) 85 | } else if i == l-1 { 86 | s = append(s, " "+line) 87 | } else { 88 | s = append(s, BoxVer+" "+line) 89 | } 90 | } 91 | } 92 | return 93 | } 94 | -------------------------------------------------------------------------------- /tree/tree.go: -------------------------------------------------------------------------------- 1 | package tree 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "strings" 7 | "unicode/utf8" 8 | ) 9 | 10 | const ( 11 | BoxVer = "│" 12 | BoxHor = "─" 13 | BoxVerRight = "├" 14 | BoxDownLeft = "┐" 15 | BoxDownRight = "┌" 16 | BoxDownHor = "┬" 17 | BoxUpRight = "└" 18 | // Gutter is number of spaces between two adjacent child nodes. 19 | Gutter = 2 20 | ) 21 | 22 | // ErrDuplicateNode indicates that a duplicate Node (node with same hash) was 23 | // encountered while going through the tree. As of now Sprint/Print and 24 | // SprintWithError/PrintWithError cannot operate on such trees. 25 | // 26 | // This error is returned by SprintWithError/PrintWithError. It's also used 27 | // in Sprint/Print as error for panic for the same case. 28 | // 29 | // FIXME: create internal representation of trees that copies data 30 | var ErrDuplicateNode = errors.New("duplicate node") 31 | 32 | // Node represents a node in a tree. Type that satisfies Node must be a hashable type. 33 | type Node interface { 34 | // Data must return a value representing the node. It is stringified using "%v". 35 | // If empty, a space is used. 36 | Data() interface{} 37 | // Children must return a list of all child nodes of the node. 38 | Children() []Node 39 | } 40 | 41 | type queue struct { 42 | arr []Node 43 | } 44 | 45 | func (q queue) empty() bool { 46 | return len(q.arr) == 0 47 | } 48 | 49 | func (q queue) len() int { 50 | return len(q.arr) 51 | } 52 | 53 | func (q *queue) push(n Node) { 54 | q.arr = append(q.arr, n) 55 | } 56 | 57 | func (q *queue) pop() Node { 58 | if q.empty() { 59 | return nil 60 | } 61 | ele := q.arr[0] 62 | q.arr = q.arr[1:] 63 | return ele 64 | } 65 | 66 | func (q *queue) peek() Node { 67 | if q.empty() { 68 | return nil 69 | } 70 | return q.arr[0] 71 | } 72 | 73 | // Print prints the formatted tree to standard output. To handle ErrDuplicateNode use PrintWithError. 74 | func Print(root Node) { 75 | fmt.Print(Sprint(root)) 76 | } 77 | 78 | // Sprint returns the formatted tree. To handle ErrDuplicateNode use SprintWithError. 79 | func Sprint(root Node) string { 80 | parents := map[Node]Node{} 81 | if err := setParents(parents, root); err != nil { 82 | panic(err) 83 | } 84 | return sprint(parents, root) 85 | } 86 | 87 | // PrintWithError prints the formatted tree to standard output. 88 | func PrintWithError(root Node) error { 89 | s, err := SprintWithError(root) 90 | if err != nil { 91 | return err 92 | } 93 | fmt.Print(s) 94 | return nil 95 | } 96 | 97 | // SprintWithError returns the formatted tree. 98 | func SprintWithError(root Node) (string, error) { 99 | parents := map[Node]Node{} 100 | if err := setParents(parents, root); err != nil { 101 | return "", err 102 | } 103 | return sprint(parents, root), nil 104 | } 105 | 106 | func sprint(parents map[Node]Node, root Node) string { 107 | isLeftMostChild := func(n Node) bool { 108 | p, ok := parents[n] 109 | if !ok { 110 | // root 111 | return true 112 | } 113 | return p.Children()[0] == n 114 | } 115 | 116 | paddings := map[Node]int{} 117 | setPaddings(paddings, map[Node]int{}, 0, root) 118 | 119 | q := queue{} 120 | q.push(root) 121 | lines := []string{} 122 | for !q.empty() { 123 | // line storing branches, and line storing nodes 124 | branches, nodes := "", "" 125 | // runes covered 126 | covered := 0 127 | qLen := q.len() 128 | for i := 0; i < qLen; i++ { 129 | n := q.pop() 130 | for _, c := range n.Children() { 131 | q.push(c) 132 | } 133 | 134 | spaces := paddings[n] - covered 135 | data := safeData(n) 136 | nodes += strings.Repeat(" ", spaces) + data 137 | 138 | w := utf8.RuneCountInString(data) 139 | covered += spaces + w 140 | current, next := isLeftMostChild(n), isLeftMostChild(q.peek()) 141 | if current { 142 | branches += strings.Repeat(" ", spaces) 143 | } else { 144 | branches += strings.Repeat(BoxHor, spaces) 145 | } 146 | 147 | if current && next { 148 | branches += BoxVer 149 | } else if current { 150 | branches += BoxVerRight 151 | } else if next { 152 | branches += BoxDownLeft 153 | } else { 154 | branches += BoxDownHor 155 | } 156 | 157 | if next { 158 | branches += strings.Repeat(" ", w-1) 159 | } else { 160 | branches += strings.Repeat(BoxHor, w-1) 161 | } 162 | } 163 | lines = append(lines, branches, nodes) 164 | } 165 | 166 | s := "" 167 | // ignore first line since it's the branch above root 168 | for _, line := range lines[1:] { 169 | s += strings.TrimRight(line, " ") + "\n" 170 | 171 | } 172 | return s 173 | } 174 | 175 | // safeData always returns non-empty representation of n's data. Empty data 176 | // messes up tree structure, and ignoring such node will return incomplete 177 | // tree output (tree without an entire subtree). So it returns a space. 178 | func safeData(n Node) string { 179 | data := fmt.Sprintf("%v", n.Data()) 180 | if data == "" { 181 | return " " 182 | } 183 | return data 184 | } 185 | 186 | // setPaddings sets left padding (distance of a node from the root) 187 | // for each node in the tree. 188 | func setPaddings(paddings map[Node]int, widths map[Node]int, pad int, root Node) { 189 | for _, c := range root.Children() { 190 | paddings[c] = pad 191 | setPaddings(paddings, widths, pad, c) 192 | pad += width(widths, c) 193 | } 194 | } 195 | 196 | // setParents sets child-parent relationships for the tree rooted 197 | // at root. 198 | func setParents(parents map[Node]Node, root Node) error { 199 | for _, c := range root.Children() { 200 | if _, ok := parents[c]; ok { 201 | return ErrDuplicateNode 202 | } 203 | parents[c] = root 204 | if err := setParents(parents, c); err != nil { 205 | return err 206 | } 207 | } 208 | return nil 209 | } 210 | 211 | // width returns either the sum of widths of it's children or its own 212 | // data length depending on which one is bigger. widths is used in 213 | // memoization. 214 | func width(widths map[Node]int, n Node) int { 215 | if w, ok := widths[n]; ok { 216 | return w 217 | } 218 | 219 | w := utf8.RuneCountInString(safeData(n)) + Gutter 220 | widths[n] = w 221 | if len(n.Children()) == 0 { 222 | return w 223 | } 224 | 225 | sum := 0 226 | for _, c := range n.Children() { 227 | sum += width(widths, c) 228 | } 229 | if sum > w { 230 | widths[n] = sum 231 | return sum 232 | } 233 | return w 234 | } 235 | -------------------------------------------------------------------------------- /tree/tree_test.go: -------------------------------------------------------------------------------- 1 | package tree_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/shivamMg/ppds/tree" 7 | ) 8 | 9 | type Node struct { 10 | data string 11 | c []*Node 12 | } 13 | 14 | func (n Node) Data() interface{} { 15 | return n.data 16 | } 17 | 18 | func (n Node) Children() (children []tree.Node) { 19 | for _, c := range n.c { 20 | children = append(children, tree.Node(c)) 21 | } 22 | return 23 | } 24 | 25 | func TestSprint(t *testing.T) { 26 | n1 := Node{data: "Color"} 27 | n2, n3, n16 := Node{"Tom&Jer", []*Node{&n1}}, Node{data: "Cabbage"}, Node{data: "stoi_cism"} 28 | n4 := Node{"boom", []*Node{&n2, &n3, &n16}} 29 | n5, n6 := Node{data: "wage gap"}, Node{data: "Furry"} 30 | n7 := Node{"pqrs enactment", []*Node{&n5, &n6}} 31 | n8 := Node{data: "_rugs"} 32 | n9 := Node{"abcdef", []*Node{&n7, &n8, &n4}} 33 | 34 | n12 := Node{data: "Vintage"} 35 | n11 := Node{"Classic", []*Node{&n12}} 36 | 37 | n13, n14 := Node{data: "Private_"}, Node{data: "Public"} 38 | n15 := Node{"class", []*Node{&n13, &n14}} 39 | n10 := Node{"random", []*Node{&n11, &n9, &n15}} 40 | 41 | const want = `random 42 | ├────────┬────────────────────────────────────────────────────┐ 43 | Classic abcdef class 44 | │ ├────────────────┬──────┐ ├─────────┐ 45 | Vintage pqrs enactment _rugs boom Private_ Public 46 | ├─────────┐ ├────────┬────────┐ 47 | wage gap Furry Tom&Jer Cabbage stoi_cism 48 | │ 49 | Color 50 | ` 51 | 52 | got := tree.Sprint(&n10) 53 | if got != want { 54 | t.Errorf("Expected:\n%s\n\nGot:\n%s\n", want, got) 55 | } 56 | } 57 | 58 | func TestSprintAndSprintHr(t *testing.T) { 59 | var classical, popRock Node 60 | { 61 | n1, n2 := Node{data: "Light"}, Node{data: "Heavy"} 62 | n3, n4 := Node{data: "Piano"}, Node{"Orchestra", []*Node{&n1, &n2}} 63 | n5, n6 := Node{data: "Male"}, Node{data: "Female"} 64 | n7, n8 := Node{"Opera", []*Node{&n5, &n6}}, Node{data: "Chorus"} 65 | n9, n10 := Node{"Instrumental", []*Node{&n3, &n4}}, Node{"Vocal", []*Node{&n7, &n8}} 66 | classical = Node{"Classical", []*Node{&n9, &n10}} 67 | } 68 | { 69 | n3 := Node{data: "Heavy metal"} 70 | n4, n5 := Node{data: "Dancing"}, Node{data: "Soft"} 71 | n6, n7 := Node{"Rock", []*Node{&n3}}, Node{"Country", []*Node{&n4, &n5}} 72 | n8, n9 := Node{data: "Late pop"}, Node{data: "Disco"} 73 | n10, n11 := Node{data: "Soft techno"}, Node{data: "Hard techno"} 74 | n12, n13 := Node{"Pop", []*Node{&n8, &n9}}, Node{"Techno", []*Node{&n10, &n11}} 75 | n14, n15 := Node{"Organic", []*Node{&n6, &n7}}, Node{"Electronic", []*Node{&n12, &n13}} 76 | popRock = Node{"Pop/Rock", []*Node{&n14, &n15}} 77 | } 78 | music := Node{"Music", []*Node{&classical, &popRock}} 79 | 80 | want := `Music 81 | ├──────────────────────────────────────────┐ 82 | Classical Pop/Rock 83 | ├────────────────────┐ ├───────────────────────────┐ 84 | Instrumental Vocal Organic Electronic 85 | ├──────┐ ├─────────────┐ ├────────────┐ ├────────────────┐ 86 | Piano Orchestra Opera Chorus Rock Country Pop Techno 87 | ├──────┐ ├─────┐ │ ├────────┐ ├─────────┐ ├────────────┐ 88 | Light Heavy Male Female Heavy metal Dancing Soft Late pop Disco Soft techno Hard techno 89 | ` 90 | got := tree.Sprint(&music) 91 | if got != want { 92 | t.Errorf("Expected:\n%s\n\nGot:\n%s\n", want, got) 93 | } 94 | 95 | want = `Music ┬─ Classical ┬─ Instrumental ┬─ Piano 96 | │ │ └─ Orchestra ┬─ Light 97 | │ │ └─ Heavy 98 | │ └─ Vocal ┬─ Opera ┬─ Male 99 | │ │ └─ Female 100 | │ └─ Chorus 101 | └─ Pop/Rock ┬─ Organic ┬─ Rock ── Heavy metal 102 | │ └─ Country ┬─ Dancing 103 | │ └─ Soft 104 | └─ Electronic ┬─ Pop ┬─ Late pop 105 | │ └─ Disco 106 | └─ Techno ┬─ Soft techno 107 | └─ Hard techno 108 | ` 109 | got = tree.SprintHr(&music) 110 | if got != want { 111 | t.Errorf("Expected:\n%s\n\nGot:\n%s\n", want, got) 112 | } 113 | 114 | want = `Music 115 | ├─ Classical 116 | │ ├─ Instrumental 117 | │ │ ├─ Piano 118 | │ │ └─ Orchestra 119 | │ │ ├─ Light 120 | │ │ └─ Heavy 121 | │ └─ Vocal 122 | │ ├─ Opera 123 | │ │ ├─ Male 124 | │ │ └─ Female 125 | │ └─ Chorus 126 | └─ Pop/Rock 127 | ├─ Organic 128 | │ ├─ Rock 129 | │ │ └─ Heavy metal 130 | │ └─ Country 131 | │ ├─ Dancing 132 | │ └─ Soft 133 | └─ Electronic 134 | ├─ Pop 135 | │ ├─ Late pop 136 | │ └─ Disco 137 | └─ Techno 138 | ├─ Soft techno 139 | └─ Hard techno 140 | ` 141 | got = tree.SprintHrn(&music) 142 | if got != want { 143 | t.Errorf("Expected:\n%s\n\nGot:\n%s\n", want, got) 144 | } 145 | } 146 | 147 | func TestSprintEmptyData(t *testing.T) { 148 | n2, n4, n5 := Node{data: "node2"}, Node{data: "node4"}, Node{data: "node5"} 149 | n3 := Node{data: "", c: []*Node{&n4}} 150 | n1 := Node{"node1", []*Node{&n2, &n3, &n5}} 151 | 152 | const want = `node1 153 | ├──────┬──────┐ 154 | node2 node5 155 | │ 156 | node4 157 | ` 158 | 159 | got := tree.Sprint(&n1) 160 | if got != want { 161 | t.Errorf("Expected:\n%s\n\nGot:\n%s\n", want, got) 162 | } 163 | } 164 | 165 | func TestSprintAndSprintWithErrorDuplicateNode(t *testing.T) { 166 | n2 := Node{data: "a"} 167 | n1 := Node{data: "b", c: []*Node{&n2, &n2}} 168 | 169 | _, err := tree.SprintWithError(&n1) 170 | if err != tree.ErrDuplicateNode { 171 | t.Errorf("Expected:\n%s\n\nGot:\n%s\n", tree.ErrDuplicateNode, err) 172 | } 173 | 174 | defer func() { 175 | r := recover() 176 | if r != tree.ErrDuplicateNode { 177 | t.Errorf("Expected:\n%s\n\nGot:\n%s\n", tree.ErrDuplicateNode, r) 178 | } 179 | }() 180 | tree.Sprint(&n1) 181 | } 182 | --------------------------------------------------------------------------------