Initial Commit

This commit is contained in:
2016-08-13 18:20:14 -05:00
commit 50f4a86fd8
408 changed files with 15301 additions and 0 deletions

32
go/react/README.md Normal file
View File

@@ -0,0 +1,32 @@
# React
Implement a basic reactive system.
Reactive programming is a programming paradigm that focuses on how values
are computed in terms of each other to allow a change to one value to
automatically propagate to other values, like in a spreadsheet.
Implement a basic reactive system with cells with settable values ("input"
cells) and cells with values computed in terms of other cells ("compute"
cells). Implement updates so that when an input value is changed, values
propagate to reach a new stable system state.
In addition, compute cells should allow for registering change notification
callbacks. Call a cells callbacks when the cells value in a new stable
state has changed from the previous stable state.
To run the tests simply run the command `go test` in the exercise directory.
If the test suite contains benchmarks, you can run these with the `-bench`
flag:
go test -bench .
For more detailed info about the Go track see the [help
page](http://exercism.io/languages/go).
## Submitting Incomplete Problems
It's possible to submit an incomplete solution so you can see how others have completed the exercise.

49
go/react/interfaces.go Normal file
View File

@@ -0,0 +1,49 @@
package react
// A Reactor manages linked cells.
type Reactor interface {
// CreateInput creates an input cell linked into the reactor
// with the given initial value.
CreateInput(int) InputCell
// CreateCompute1 creates a compute cell which computes its value
// based on one other cell. The compute function will only be called
// if the value of the passed cell changes.
CreateCompute1(Cell, func(int) int) ComputeCell
// CreateCompute2 is like CreateCompute1, but depending on two cells.
// The compute function will only be called if the value of any of the
// passed cells changes.
CreateCompute2(Cell, Cell, func(int, int) int) ComputeCell
}
// A Cell is conceptually a holder of a value.
type Cell interface {
// Value returns the current value of the cell.
Value() int
}
// An InputCell has a changeable value, changing the value triggers updates to
// other cells.
type InputCell interface {
Cell
// SetValue sets the value of the cell.
SetValue(int)
}
// A ComputeCell always computes its value based on other cells and can
// call callbacks upon changes.
type ComputeCell interface {
Cell
// AddCallback adds a callback which will be called when the value changes.
// It returns a callback handle which can be used to remove the callback.
AddCallback(func(int)) CallbackHandle
// RemoveCallback removes a previously added callback, if it exists.
RemoveCallback(CallbackHandle)
}
// A CallbackHandle is used to remove previously added callbacks, see ComputeCell.
type CallbackHandle interface{}

94
go/react/react.go Normal file
View File

@@ -0,0 +1,94 @@
package react
import (
"fmt"
"strconv"
)
const testVersion = 4
// MyReactor implements Reactor
type MyReactor struct {
lastId int
cells []Cell
}
// New creates a new Reactor
func New() *MyReactor {
r := &MyReactor{}
//r.callbacks = make(map[Cell][]func(int))
return r
}
// CreateInput builds an input cell and adds it to the reactor
func (r *MyReactor) CreateInput(i int) InputCell {
r.lastId++
ic := MyCell{val: i, id: r.lastId}
return &ic
}
// CreateCompute1 Takes a cell and a function and returns a compute cell
// which has a value based on running the function on the cells value.
func (r *MyReactor) CreateCompute1(c Cell, f func(int) int) ComputeCell {
r.lastId++
cc := &MyCell{id: r.lastId, isComputed: true}
cc.compVal = func() int { return f(c.Value()) }
return cc
}
// CreateCompute2 Takes two cells and a function and returns a compute cell
// which has a value based on running the function on the cells values.
func (r *MyReactor) CreateCompute2(c1, c2 Cell, f func(int, int) int) ComputeCell {
r.lastId++
cc := &MyCell{id: r.lastId, isComputed: true}
cc.compVal = func() int { return f(c1.Value(), c2.Value()) }
return cc
}
// MyCell implements the all Cell interfaces
type MyCell struct {
id int
isComputed bool
val int
compVal func() int
lastCallbackId int
callbacks map[int]func(int)
}
// Value returns the value of the cell
func (c MyCell) Value() int {
if c.isComputed {
return c.compVal()
}
return c.val
}
// SetValue sets the value on the cell
func (c *MyCell) SetValue(i int) {
if i == c.val || c.isComputed {
// No change or this is a computed cell, just return
return
}
c.val = i
// Hit all callbacks
for _, v := range c.callbacks {
fmt.Println("Hitting a callback: " + strconv.Itoa(i))
v(i)
}
}
func (c *MyCell) AddCallback(cb func(int)) CallbackHandle {
if c.lastCallbackId == 0 {
fmt.Println("Initializing Callback Map (Cell " + strconv.Itoa(c.id) + ")")
c.callbacks = make(map[int]func(int))
}
fmt.Println("Adding a Callback to " + strconv.Itoa(c.id))
c.lastCallbackId++
c.callbacks[c.lastCallbackId] = cb
fmt.Println("Number of Callbacks: " + strconv.Itoa(c.lastCallbackId))
return c.lastCallbackId
}
func (c *MyCell) RemoveCallback(cbh CallbackHandle) {
delete(c.callbacks, cbh.(int))
}

278
go/react/react_test.go Normal file
View File

@@ -0,0 +1,278 @@
package react
import (
"runtime"
"testing"
)
// Define a function New() Reactor and the stuff that follows from
// implementing Reactor.
//
// Also define a testVersion with a value that matches
// the targetTestVersion here.
const targetTestVersion = 4
// This is a compile time check to see if you've properly implemented New().
var _ Reactor = New()
// If this test fails and you've proprly defined testVersion the requirements
// of the tests have changed since you wrote your submission.
func TestTestVersion(t *testing.T) {
if testVersion != targetTestVersion {
t.Fatalf("Found testVersion = %v, want %v", testVersion, targetTestVersion)
}
}
func assertCellValue(t *testing.T, c Cell, expected int, explanation string) {
observed := c.Value()
_, _, line, _ := runtime.Caller(1)
if observed != expected {
t.Fatalf("(from line %d) %s: expected %d, got %d", line, explanation, expected, observed)
}
}
// Setting the value of an input cell changes the observable Value()
func TestSetInput(t *testing.T) {
r := New()
i := r.CreateInput(1)
assertCellValue(t, i, 1, "i.Value() doesn't match initial value")
i.SetValue(2)
assertCellValue(t, i, 2, "i.Value() doesn't match changed value")
}
// The value of a compute 1 cell is determined by the value of the dependencies.
func TestBasicCompute1(t *testing.T) {
r := New()
i := r.CreateInput(1)
c := r.CreateCompute1(i, func(v int) int { return v + 1 })
assertCellValue(t, c, 2, "c.Value() isn't properly computed based on initial input cell value")
i.SetValue(2)
assertCellValue(t, c, 3, "c.Value() isn't properly computed based on changed input cell value")
}
// The value of a compute 2 cell is determined by the value of the dependencies.
func TestBasicCompute2(t *testing.T) {
r := New()
i1 := r.CreateInput(1)
i2 := r.CreateInput(2)
c := r.CreateCompute2(i1, i2, func(v1, v2 int) int { return v1 | v2 })
assertCellValue(t, c, 3, "c.Value() isn't properly computed based on initial input cell values")
i1.SetValue(4)
assertCellValue(t, c, 6, "c.Value() isn't properly computed when first input cell value changes")
i2.SetValue(8)
assertCellValue(t, c, 12, "c.Value() isn't properly computed when second input cell value changes")
}
// Compute 2 cells can depend on compute 1 cells.
func TestCompute2Diamond(t *testing.T) {
r := New()
i := r.CreateInput(1)
c1 := r.CreateCompute1(i, func(v int) int { return v + 1 })
c2 := r.CreateCompute1(i, func(v int) int { return v - 1 })
c3 := r.CreateCompute2(c1, c2, func(v1, v2 int) int { return v1 * v2 })
assertCellValue(t, c3, 0, "c3.Value() isn't properly computed based on initial input cell value")
i.SetValue(3)
assertCellValue(t, c3, 8, "c3.Value() isn't properly computed based on changed input cell value")
}
// Compute 1 cells can depend on other compute 1 cells.
func TestCompute1Chain(t *testing.T) {
r := New()
inp := r.CreateInput(1)
var c Cell = inp
for i := 2; i <= 8; i++ {
// must save current value of loop variable i for correct behavior.
// compute function has to use digitToAdd not i.
digitToAdd := i
c = r.CreateCompute1(c, func(v int) int { return v*10 + digitToAdd })
}
assertCellValue(t, c, 12345678, "c.Value() isn't properly computed based on initial input cell value")
inp.SetValue(9)
assertCellValue(t, c, 92345678, "c.Value() isn't properly computed based on changed input cell value")
}
// Compute 2 cells can depend on other compute 2 cells.
func TestCompute2Tree(t *testing.T) {
r := New()
ins := make([]InputCell, 3)
for i, v := range []int{1, 10, 100} {
ins[i] = r.CreateInput(v)
}
add := func(v1, v2 int) int { return v1 + v2 }
firstLevel := make([]ComputeCell, 2)
for i := 0; i < 2; i++ {
firstLevel[i] = r.CreateCompute2(ins[i], ins[i+1], add)
}
output := r.CreateCompute2(firstLevel[0], firstLevel[1], add)
assertCellValue(t, output, 121, "output.Value() isn't properly computed based on initial input cell values")
for i := 0; i < 3; i++ {
ins[i].SetValue(ins[i].Value() * 2)
}
assertCellValue(t, output, 242, "output.Value() isn't properly computed based on changed input cell values")
}
// Compute cells can have callbacks.
func TestBasicCallback(t *testing.T) {
r := New()
i := r.CreateInput(1)
c := r.CreateCompute1(i, func(v int) int { return v + 1 })
var observed []int
c.AddCallback(func(v int) {
observed = append(observed, v)
})
if len(observed) != 0 {
t.Fatalf("callback called before changes were made")
}
i.SetValue(2)
if len(observed) != 1 {
t.Fatalf("callback not called when changes were made")
}
if observed[0] != 3 {
t.Fatalf("callback not called with proper value")
}
}
// Callbacks and only trigger on change.
func TestOnlyCallOnChanges(t *testing.T) {
r := New()
i := r.CreateInput(1)
c := r.CreateCompute1(i, func(v int) int {
if v > 3 {
return v + 1
}
return 2
})
var observedCalled int
c.AddCallback(func(int) {
observedCalled++
})
i.SetValue(1)
if observedCalled != 0 {
t.Fatalf("observe function called even though input didn't change")
}
i.SetValue(2)
if observedCalled != 0 {
t.Fatalf("observe function called even though computed value didn't change")
}
}
// Callbacks can be added and removed.
func TestCallbackAddRemove(t *testing.T) {
r := New()
i := r.CreateInput(1)
c := r.CreateCompute1(i, func(v int) int { return v + 1 })
var observed1 []int
cb1 := c.AddCallback(func(v int) {
observed1 = append(observed1, v)
})
var observed2 []int
c.AddCallback(func(v int) {
observed2 = append(observed2, v)
})
i.SetValue(2)
if len(observed1) != 1 || observed1[0] != 3 {
t.Fatalf("observed1 not properly called")
}
if len(observed2) != 1 || observed2[0] != 3 {
t.Fatalf("observed2 not properly called")
}
c.RemoveCallback(cb1)
i.SetValue(3)
if len(observed1) != 1 {
t.Fatalf("observed1 called after removal")
}
if len(observed2) != 2 || observed2[1] != 4 {
t.Fatalf("observed2 not properly called after first callback removal")
}
}
func TestMultipleCallbackRemoval(t *testing.T) {
r := New()
inp := r.CreateInput(1)
c := r.CreateCompute1(inp, func(v int) int { return v + 1 })
numCallbacks := 5
calls := make([]int, numCallbacks)
handles := make([]CallbackHandle, numCallbacks)
for i := 0; i < numCallbacks; i++ {
// Rebind i, otherwise all callbacks will use i = numCallbacks
i := i
handles[i] = c.AddCallback(func(v int) { calls[i]++ })
}
inp.SetValue(2)
for i := 0; i < numCallbacks; i++ {
if calls[i] != 1 {
t.Fatalf("callback %d/%d should be called 1 time, was called %d times", i+1, numCallbacks, calls[i])
}
c.RemoveCallback(handles[i])
}
inp.SetValue(3)
for i := 0; i < numCallbacks; i++ {
if calls[i] != 1 {
t.Fatalf("callback %d/%d was called after it was removed", i+1, numCallbacks)
}
}
}
func TestRemoveIdempotence(t *testing.T) {
r := New()
inp := r.CreateInput(1)
output := r.CreateCompute1(inp, func(v int) int { return v + 1 })
timesCalled := 0
cb1 := output.AddCallback(func(int) {})
output.AddCallback(func(int) { timesCalled++ })
for i := 0; i < 10; i++ {
output.RemoveCallback(cb1)
}
inp.SetValue(2)
if timesCalled != 1 {
t.Fatalf("remaining callback function was not called")
}
}
// Callbacks should only be called once even though
// multiple dependencies have changed.
func TestOnlyCallOnceOnMultipleDepChanges(t *testing.T) {
r := New()
i := r.CreateInput(1)
c1 := r.CreateCompute1(i, func(v int) int { return v + 1 })
c2 := r.CreateCompute1(i, func(v int) int { return v - 1 })
c3 := r.CreateCompute1(c2, func(v int) int { return v - 1 })
c4 := r.CreateCompute2(c1, c3, func(v1, v3 int) int { return v1 * v3 })
changed4 := 0
c4.AddCallback(func(int) { changed4++ })
i.SetValue(3)
if changed4 < 1 {
t.Fatalf("callback function was not called")
} else if changed4 > 1 {
t.Fatalf("callback function was called too often")
}
}
// Callbacks should not be called if dependencies change in such a way
// that the final value of the compute cell does not change.
func TestNoCallOnDepChangesResultingInNoChange(t *testing.T) {
r := New()
inp := r.CreateInput(0)
plus1 := r.CreateCompute1(inp, func(v int) int { return v + 1 })
minus1 := r.CreateCompute1(inp, func(v int) int { return v - 1 })
// The output's value is always 2, no matter what the input is.
output := r.CreateCompute2(plus1, minus1, func(v1, v2 int) int { return v1 - v2 })
timesCalled := 0
output.AddCallback(func(int) { timesCalled++ })
inp.SetValue(5)
if timesCalled != 0 {
t.Fatalf("callback function called even though computed value didn't change")
}
}