diff --git a/README.md b/README.md index d0fa7f8..c3c57cd 100644 --- a/README.md +++ b/README.md @@ -189,7 +189,7 @@ Here's an explanation of some key parts of this package: - Provides methods default state (like Default, TrueIfUnknown, FalseIFUnknown etc.,) that check if the value of a Trit is Unknown and change its value based on the method called. For instance, TrueIfUnknown will set the Trit to True if its current value is Unknown. - - There are some methods (like IsFalse, IsUnknown, IsTrue, IsConfidence etc.,) that check the state of a Trit and return a boolean indicating if the Trit is in the corresponding state. + - There are some methods (like IsFalse, IsUnknown, IsTrue, etc.,) that check the state of a Trit and return a boolean indicating if the Trit is in the corresponding state. - Some methods perform various operations like assigning a value to a Trit (Set), returning the normalized value of a Trit (Val), normalizing the Trit in place (Norm), getting the integer representation of the Trit (Int), and getting the string representation of the Trit (String). diff --git a/fn.go b/fn.go index c97850a..71c0f39 100644 --- a/fn.go +++ b/fn.go @@ -1,7 +1,18 @@ package trit import ( + "context" "reflect" + "runtime" + "sync" +) + +var ( + // The parallelTasks the number of parallel tasks. + parallelTasks = 1 + + // The maxParallelTasks is the maximum number of parallel tasks. + maxParallelTasks = runtime.NumCPU() * 3 ) // Logicable is a special data type from which to determine the state of Trit @@ -13,6 +24,66 @@ type Logicable interface { float32 | float64 } +// The logicFoundValue is a helper struct that holds a boolean value +// and a Mutex to protect it from concurrent access. +// +// They are used in the In function to detect the desired result +// in a separate goroutine. +type logicFoundValue struct { + m sync.Mutex + value Trit +} + +// SetValue sets a new value for the Found. It locks the Mutex before +// changing the value and unlocks it after the change is complete. +func (f *logicFoundValue) SetValue(value Trit) { + f.m.Lock() + defer f.m.Unlock() + f.value = value +} + +// GetValue retrieves the current value of the Found. It locks the Mutex +// before reading the value and unlocks it after the read is complete. +func (f *logicFoundValue) GetValue() Trit { + f.m.Lock() + defer f.m.Unlock() + return f.value +} + +// The init initializes the randomGenerator variable. +func init() { + parallelTasks = runtime.NumCPU() * 2 +} + +// ParallelTasks returns the number of parallel tasks. +// +// If the function is called without parameters, it returns the +// current value of parallelTasks. +// +// A function can receive one or more values for parallelTasks, +// these values are added together to form the final result for +// parallelTasks. If the new value for parallelTasks is less than +// or equal to zero - it will be set to 1, if it is greater than +// maxParallelTasks - it will be set to maxParallelTasks. +func ParallelTasks(v ...int) int { + if len(v) > 0 { + n := 0 + for _, value := range v { + n += value + } + + if n <= 0 { + parallelTasks = 1 + } else if n > maxParallelTasks { + parallelTasks = maxParallelTasks + } else { + parallelTasks = n + } + } + + return parallelTasks +} + // The logicToTrit function converts any logic type to Trit. func logicToTrit[T Logicable](v T) Trit { switch any(v).(type) { @@ -133,14 +204,65 @@ func Convert[T Logicable](v T) Trit { // t := trit.All(trit.True, trit.True, trit.True) // fmt.Println(t.String()) // Output: True func All[T Logicable](t ...T) Trit { - for _, v := range t { - trit := logicToTrit(v) - if trit.IsFalse() { - return False + var wg sync.WaitGroup + + // Will use context to stop the rest of the goroutines + // if the value has already been found. + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + p := parallelTasks + found := &logicFoundValue{value: True} + + // If the length of the slice is less than or equal to + // the number of parallel tasks, then we do not need + // to use goroutines. + if l := len(t); l == 0 { + return False + } else if l < p*2 { + for _, v := range t { + trit := logicToTrit(v) + if trit.IsFalse() || trit.IsUnknown() { + return False + } } + + return True } - return True + chunkSize := len(t) / p + for i := 0; i < p; i++ { + wg.Add(1) + + start := i * chunkSize + end := start + chunkSize + if i == p-1 { + end = len(t) + } + + go func(start, end int) { + defer wg.Done() + + for _, b := range t[start:end] { + trit := logicToTrit(b) + // Check if the context has been cancelled. + select { + case <-ctx.Done(): + return + default: + } + + if trit.IsFalse() || trit.IsUnknown() { + found.SetValue(False) + cancel() // stop all other goroutines + return + } + } + }(start, end) + } + + wg.Wait() + return found.GetValue() } // Any returns True if any of the trit-objects are True. @@ -150,14 +272,66 @@ func All[T Logicable](t ...T) Trit { // t := trit.Any(trit.True, trit.False, trit.False) // fmt.Println(t.String()) // Output: True func Any[T Logicable](t ...T) Trit { - for _, v := range t { - trit := logicToTrit(v) - if trit.IsTrue() { - return True + var wg sync.WaitGroup + + // Will use context to stop the rest of the goroutines + // if the value has already been found. + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + p := parallelTasks + found := &logicFoundValue{value: False} + + // If the length of the slice is less than or equal to + // the number of parallel tasks, then we do not need + // to use goroutines. + if l := len(t); l == 0 { + return False + } else if l < p*2 { + for _, v := range t { + trit := logicToTrit(v) + if trit.IsTrue() { + return True + } } + + return False } - return False + chunkSize := len(t) / p + for i := 0; i < p; i++ { + wg.Add(1) + + start := i * chunkSize + end := start + chunkSize + if i == p-1 { + end = len(t) + } + + go func(start, end int) { + defer wg.Done() + + for _, b := range t[start:end] { + trit := logicToTrit(b) + + // Check if the context has been cancelled. + select { + case <-ctx.Done(): + return + default: + } + + if trit.IsTrue() { + found.SetValue(True) + cancel() // stop all other goroutines + return + } + } + }(start, end) + } + + wg.Wait() + return found.GetValue() } // None returns True if none of the trit-objects are True. @@ -333,6 +507,79 @@ func Neq[T, U Logicable](a T, b U) Trit { return ta.Neq(tb) } +// Known returns boolean true if all Trit-Like values has definiteness, +// i.e. is either True or False. +// +// Example usage: +// +// a := trit.Known(trit.True, trit.False, trit.Unknown) +// fmt.Println(a.String()) // Output: False +// +// b := trit.Known(trit.True, trit.True, trit.False) +// fmt.Println(b.String()) // Output: True +func Known[T Logicable](ts ...T) Trit { + var wg sync.WaitGroup + + // Will use context to stop the rest of the goroutines + // if the value has already been found. + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + p := parallelTasks + found := &logicFoundValue{value: True} + + // If the length of the slice is less than or equal to + // the number of parallel tasks, then we do not need + // to use goroutines. + if l := len(ts); l == 0 { + return False + } else if l < p*2 { + for _, t := range ts { + trit := logicToTrit(t) + if trit == Unknown { + return False + } + } + + return True + } + + chunkSize := len(ts) / p + for i := 0; i < p; i++ { + wg.Add(1) + + start := i * chunkSize + end := start + chunkSize + if i == p-1 { + end = len(ts) + } + + go func(start, end int) { + defer wg.Done() + + for _, b := range ts[start:end] { + trit := logicToTrit(b) + + // Check if the context has been cancelled. + select { + case <-ctx.Done(): + return + default: + } + + if trit.IsUnknown() { + found.SetValue(False) + cancel() // stop all other goroutines + return + } + } + }(start, end) + } + + wg.Wait() + return found.GetValue() +} + // IsConfidence returns boolean true if all Trit-Like values has definiteness, // i.e. is either True or False. // diff --git a/fn_test.go b/fn_test.go index fe6efb1..2f35fe5 100644 --- a/fn_test.go +++ b/fn_test.go @@ -4,6 +4,50 @@ import ( "testing" ) +// TestParalellTasks tests the ParallelTasks function. +func TestParallelTasks(t *testing.T) { + tests := []struct { + name string + inputs []int + want int + }{ + { + name: "No input values", + inputs: []int{}, + want: parallelTasks, // should return current value of pt + }, + { + name: "Input values sum to less than 0", + inputs: []int{-10, -5}, + want: 1, // should be set to 1 + }, + { + name: "Input values sum to 0", + inputs: []int{-5, 5}, + want: 1, // should be set to 1 + }, + { + name: "Input values sum to more than maxParallelTasks", + inputs: []int{10, maxParallelTasks + 1}, + want: maxParallelTasks, // should be set to maxParallelTasks + }, + { + name: "Input values sum to valid value", + inputs: []int{3, 4}, + want: 7, // should be set to the sum of the inputs + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := ParallelTasks(test.inputs...) + if got != test.want { + t.Errorf("ParallelTasks() = %v, want %v", got, test.want) + } + }) + } +} + // TestIsFalse tests the IsFalse function. func TestIsFalse(t *testing.T) { tests := []struct { @@ -122,6 +166,7 @@ func TestConvert(t *testing.T) { // TestAll tests the All function. func TestAll(t *testing.T) { + ParallelTasks(2) tests := []struct { name string in []Trit @@ -135,6 +180,27 @@ func TestAll(t *testing.T) { {"[0, 1, 1] should return False", []Trit{False, True, True}, False}, {"[1, 0, 0] should return False", []Trit{True, False, False}, False}, {"[1, 1, 0] should return False", []Trit{True, True, False}, False}, + { + name: "Just empty list", + in: []Trit{}, + out: False, + }, + { + name: "A very large list for testing goroutines", + in: []Trit{ + True, True, True, True, True, True, True, True, True, True, + True, True, True, True, True, True, True, True, True, True, + True, True, True, True, True, True, True, True, True, True, + True, True, True, True, True, True, True, True, True, True, + True, True, True, True, True, True, True, True, True, True, + True, True, True, True, True, True, True, True, True, True, + True, True, True, True, True, True, True, True, True, True, + True, True, True, Unknown, True, True, True, True, True, True, + True, True, True, True, True, True, True, True, True, True, + True, True, True, True, True, True, True, True, True, True, + }, + out: False, + }, } for _, test := range tests { @@ -150,6 +216,7 @@ func TestAll(t *testing.T) { // TestAny tests the Any function. func TestAny(t *testing.T) { + ParallelTasks(2) tests := []struct { name string in []Trit @@ -163,6 +230,27 @@ func TestAny(t *testing.T) { {"[0, 1, 1] should return True", []Trit{False, True, True}, True}, {"[1, 0, 0] should return True", []Trit{True, False, False}, True}, {"[1, 1, 0] should return True", []Trit{True, True, False}, True}, + { + name: "Just empty list", + in: []Trit{}, + out: False, + }, + { + name: "A very large list for testing goroutines", + in: []Trit{ + False, False, False, False, False, False, False, False, False, + False, False, False, False, False, False, False, False, False, + False, False, False, False, False, False, False, False, False, + False, False, False, False, False, False, False, False, False, + False, False, False, False, False, False, False, False, False, + False, False, False, False, False, False, False, False, False, + False, False, False, False, False, False, False, False, False, + False, False, False, True, False, False, False, False, False, + False, False, False, False, False, False, False, False, False, + False, False, False, False, False, False, False, False, False, + }, + out: True, + }, } for _, test := range tests { @@ -802,6 +890,73 @@ func TestNeq(t *testing.T) { } } +// TestKnown tests the Known function. +func TestKnown(t *testing.T) { + ParallelTasks(2) + tests := []struct { + name string + in []Trit + out Trit + }{ + { + name: "Known should return True for (True, True, True)", + in: []Trit{True, True, True}, + out: True, + }, + { + name: "Known should return True for (True, True, False)", + in: []Trit{True, True, False}, + out: True, + }, + { + name: "Known should return True for (False, False, False)", + in: []Trit{False, False, False}, + out: True, + }, + { + name: "Known should return False for (False, Unknown, True)", + in: []Trit{False, Unknown, True}, + out: False, + }, + { + name: "Known should return False for (Unknown, Unknown)", + in: []Trit{Unknown, Unknown}, + out: False, + }, + { + name: "Just empty list", + in: []Trit{}, + out: False, + }, + { + name: "A very large list for testing goroutines", + in: []Trit{ + True, True, True, True, True, True, True, True, True, True, + True, True, True, True, True, True, True, True, True, True, + True, True, True, True, True, True, False, True, True, True, + True, True, True, True, True, True, True, True, True, True, + True, True, True, True, True, True, True, True, True, True, + True, True, False, True, True, True, True, True, True, True, + True, True, True, True, True, True, True, True, True, True, + True, True, True, Unknown, True, True, True, True, True, True, + True, True, True, True, True, True, True, True, True, True, + True, True, True, True, True, True, False, False, False, + }, + out: False, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + result := Known(test.in...) + if result != test.out { + t.Errorf("Known did not return %v for %v", + test.out, test.in) + } + }) + } +} + // TestIsConfidence tests the IsConfidence function. func TestIsConfidence(t *testing.T) { tests := []struct {