diff --git a/iter/iter.go b/iter/iter.go index 62fbc0e..e026245 100644 --- a/iter/iter.go +++ b/iter/iter.go @@ -80,3 +80,24 @@ func Max[T constraints.Ordered](src Able[T]) T { } return max } + +// Discarded Iterator types: +// +// This was my first attempt. I like to have a method that returns a bool so +// you can use it succinctly in a for loop, but I didn't love having to call +// both done and next +// +// type Ator[T any] interface { +// Done() bool +// Next() T +// } +// +// I was never optimistic about this. In practice, using this in a for loop is +// just annoying so I stopped doing it. But also there's another thing I find +// annoying: it's a copy every time. You're not really iterating over the +// values, you're iterating over copies of the values, it seemed like a lot of +// unecessary copying. +// +// type Ator[T any] interface { +// Next() (T, bool) +// } diff --git a/iter/slice.go b/iter/slice.go new file mode 100644 index 0000000..0ea82b7 --- /dev/null +++ b/iter/slice.go @@ -0,0 +1,26 @@ +package iter + +import "constraints" + +type slice[T any] []T + +type sliceIter[T any] struct { + s slice[T] + i int +} + +func (it *sliceIter[T]) Next(v *T) bool { + if it.i >= len(it.s) { + return false + } + *v = it.s[it.i] + it.i++ + return true +} + +func (it *sliceIter[T]) Iter() Ator[T] { return &sliceIter[T]{s: it.s} } + +func (s slice[T]) Iter() Ator[T] { return &sliceIter[T]{s: s} } + +// Slice takes a slice and returns an iterable backed by that slice +func Slice[T constraints.Integer](s []T) Able[T] { return slice[T](s) } diff --git a/list/iter.go b/list/iter.go index e1749c2..28a0fb6 100644 --- a/list/iter.go +++ b/list/iter.go @@ -1,15 +1,15 @@ package list -type Iterable[T any] interface { - Iter() Iter[T] -} - -type Iter[T any] interface { - Next(*T) bool -} +// type Iterable[T any] interface { +// Iter() Iter[T] +// } +// +// type Iter[T any] interface { +// Next(*T) bool +// } // for v, it := iter.Ate(foods); it.Next(&v); { -// +// // } // This actually works, but for some reason I don't yet understand, defining @@ -20,7 +20,7 @@ type Iter[T any] interface { // // func Max[T constraints.Ordered](l Iterable[T]) T { // it := l.Iter() -// +// // var v T // if !it.Next(&v) { // return v @@ -34,7 +34,6 @@ type Iter[T any] interface { // return max // } - // Discarded Iterator types: // // This was my first attempt. I like to have a method that returns a bool so diff --git a/list/list.go b/list/list.go index 4f01bd5..bb99ea4 100644 --- a/list/list.go +++ b/list/list.go @@ -5,6 +5,8 @@ import ( "sync" "constraints" "fmt" + + "github.com/jordanorelli/generic/iter" ) type node[T any] struct { @@ -41,9 +43,6 @@ func Make[T any](vals ...T) List[T] { return l } -// func From[T any](it iter.Able) List[T] { -// } - // Empty is true for empty lists func (l List[T]) Empty() bool { return l.head == nil @@ -93,7 +92,9 @@ func (l List[T]) Head() T { // 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. +// empty list. Note that Tail creates a new list that is backed by the same +// elements as the old list; mutations on the origin list are visible in the +// tail and vice-versa. func (l List[T]) Tail() List[T] { if l.head == nil || l.head.next == nil { return List[T]{} @@ -114,13 +115,13 @@ func (l List[T]) Len() int { return i } -type iter[T any] struct { +type _iter[T any] struct { n *node[T] } -func (i iter[T]) Done() bool { return i.n == nil } +func (i _iter[T]) Done() bool { return i.n == nil } -func (i *iter[T]) Next(dest *T) bool { +func (i *_iter[T]) Next(dest *T) bool { if i.n == nil { return false } @@ -130,9 +131,9 @@ func (i *iter[T]) Next(dest *T) bool { return true } -func (l List[T]) Iter() Iter[T] { - return &iter[T]{n: l.head} -} +func (i *_iter[T]) Iter() iter.Ator[T] { return &_iter[T]{n: i.n} } + +func (l List[T]) Iter() iter.Ator[T] { return &_iter[T]{n: l.head} } func Max[T constraints.Ordered](l List[T]) T { if l.Empty() { @@ -149,45 +150,81 @@ func Max[T constraints.Ordered](l List[T]) T { return v } +// Map exists as a method to permit chaining in the event that your input +// function maps T -> T. Since methods cannot have type parameters, mapping a +// function that transforms T -> Z is not possible as a method. +func (l List[T]) Map(f func(T) T) List[T] { return Map(l, f) } + // 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] { +func Map[T any, Z any](l List[T], f func(T) Z) List[Z] { if l.Empty() { - return List[T]{} + var empty List[Z] + return empty } - mapped := List[T]{head: &node[T]{val: f(l.head.val)}} + mapped := List[Z]{head: &node[Z]{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.next = &node[Z]{val: f(n.val)} last = last.next } return mapped } +type numbered[T any] struct { + val T + i int +} + +func waitNClose[T any](wg *sync.WaitGroup, c chan T) { + wg.Wait() + close(c) +} + // Run is the same as Map, but is run concurrently. The function f will be run -// for every element of l in its own goroutine. +// for every element of l in its own goroutine. The results of running f on +// each of the inputs will be stored into a new list in an order-preserving +// manner. func Run[T any, Z any](l List[T], f func(T) Z) List[Z] { + if l.Empty() { + var empty List[Z] + return empty + } + + // surprise: type declarations are not allowed inside of generic functions + // + // type numbered[T any] struct { + // val T + // i int + // } + var wg sync.WaitGroup - c := make(chan Z) + c := make(chan numbered[Z]) - for n := l.head; n != nil; n = n.next { + i := 0 + for n := l.head; n != nil; n = n.next{ wg.Add(1) - go func(v T) { + go func(v T, i int) { defer wg.Done() - c <- f(v) - }(n.val) + c <- numbered[Z]{val: f(v), i: i} + }(n.val, i) + i++ } - go func() { - wg.Wait() - close(c) - }() + mem := make([]Z, i) + go waitNClose(&wg, c) + for z := range c { + mem[z.i] = z.val + } var results List[Z] - for z := range c { - results.Push(z) + for i, _ := range mem { + results.head = &node[Z]{ + val: mem[i], + next: results.head, + } } return results } @@ -218,3 +255,23 @@ func (l List[T]) Filter(f func(T) bool) List[T] { return passed } + +type Pair[T any, Z any] struct { + Left T + Right Z +} + +// Zip takes two lists and joins them to create a list of pairs. It's the same +// as the python zip function, and totally stupid and Pair should not be in +// this package but I'm testing the iterable interfaces and this shows they are +// good, actually +func Zip[T any, Z any](left List[T], right List[Z]) List[Pair[T, Z]] { + lit, rit := left.Iter(), right.Iter() + var out List[Pair[T, Z]] + + var next Pair[T, Z] + for lit.Next(&next.Left) && rit.Next(&next.Right) { + out.head = &node[Pair[T, Z]]{val: next, next: out.head} + } + return out +} diff --git a/list/list_test.go b/list/list_test.go index 09ad424..4c53630 100644 --- a/list/list_test.go +++ b/list/list_test.go @@ -2,6 +2,7 @@ package list import ( "testing" + "time" ) func TestEmpty(t *testing.T) { @@ -124,7 +125,18 @@ func TestMap(t *testing.T) { eq(t, 0, nums.At(3)) eq(t, 30, Max(nums)) +} + +func TestRun(t *testing.T) { + sleep := func(n int) time.Duration { + dur := time.Duration(n) * time.Millisecond + time.Sleep(dur) + return dur + } + l := Make(1, 2, 3, 4, 5, 4, 3, 2, 1) + durs := Run(l, sleep) + t.Logf("%v", durs) } func TestIter(t *testing.T) { diff --git a/span/span.go b/span/span.go index 259eb39..2b9bff7 100644 --- a/span/span.go +++ b/span/span.go @@ -6,6 +6,7 @@ import ( "github.com/jordanorelli/generic/iter" ) +// Span represents some range of integers type Span[T constraints.Integer] struct { Start T End T diff --git a/span/span_test.go b/span/span_test.go index 95b3466..dabbb99 100644 --- a/span/span_test.go +++ b/span/span_test.go @@ -24,7 +24,7 @@ func TestSpan(t *testing.T) { // can be inferred, but when New returns a value of Span[T] (which // satisfies iter.Able[T]), the type parameter cannot be inferred. I don't // know why this behavior exists or if this is the intended behavior. - // + + // | // | // V beer := iter.Max[int](New(1, 100)) @@ -37,6 +37,20 @@ func TestSpan(t *testing.T) { t.Errorf("expected 30 to be old but saw %d instead", old) } + // honestly a very ridiculous use-case, but I'm looking at some weird + // inferrence corner cases. It seems the third parameter can't be the + // literal 2, you have to explicitly type it. + alpha := Step('a', 'z'+1, rune(2)) + + // the rune type can't be inferred here either + for a, it := iter.Start[rune](alpha); it.Next(&a); { + t.Logf("%c", a) + } + + // the rune type can't be inferred here either! + for a, it := iter.Start[rune](alpha.Iter()); it.Next(&a); { + } + t.Logf("%T", iter.Max[int8](New[int8](3, 10))) }