Last active
December 18, 2019 14:06
-
-
Save BraedonWooding/2b5e9954236a41eb6e97c249ebdb6b23 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
public class Results | |
{ | |
public double sum; | |
public double avg; | |
public double unique; | |
} | |
public static class Program | |
{ | |
// O(n) but loops twice | |
public static Results PerformOperationsNaive(double[] array) | |
{ | |
double counter = array.Sum(); | |
return new Results() { | |
sum = counter, | |
avg = counter / array.Length, | |
// Count is O(1) according to docs but overall segment is O(n) | |
// (Surprising about count since normally Linq is O(n) count) | |
// hashset > hashmap (don't care about values) | |
unique = new HashSet<double>(array).Count() | |
}; | |
} | |
public static Results PerformOperationsBetter(double[] array) | |
{ | |
double counter = 0; | |
// preset capacity to improve performance | |
HashSet<double> set = new HashSet<double>(array.Length); | |
// there is a perf diff to LINQ but it's negligable | |
// the larger difference is the fact we are 2n | |
foreach (var x in array) | |
{ | |
counter += x; | |
set.Add(x); | |
} | |
return new Results() { | |
sum = counter, | |
avg = counter / array.Length, | |
// Count is O(1) still | |
unique = set.Count() | |
}; | |
} | |
public static Results[] GetTableResults(double[][] table, Func<double[], Results> map) | |
{ | |
// trivial implementation of just a simple map | |
// you could parallelise this of course | |
return table.Select(map); | |
} | |
} | |
/* | |
If I had to write this again I would write it using decorators. | |
i.e. each decorator takes in 'x' processes it's change in internal state then passes it onto inner decorator | |
(this would be handled in abstract class to avoid annoyances / possible pitfalls of forgetting/breaking ABI) | |
This would have the benefit of making it simple to build the chains i.e. | |
GetTableResults(table, Sum.And(Avg.And(Unique.Only))) | |
You could use async with this approach too (allowing multiple transformations to work at once). | |
I sincerely doubt you would see a perf difference though (it may even be negative) because of | |
the fact that the work done by each object is tiny and very pipelinable and there are sync costs | |
(heck you may even get static polymorphism if working in C++ even with virtuals due to how nice it's setup) | |
You could again also parallelise the outer arrays (if you wanted to). | |
This would have a perf difference (positive) since each array's result is independent with the sync cost | |
being much smaller than the total cost of each job. | |
This is also much much much easier! Could even just create a worker scheduler and spawn threads for each | |
sub array and then sync up workers (or use async) | |
*/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{-# LANGUAGE ExistentialQuantification #-} | |
module Program where | |
-- Note: Set uses binary tree as backing so it's O(log n) instead of O(1) | |
-- Haskell hates typical hashsets/hashmaps since they aren't persistent | |
-- there are some packages that provide them but yeet let's avoid that | |
-- Other than that the complexity matches that of C# | |
import qualified Data.Set as S | |
class OperationDecorator c where | |
process :: c -> Double -> c | |
-- i.e. (Name, Value) | |
finalise :: c -> (String, String) | |
data Sum = Sum Double | |
data Avg = Avg Int Double | |
data Unique = Unique (S.Set Double) | |
instance OperationDecorator Sum where | |
process (Sum acc) = Sum . (+) acc | |
finalise (Sum acc) = ("sum", show acc) | |
instance OperationDecorator Avg where | |
process (Avg len acc) = Avg (len + 1) . ((+) acc) | |
finalise (Avg len acc) = ("avg", show $ if len > 0 then acc / fromIntegral len else 0) | |
instance OperationDecorator Unique where | |
process (Unique set) = Unique . flip S.insert set | |
finalise (Unique set) = ("unique", (show . S.size) set) | |
-- This is called existential typing | |
-- Basically it's just using a boxed type to get around type system requirements | |
-- It's a little hacky (in some ways) syntactically but often is the most | |
-- efficient. We can also simulate OOP but that'll cover a performance cost | |
-- There are other ways to fix this too (change how our data works OR work | |
-- on a fix set of transformations) | |
data AnyOperation = forall a. OperationDecorator a => AnyOperation a | |
instance OperationDecorator AnyOperation where | |
process (AnyOperation a) b = AnyOperation (process a b) | |
finalise (AnyOperation a) = finalise a | |
initSum = pack (Sum 0.0) | |
initAvg = pack (Avg 0 0.0) | |
initUnique = pack (Unique S.empty) | |
pack :: OperationDecorator a => a -> AnyOperation | |
pack = AnyOperation | |
applyOp :: [AnyOperation] -> [Double] -> [(String, String)] | |
applyOp ds [] = map finalise ds | |
-- This is a bit ugly... Could be cleaned up | |
applyOp ds (x:xs) = applyOp (map (\a -> a x) (map process ds)) xs | |
-- Example | |
results = applyOp [initSum, initAvg, initUnique] [1, 2, 3, 4, 5, 2, 2, 0, -100, 0] | |
-- [("sum","-81.0"),("avg","-8.1"),("unique","7")] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment