From 82696bb29acc1edd84344fbdbf0036bcf4de6437 Mon Sep 17 00:00:00 2001 From: Jordan Orelli Date: Sun, 21 Nov 2021 20:34:28 -0600 Subject: [PATCH] dunno how to feeeeeeeeeel --- go.mod | 3 ++ list/list.go | 117 ++++++++++++++++++++++++++++++++++++++++++++++ list/list_test.go | 111 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 231 insertions(+) create mode 100644 go.mod create mode 100644 list/list.go create mode 100644 list/list_test.go diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..52fab91 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/jordanorelli/generic + +go 1.18 diff --git a/list/list.go b/list/list.go new file mode 100644 index 0000000..bcb8581 --- /dev/null +++ b/list/list.go @@ -0,0 +1,117 @@ +package list + +import ( + "strings" + "fmt" +) + +type node[T any] struct { + val T + next *node[T] +} + +// List[T] is a singly-linked list of T +type List[T any] struct { + head *node[T] +} + +func (l List[T]) String() string { + var buf strings.Builder + + buf.WriteRune('[') + for n := l.head; n != nil; n = n.next { + fmt.Fprintf(&buf, "%v", n.val) + if n.next != nil { + buf.WriteString(", ") + } + } + buf.WriteRune(']') + return buf.String() +} + +// Make creates a list of T with a set of provided values. It's called Make +// instead of New because it performs O(n) allocations, where n == len(vals) +func Make[T any](vals ...T) List[T] { + var l List[T] + for i := len(vals)-1; i >= 0; i-- { + l.Prepend(vals[i]) + } + return l +} + +// Empty is true for empty lists +func (l List[T]) Empty() bool { + return l.head == nil +} + +// Head returns the first element of the list. If the list is empty, Head +// returns the zero-value for the type T. This is the same thing as Peek() +func (l List[T]) Head() T { + if l.head == nil { + var v T + return v + } + return l.head.val +} + +// Pop returns the first element of the list and removes it from the list. +func (l *List[T]) Pop() T { + if l.Empty() { + var v T + return v + } + + v := l.head.val + l.head = l.head.next + return v +} + +// Tail returns a list which is the original list without its Head element. +// If the original list is an empty list or a list of size 1, Tail is an +// empty list. +func (l List[T]) Tail() List[T] { + if l.head == nil || l.head.next == nil { + return List[T]{} + } + return List[T]{head: l.head.next} +} + +// Len is the length of the list +func (l List[T]) Len() int { + if l.head == nil { + return 0 + } + + i := 0 + for n := l.head; n != nil; n = n.next { + i++ + } + return i +} + +// Prepend adds an element to the front of the list +func (l *List[T]) Prepend(v T) { + l.head = &node[T]{ + val: v, + next: l.head, + } +} + +// Map applies the input function f to each element of the list l, returning a +// new list containing the values produced by f +func (l List[T]) Map(f func(T) T) List[T] { + var mapped List[T] + + if l.Empty() { + return mapped + } + + mapped.head = &node[T]{val: f(l.head.val)} + last := mapped.head + for n := l.head.next; n != nil; n = n.next { + last.next = &node[T]{val: f(n.val)} + last = last.next + } + + return mapped +} diff --git a/list/list_test.go b/list/list_test.go new file mode 100644 index 0000000..b9e8136 --- /dev/null +++ b/list/list_test.go @@ -0,0 +1,111 @@ +package list + +import ( + "testing" +) + +func TestEmpty(t *testing.T) { + var l List[int] + + if !l.Empty() { + t.Errorf("new list is not empty, but should be") + } + + if l.Head() != 0 { + t.Errorf("expect head of list to be zero-value for that type but saw %d instead of 0 for int", l.Head()) + } + + tail := l.Tail() + if !tail.Empty() { + t.Errorf("empty list should have empty tail but saw %v instead", tail) + } + + if l.Len() != 0 { + t.Errorf("empty list should have a length of 0 but has %d instead", l.Len()) + } +} + +func TestOne(t *testing.T) { + var l List[int] + + l.Prepend(3) + + if l.Empty() { + t.Errorf("list should have 1 element but is empty") + } + + if l.Head() != 3 { + t.Errorf("list's head element should be 3 but saw %d instead", l.Head()) + } + + if l.Len() != 1 { + t.Errorf("expected a list of size 1 but saw %d instead", l.Len()) + } +} + +func TestMake(t *testing.T) { + t.Run("empty", func(t *testing.T) { + // This doesn't work because the type parameter T cannot be inferred: + // Make[]() + + // You have to provide T in the case of an empty list + l := Make[int]() + if !l.Empty() { + t.Errorf("make with no params should create empty list but gave %v instead", l) + } + }) + + t.Run("some strings", func(t *testing.T) { + // The type parameter T can be inferred based on the arguments passed + // in + l := Make("bob", "carol", "dave") + + if l.Len() != 3 { + t.Errorf("expected length of 3 but saw %d instead", l.Len()) + } + + if l.Head() != "bob" { + t.Errorf("expected a head element of %q but saw %q instead", "bob", l.Head()) + } + + l.Prepend("alice") + if l.Head() != "alice" { + t.Errorf("expected a head element of %q but saw %q instead", "alice", l.Head()) + } + if l.Len() != 4 { + t.Errorf("expected length of 4 but saw %d instead", l.Len()) + } + }) + + t.Run("mixed element types", func(t *testing.T) { + l := Make[any]("alice", 3, "carol") + + if l.Len() != 3 { + t.Errorf("expected length of 3 but saw %d instead", l.Len()) + } + }) +} + +func mult[T wholeNumber](x T) func(T) T { + return func(y T) T { + return x * y + } +} + +type wholeNumber interface { + ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uint | + ~int8 | ~int16 | ~int32 | ~int64 | int +} + +func eq[T comparable](t *testing.T, expect T, found T) { + if found != expect { + t.Errorf("expected %v, found %v", expect, found) + } +} + +func TestMap(t *testing.T) { + nums := Make(2, 4, 6).Map(mult(5)) + eq(t, 10, nums.Pop()) + eq(t, 20, nums.Pop()) + eq(t, 30, nums.Pop()) +}