Package: shape-warrior-t.lusbir
LusbirGithub|PyPIThis library provides a Python integer range type -- thelusbir-- that is based on a different characterization of integer ranges than the characterization that built-in Python ranges are based on.pip install shape-warrior-t.lusbirThis project wasn't really made with a practical use case in mind -- it was more for exploring a novel concept, and also for gaining some experience making and publishing a library. That being said, if you think you'll find this useful, feel free to install and use this library in your own code. The library should work for any version of Python starting from Python 3.10, though it has only been tested on Python 3.11.1.How do lusbirs work?(Built-in) Python ranges are specified by three integers:start,stop, andstep. The numbers in a Python range start atstart, and then increase bystepuntil they reachstop, which is not included in the range.Lusbirs are specified by four pieces of information: alower bound, anupper bound, a nonzerostep, and abase.All numbers in a lusbir are between the lusbir's lower bound and upper bound, which can be any combination of inclusive and exclusive (unlike Python ranges, wherestopis always exclusive).Additionally, all numbers in a lusbir are of the form $n \times \mathrm{step} + \mathrm{base}$, where $n$ is an integer. This is like how all numbers in a Python range are of the form $n \times \mathrm{step} + \mathrm{start}$ for some integer $n$, but for lusbirs, $n$ can be negative -- instead of starting at 0, the starting value of $n$ is determined by the lower and upper bounds.Just like Python ranges, we go from one element of a lusbir to the next by adding the step. This means that lusbirs with a positive step are ordered from low to high and lusbirs with a negative step are ordered from high to low, the same as Python ranges.Example: a lusbir with inclusive lower bound 0, exclusive upper bound 10, step 2, and base 1We consider all numbers of the form $n \times \mathrm{step} + \mathrm{base}$, where $n$ is an integer -- in this case, all numbers of the form $2n + 1$ for some integer $n$:$$\dotsc, -5, -3, -1, 1, 3, 5, 7, 9, 11, 13, 15, \dotsc$$Then we apply the bounds, keeping only the numbers between 0 (inclusive) and 10 (exclusive):$$\dotsc, -5, -3, -1, \boxed{1, 3, 5, 7, 9}, 11, 13, 15, \dotsc$$$$\downarrow$$$$1, 3, 5, 7, 9$$The end result, $[1, 3, 5, 7, 9]$, is the list of numbers that this lusbir represents.Example: a lusbir with exclusive lower bound 5, inclusive upper bound 55, step -10, and base 5Take all numbers of the form $-10n + 5$ for some integer $n$:$$\dotsc, 75, 65, 55, 45, 35, 25, 15, 5, -5, -15, -25, \dotsc$$Keep only the numbers between 5 (exclusive) and 55 (inclusive):$$\dotsc, 75, 65, \boxed{55, 45, 35, 25, 15}, 5, -5, -15, -25, \dotsc$$$$\downarrow$$$$55, 45, 35, 25, 15$$We find that this lusbir represents the list $[55, 45, 35, 25, 15]$. Since the step is negative, the list is ordered from high to low.In general, if a number is of the form $m \times \mathrm{step} + \mathrm{base}$ for some integer $m$, then it will be of the form $n \times \mathrm{-step} + \mathrm{base}$ for the integer $n = -m$. Thus, flipping the sign of a lusbir's step does not change which numbers it contains; it only reverses the lusbir's order.Lusbirs in codeIntroductory example>>>fromlusbirimportLusbir>>>foriinLusbir('[)',0,30,3,2):...print(i)...25811141720232629We create a lusbir by passing some arguments to theLusbirconstructor, and we can iterate over the resultingLusbirobject just like a Python range.The four numeric arguments specify, in order, the numeric lower bound, numeric upper bound, step, and base of the new lusbir. In this case, the lower bound is 0, the upper bound is 30, the step is 3, and the base is 2. Thus, the resulting lusbir represents the list of all numbers between 0 and 30 that are of the form $3n + 2$ -- in other words, the list $[2, 5, 8, 11, 14, 17, 20, 23, 26, 29]$, which matches the output that we got.The initial string argument, meanwhile, is the new lusbir'sbound type, representing whether the lower bound and the upper bound are inclusive or exclusive. The options for this argument are as follows:Bound typeLower boundUpper bound'()'exclusiveexclusive'(]'exclusiveinclusive'[)'inclusiveexclusive'[]'inclusiveinclusiveGeneral formMore generally, a call to theLusbirconstructor looks like this:Lusbir(bound_type, lb_num, ub_num, step, base)wherebound_typeis one of the four options from above, andlb_num(numeric lower bound),ub_num(numeric upper bound),step, andbaseare integers, with the restriction thatstepis nonzero. All arguments are positional-only.There are abbreviated forms for theLusbirconstructor that do not specify all arguments, letting unspecified arguments take on their default values. Here are all of the ways to call theLusbirconstructor:Lusbir(ub_num)Lusbir(lb_num, ub_num)Lusbir(lb_num, ub_num, step)Lusbir(lb_num, ub_num, step, base)Lusbir(bound_type, lb_num, ub_num)Lusbir(bound_type, lb_num, ub_num, step)Lusbir(bound_type, lb_num, ub_num, step, base)By default,bound_typeis'[)',lb_numis 0,stepis 1, andbaseis 0. (ub_nummust always be specified.)Big table of more examplesCodeBoundsFormList representedLusbir('[)', 0, 10, 2, 1)$0 \leq x < 10$$x = 2n + 1$$[1, 3, 5, 7, 9]$Lusbir('(]', 5, 55, -10, 5)$5 < x \leq 55$$x = -10n + 5$$[55, 45, 35, 25, 15]$Lusbir('[]', -6, 6, 2)$-6 \leq x \leq 6$$x = 2n$$[-6, -4, -2, 0, 2, 4, 6]$Lusbir('()', -6, 0)$-6 < x < 0$$x = n$$[-5, -4, -3, -2, -1]$Lusbir(10)$0 \leq x < 10$$x = n$$[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]$Lusbir(10, 15)$10 \leq x < 15$$x = n$$[10, 11, 12, 13, 14]$Lusbir(10, 20, 2)$10 \leq x < 20$$x = 2n$$[10, 12, 14, 16, 18]$Lusbir(10, 20, 2, 1)$10 \leq x < 20$$x = 2n + 1$$[11, 13, 15, 17, 19]$Lusbir(10, 20, -2, 1)$10 \leq x < 20$$x = -2n + 1$$[19, 17, 15, 13, 11]$Lusbir(0, 10, -1)$0 \leq x < 10$$x = -n$$[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]$Lusbir('(]', 0, 10, -1)$0 < x \leq 10$$x = -n$$[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]$Lusbir('(]', 0, 10, -2)$0 < x \leq 10$$x = -2n$$[10, 8, 6, 4, 2]$Lusbir(0, -10)$0 \leq x < -10$$x = n$(empty list)Lusbir(10, 0, -1)$10 \leq x < 0$$x = -n$(empty list)Lusbir('[]', -10, 10, 3)$-10 \leq x \leq 10$$x = 3n$$[-9, -6, -3, 0, 3, 6, 9]$Lusbir('[]', -10, 10, 3, -10)$-10 \leq x \leq 10$$x = 3n - 10$$[-10, -7, -4, -1, 2, 5, 8]$Lusbir('[]', -10, 10, 3, 10)$-10 \leq x \leq 10$$x = 3n + 10$$[-8, -5, -2, 1, 4, 7, 10]$Lusbir('()', -40, -20, 5, 1)$-40 < x < -20$$x = 5n + 1$$[-39, -34, -29, -24]$Lusbir('()', 0, 40, 8, -1)$0 < x < 40$$x = 8n - 1$$[7, 15, 23, 31, 39]$Other exported itemsAside from the coreLusbirclass, the Lusbir library also exports theBoundTypetype alias and theBoundandLusbTuplenamed tuple types.BoundTypeis simply a type alias forLiteral['()', '(]', '[)', '[]'], representing the four options for a lusbir's bound type.The lower and upper bounds of a lusbir comprise both a numeric bound and an inclusive/exclusive status. In the Lusbir library, they are modelled byBoundobjects, which are named tuples with the formBound(number: int, inclusive: bool). For example,Bound(0, True)represents an inclusive bound of 0, whileBound(100, False)represents an exclusive bound of 100. Both lower bounds and upper bounds use the same class -- there is nothing that keeps track of whether a bound is a lower bound or an upper bound.The four pieces of information that characterize a lusbir -- the lower bound (lbound), upper bound (ubound), step, and base -- can be grouped into alusb tuple$(\mathrm{lbound}, \mathrm{ubound}, \mathrm{step}, \mathrm{base})$. These are modelled in the Lusbir library byLusbTupleobjects, which are named tuples with the formLusbTuple(lbound: Bound, ubound: Bound, step: int, base: int). For example, a lusbir with inclusive lower bound 0, exclusive upper bound 30, step 3, and base 2 has the lusb tupleLusbTuple(Bound(0, True), Bound(30, False), 3, 2).A lusbir's lusb tuple can be obtained through itslusb_tupleproperty, and a lusbir can be created from a given lusb tuple usingLusbir.from_lusb_tuple:>>>fromlusbirimportBound,LusbTuple,Lusbir>>>Lusbir('[)',0,30,3,2).lusb_tupleLusbTuple(lbound=Bound(number=0,inclusive=True),ubound=Bound(number=30,inclusive=False),step=3,base=2)>>>lusbir=Lusbir.from_lusb_tuple(LusbTuple(Bound(0,True),Bound(30,False),3,2))>>>lusbirLusbir('[)',0,30,3,2)>>>list(lusbir)[2,5,8,11,14,17,20,23,26,29]There are a few reasons one might want to work with lusb tuples:Equality for lusb tuples has stricter criteria than equality for lusbirs. Two lusbirs -- for example,Lusbir('(]', 20, 50, 10, 5)andLusbir('[)', 25, 55, 10, 25)-- can represent the same list (in this case, $[25, 35, 45]$), but have different values for the lower bound, upper bound, step, and/or base. In this case, the lusbirs will compare equal, but their lusb tuples will not.Unpacking a lusb tuple is an easy way of getting all of the information that specifies a lusbir:>>>fromlusbirimportLusbir>>>lusbir=Lusbir('(]',0,20,-2)>>>(lb_num,lb_incl),(ub_num,ub_incl),step,base=lusbir.lusb_tuple>>>print(lb_incl,ub_incl,lb_num,ub_num,step,base)FalseTrue020-20If you already have booleans denoting whether the lower/upper bounds of a lusbir are inclusive, there's no need to convert the boolean inclusivities to a bound type when usingLusbir.from_lusb_tuple.OtherLusbirfunctionalityA lusbir's lower bound, upper bound, step, and base can be accessed through itslbound,ubound,step, andbaseproperties, which have the same values as the corresponding properties on the lusbir's lusb tuple.>>>fromlusbirimportLusbir>>>lusbir=Lusbir('(]',0,20,-2)>>>lusbir.lboundBound(number=0,inclusive=False)>>>lusbir.uboundBound(number=20,inclusive=True)>>>lusbir.step-2>>>lusbir.base0Theto_rangeinstance method and theLusbir.from_rangestatic method allow for conversions between lusbirs and Python ranges (note that conversions may be lossy):>>>fromlusbirimportLusbir>>>Lusbir(0,40,8,2).to_range()range(2,42,8)>>>Lusbir.from_range(range(2,42,8))Lusbir('[)',2,42,8,2)Most importantly,Lusbiris a subclass ofcollections.abc.Sequence, and implements the same functionality thatrangeimplements --__getitem__,__hash__,__len__,count,index, etc.Here are all of the public constructors, properties, and methods of theLusbirclass (lrrepresents an instance of the class):Lusbir(ub_num: int, /) -> LusbirLusbir(lb_num: int, ub_num: int, step: int = 1, base: int = 0, /) -> LusbirLusbir(bound_type: BoundType, lb_num: int, ub_num: int, step: int = 1, base: int = 0, /) -> LusbirLusbir.from_lusb_tuple(lusb_tuple: LusbTuple, /) -> Lusbirlr.lbound: Boundlr.ubound: Boundlr.step: intlr.base: intlr.lusb_tuple: LusbTuplelr.__eq__(other, /) -> boollr.__getitem__(index: int, /) -> intlr.__getitem__(slice_: slice, /) -> Lusbirlr.__hash__() -> intlr.__iter__() -> Iterator[int]lr.__repr__() -> strLusbir.from_range(r: range, /) -> Lusbirlr.to_range() -> rangelr.__bool__() -> boollr.__contains__(x: int, /) -> boollr.__len__() -> intlr.__reversed__() -> Iterator[int]lr.count(x: int, /) -> intlr.index(x: int, /) -> intMiscI currently have no plans to further maintain or update the code for this library. If I do make changes to this library, it will be published as a new version, conforming toSemVer.Still, if you spot any bugs, typos, or other problems in the code, tests, or documentation, please let me know via email:bl.swt1@gmail.com.If you manage to find some amazing use case for this library not already covered by built-in Python ranges, I would love to hear about it.Thanks for viewing and reading about this project!