From 123fbeae2efe76c276c4f4c67b5c3a8af061f3be Mon Sep 17 00:00:00 2001 From: "Bradley M. Small" <bradley_small@hotmail.com> Date: Fri, 21 Feb 2014 13:10:08 -0500 Subject: [PATCH] initial entry --- bis.py | 24 +++++++ range.py | 187 ++++++++++++++++++++++++++++++++++++++++++++++++++ test_range.py | 29 ++++++++ 3 files changed, 240 insertions(+) create mode 100644 bis.py create mode 100644 range.py create mode 100644 test_range.py diff --git a/bis.py b/bis.py new file mode 100644 index 0000000..5e15430 --- /dev/null +++ b/bis.py @@ -0,0 +1,24 @@ + +import bisect +import random + +# Use a constant see to ensure that we see +# the same pseudo-random numbers each time +# we run the loop. +random.seed(1) + +# Generate 20 random numbers and +# insert them into a list in sorted +# order. +l = [] +for i in range(1, 20): + r = random.randint(1, 100) + position = bisect.bisect_left(l, r) + if len(l) == position: + bisect.insort(l, r) + print '%2d %2d' % (r, position), l + else : + if l[position] != r: + bisect.insort(l, r) + print '%2d %2d' % (r, position), l + diff --git a/range.py b/range.py new file mode 100644 index 0000000..ff06e9d --- /dev/null +++ b/range.py @@ -0,0 +1,187 @@ +""" + Author: Bradley M. Small + All work is my own, and copyright (c) 2014 is retained. + This code is provided as a testing exercise solely for evaluating me for employment. + +""" +import bisect +class Range: + """ + Class for managing tracked ranges + It contains a list to hold tuples which represent ranges + It allows: + adding new tracked ranges + deleting existing tracked ranges + querying the existance of a tracked range + + Ranges are all-in or all-out. There is no special case for handling overlapping ranges + or even fully encompassed ranges. 1,10 though it encompases 2,9 will not query true for + it. This design will have the most efficiency by being able to compare tuples using the + list's comparisons. + + However, if that were the intent changes would have to be made to test the tuple + to see if it encloses the range. Handling the encompassed list could use a class + rather than a tuple, and that class would have comparison operator. Rules would still + have to be clarified for partially encompassed ranges vs fully encompassed ranges. + Such as 1,5 and 6,10 exist and you want to track 3,9 as opposed to 2,4. + + These use-cases may require something along the lines of PartiallyIncludedRange or + FullyIncludedRange. + """ + + def __init__ (self): + """ + construction will simply create an empty member list + """ + self.TrackedRanges = [] + self.isSorted = True + + def __del__ (self): + """ + destruction will simply delete that list + """ + del self.TrackedRanges[:] + +## # helper to handle contained ranges if later desired +## def _Contains(self, rangeStart, rangeStop, start, stop): +## if rangeStart <=start && rangeStop >= stop: +## return True +## else: +## return False + + def AddRange(self, start, stop): + """ + If a range already exists return false for failure + When it doesn't already exist then add it and return true + + Depending on use-case false may be a warning ignored rather than a fatal error + that is left to the consumer + """ + +## # this is how it could be changed to handle contained ranges +## for (RangeStart, RangeStop) in self.TrackedRanges: +## if self._Contains(RangeStart, RangeStop, start, stop): +## return False #already there short circuit +## +## self.TrackedRanges.append( (start,stop) ) +## return True + + # Start tracking the given range + try: + if (start, stop) not in self.TrackedRanges: + self.TrackedRanges.append( (start,stop) ) + self.isSorted = False + return True + else: + return False + except MemoryError: + # in this case I would think it most appropriate to simply + # raise the memory error because the user would have a + # difficult time differentiating between not adding because + # it is already there, and not adding because the system was + # out of memory. However, this allows for expansion with + # a known possible error condition and can be expanded as + # necessary. + return False + + def DeleteRange(self, start, stop): + """ + If a range doesn't exist return false for failure + When it does already exist then remove it and return true + + Depending on use-case false may be a warning ignored rather than a fatal error + that is left to the consumer + """ +## # this is how it could be changed to handle contained ranges +## Deletions would either have to be maintained as they are since +## removing partial ranges would have to result in additional ranges +## being added. For example 1,10 exists. Removing 3,5 would require +## adding 1,2 and 6,10. Though programatically do-able it appears to +## the author that such would involve business logic outside the definition +## of this design specification. + + if self.isSorted == False: + self.TrackedRanges.sort() + self.isSorted = True + + position = bisect.bisect_left(self.TrackedRanges, (start, stop)) + if len(self.TrackedRanges) == position: + return False + else: + if self.TrackedRanges[position] == (start, stop): + self.TrackedRanges.remove( (start,stop) ) + return True + else: + return False + + # Stop tracking the given range +# if (start, stop) in self.TrackedRanges: +# self.TrackedRanges.remove( (start,stop) ) + # the next 3 lines could be removed or replaced with a pass + # to effect a noop for removing non-existing ranges + # hoever, their existance makes the unit_tests more + # reasonable, and one gains the flexibility of merely + # ignoring the return value +# return True +# else: +# return False + + def QueryRange(self, start, stop): + """ + If a range doesn't exist return false for failure + When it does exist then return true + """ + if self.isSorted == False: + self.TrackedRanges.sort() + self.isSorted = True + + position = bisect.bisect_left(self.TrackedRanges, (start, stop)) + if len(self.TrackedRanges) == position: + return False + else: + if self.TrackedRanges[position] == (start, stop): + return True + else: + return False + +## # this is how it could be changed to handle contained ranges +## for (RangeStart, RangeStop) in self.TrackedRanges: +## if self._Contains(RangeStart, RangeStop, start, stop): +## return True +## +## return False + + # Check whether the entire given range is being tracked +# if (start, stop) in self.TrackedRanges: +# return True +# else: +# return False + +##def main (): +## """ +## """ +## myRange = Range() +## +## myRange.AddRange(1,10) +## if myRange.QueryRange(1,10): +## print "Yest it is" +## else: +## print "No it isn't" +## +### Notice Ignored return value = noop +## myRange.DeleteRange(1,10) +## +## if myRange.QueryRange(1,10): +## print "Yest it is" +## else: +## print "No it isn't" +## +## +## return 0 +## +## +##if 0 == main(): +## print 'Success' +##else: +## print 'Fail' +## diff --git a/test_range.py b/test_range.py new file mode 100644 index 0000000..2d0ec15 --- /dev/null +++ b/test_range.py @@ -0,0 +1,29 @@ +""" + Author: Bradley M. Small + All work is my own, and copyright (c) 2014 is retained. + This code is provided as a testing exercise solely for evaluating me for employment. + +""" +import unittest +import range + +class Test_Range(unittest.TestCase): + def setUp(self): + self.myRange = range.Range() + + def test_AddRange(self): + self.assertTrue(self.myRange.AddRange(2,9)) + self.assertFalse(self.myRange.AddRange(2,9)) + + def test_DeleteRange(self): + self.assertTrue(self.myRange.AddRange(2,9)) + self.assertTrue(self.myRange.DeleteRange(2,9)) + self.assertFalse(self.myRange.DeleteRange(2,9)) + + def test_QueryRange(self): + self.assertTrue(self.myRange.AddRange(1,10)) + self.assertTrue(self.myRange.QueryRange(1,10)) + self.assertFalse(self.myRange.QueryRange(12,30)) + +if __name__ == '__main__': + unittest.main() -- GitLab