Package digint

digint.digint

Base-Agnostic Integer Manipulation

digint.digint is a module focused on easy high-level integer manipulation across any numerical base. Works with binary, decimal, or any other base. digint.digint seeks to make complex digit-level and notation operations easy, just like they were Collections.

Documentation

Setup

This module can be installed using:

pip install digint

Usage

This module is intended to be used only as a module, and can be imported after installing using the traditional process:

from digint import digitint

Create an integer in any base

# input integers as you would with `int()`,
# if the intiger is already in the base you wish to use
n1 = digitint(1234, base=10)
n2 = digitint("BASE36", base=36)
n3 = digitint(0xABCDEF, base=16)

# convert bases on initialization, if the input is a intiger type
n4 = digitint(255, base=2) # == 0b11111111
n5 = digitint(int("BASE36", 36), base=10) # == 683248722
n6 = digitint(0xABCDEF, base=10) # == 11259375

Access and modify digits like a collection

# get the digit at index 2
print(n1.get_digit(2)) # outputs "3"
num.set_digit(2, 5)
print(n1.get_digit(2)) # outputs "5"

Easy notation

print(str(n2)) # output "BASE36"
print(str(n3)) # output "ABCDEF"
print(str(n5)) # output "683248722", as the base is set to 10

Full mutable collection implementation on integers

print(n2.pop(-1)) #outputs "6"
print(n2.pop(-1)) #outputs "3"
n2.append(int("D", 36))
print(n2) #outputs "BASED"

# The sum of all digits
print(sum(n2)) #outputs "76"

# The average of all digits
print(sum(n2)/len(n2)) #outputs "15.2"

Customizable Notation

# same as str(n3) 
print(n3.notate()) # outputs "ABCDEF"

from digint import NotationFormat
fmt = NotationFormat(*tuple("0123456789ZYXWVU"))
print(n3.notate(fmt)) # outputs "ZYXWVU"

And More

There are a handfull of other ease of use features that this module provides, feel free to reference the documentation for more information.

Licence

This is licensed under the Mozilla Public License 2.0 (MPL 2.0) Licence. See the Licence file in this repository for more information.

Contribute

Contributions are always welcome! Use the github repository to report issues and contribute to this project.

Credits

While not required, feel free to credit "Markus Hammer" (or just "Markus") if you find this code or script useful for whatever you may be doing with it.

Security Policy

While the python source code will be actively maintained, any binary files (if at all provided) are in no way supported. These are provided as a courtesy and are not intended to be the main usage of this software. Please keep this in mind when choosing how you wish to use this software.

Supported Versions

Version Supported
1.0.0.0 >=
1.0.0.0 <

Reporting a Vulnerability

Please report any issues to the email 107761433+MarkusHammer(THEN THE @ SYMBOL HERE)users.noreply.github.com

Mozilla Public License Version 2.0

1. Definitions

1.1. "Contributor" means each individual or legal entity that creates, contributes to the creation of, or owns Covered Software.

1.2. "Contributor Version" means the combination of the Contributions of others (if any) used by a Contributor and that particular Contributor's Contribution.

1.3. "Contribution" means Covered Software of a particular Contributor.

1.4. "Covered Software" means Source Code Form to which the initial Contributor has attached the notice in Exhibit A, the Executable Form of such Source Code Form, and Modifications of such Source Code Form, in each case including portions thereof.

1.5. "Incompatible With Secondary Licenses" means

(a) that the initial Contributor has attached the notice described
    in Exhibit B to the Covered Software; or

(b) that the Covered Software was made available under the terms of
    version 1.1 or earlier of the License, but not also under the
    terms of a Secondary License.

1.6. "Executable Form" means any form of the work other than Source Code Form.

1.7. "Larger Work" means a work that combines Covered Software with other material, in a separate file or files, that is not Covered Software.

1.8. "License" means this document.

1.9. "Licensable" means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently, any and all of the rights conveyed by this License.

1.10. "Modifications" means any of the following:

(a) any file in Source Code Form that results from an addition to,
    deletion from, or modification of the contents of Covered
    Software; or

(b) any new file in Source Code Form that contains any Covered
    Software.

1.11. "Patent Claims" of a Contributor means any patent claim(s), including without limitation, method, process, and apparatus claims, in any patent Licensable by such Contributor that would be infringed, but for the grant of the License, by the making, using, selling, offering for sale, having made, import, or transfer of either its Contributions or its Contributor Version.

1.12. "Secondary License" means either the GNU General Public License, Version 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General Public License, Version 3.0, or any later versions of those licenses.

1.13. "Source Code Form" means the form of the work preferred for making modifications.

1.14. "You" (or "Your") means an individual or a legal entity exercising rights under this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with You. For purposes of this definition, "control" means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity.

2. License Grants and Conditions

2.1. Grants

Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license:

(a) under intellectual property rights (other than patent or trademark) Licensable by such Contributor to use, reproduce, make available, modify, display, perform, distribute, and otherwise exploit its Contributions, either on an unmodified basis, with Modifications, or as part of a Larger Work; and

(b) under Patent Claims of such Contributor to make, use, sell, offer for sale, have made, import, and otherwise transfer either its Contributions or its Contributor Version.

2.2. Effective Date

The licenses granted in Section 2.1 with respect to any Contribution become effective for each Contribution on the date the Contributor first distributes such Contribution.

2.3. Limitations on Grant Scope

The licenses granted in this Section 2 are the only rights granted under this License. No additional rights or licenses will be implied from the distribution or licensing of Covered Software under this License. Notwithstanding Section 2.1(b) above, no patent license is granted by a Contributor:

(a) for any code that a Contributor has removed from Covered Software; or

(b) for infringements caused by: (i) Your and any other third party's modifications of Covered Software, or (ii) the combination of its Contributions with other software (except as part of its Contributor Version); or

(c) under Patent Claims infringed by Covered Software in the absence of its Contributions.

This License does not grant any rights in the trademarks, service marks, or logos of any Contributor (except as may be necessary to comply with the notice requirements in Section 3.4).

2.4. Subsequent Licenses

No Contributor makes additional grants as a result of Your choice to distribute the Covered Software under a subsequent version of this License (see Section 10.2) or under the terms of a Secondary License (if permitted under the terms of Section 3.3).

2.5. Representation

Each Contributor represents that the Contributor believes its Contributions are its original creation(s) or it has sufficient rights to grant the rights to its Contributions conveyed by this License.

2.6. Fair Use

This License is not intended to limit any rights You have under applicable copyright doctrines of fair use, fair dealing, or other equivalents.

2.7. Conditions

Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in Section 2.1.

3. Responsibilities

3.1. Distribution of Source Form

All distribution of Covered Software in Source Code Form, including any Modifications that You create or to which You contribute, must be under the terms of this License. You must inform recipients that the Source Code Form of the Covered Software is governed by the terms of this License, and how they can obtain a copy of this License. You may not attempt to alter or restrict the recipients' rights in the Source Code Form.

3.2. Distribution of Executable Form

If You distribute Covered Software in Executable Form then:

(a) such Covered Software must also be made available in Source Code Form, as described in Section 3.1, and You must inform recipients of the Executable Form how they can obtain a copy of such Source Code Form by reasonable means in a timely manner, at a charge no more than the cost of distribution to the recipient; and

(b) You may distribute such Executable Form under the terms of this License, or sublicense it under different terms, provided that the license for the Executable Form does not attempt to limit or alter the recipients' rights in the Source Code Form under this License.

3.3. Distribution of a Larger Work

You may create and distribute a Larger Work under terms of Your choice, provided that You also comply with the requirements of this License for the Covered Software. If the Larger Work is a combination of Covered Software with a work governed by one or more Secondary Licenses, and the Covered Software is not Incompatible With Secondary Licenses, this License permits You to additionally distribute such Covered Software under the terms of such Secondary License(s), so that the recipient of the Larger Work may, at their option, further distribute the Covered Software under the terms of either this License or such Secondary License(s).

3.4. Notices

You may not remove or alter the substance of any license notices (including copyright notices, patent notices, disclaimers of warranty, or limitations of liability) contained within the Source Code Form of the Covered Software, except that You may alter any license notices to the extent required to remedy known factual inaccuracies.

3.5. Application of Additional Terms

You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, You may do so only on Your own behalf, and not on behalf of any Contributor. You must make it absolutely clear that any such warranty, support, indemnity, or liability obligation is offered by You alone, and You hereby agree to indemnify every Contributor for any liability incurred by such Contributor as a result of warranty, support, indemnity or liability terms You offer. You may include additional disclaimers of warranty and limitations of liability specific to any jurisdiction.

4. Inability to Comply Due to Statute or Regulation

If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Software due to statute, judicial order, or regulation then You must: (a) comply with the terms of this License to the maximum extent possible; and (b) describe the limitations and the code they affect. Such description must be placed in a text file included with all distributions of the Covered Software under this License. Except to the extent prohibited by statute or regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be able to understand it.

5. Termination

5.1. The rights granted under this License will terminate automatically if You fail to comply with any of its terms. However, if You become compliant, then the rights granted under this License from a particular Contributor are reinstated (a) provisionally, unless and until such Contributor explicitly and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor fails to notify You of the non-compliance by some reasonable means prior to 60 days after You have come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an ongoing basis if such Contributor notifies You of the non-compliance by some reasonable means, this is the first time You have received notice of non-compliance with this License from such Contributor, and You become compliant prior to 30 days after Your receipt of the notice.

5.2. If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate.

5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user license agreements (excluding distributors and resellers) which have been validly granted by You or Your distributors under this License prior to termination shall survive termination.


  • *
    1. Disclaimer of Warranty *
  • ------------------------- *
  • *
  • Covered Software is provided under this License on an "as is" *
  • basis, without warranty of any kind, either expressed, implied, or *
  • statutory, including, without limitation, warranties that the *
  • Covered Software is free of defects, merchantable, fit for a *
  • particular purpose or non-infringing. The entire risk as to the *
  • quality and performance of the Covered Software is with You. *
  • Should any Covered Software prove defective in any respect, You *
  • (not any Contributor) assume the cost of any necessary servicing, *
  • repair, or correction. This disclaimer of warranty constitutes an *
  • essential part of this License. No use of any Covered Software is *
  • authorized under this License except under this disclaimer. *
  • *


  • *
    1. Limitation of Liability *
  • -------------------------- *
  • *
  • Under no circumstances and under no legal theory, whether tort *
  • (including negligence), contract, or otherwise, shall any *
  • Contributor, or anyone who distributes Covered Software as *
  • permitted above, be liable to You for any direct, indirect, *
  • special, incidental, or consequential damages of any character *
  • including, without limitation, damages for lost profits, loss of *
  • goodwill, work stoppage, computer failure or malfunction, or any *
  • and all other commercial damages or losses, even if such party *
  • shall have been informed of the possibility of such damages. This *
  • limitation of liability shall not apply to liability for death or *
  • personal injury resulting from such party's negligence to the *
  • extent applicable law prohibits such limitation. Some *
  • jurisdictions do not allow the exclusion or limitation of *
  • incidental or consequential damages, so this exclusion and *
  • limitation may not apply to You. *
  • *

8. Litigation

Any litigation relating to this License may be brought only in the courts of a jurisdiction where the defendant maintains its principal place of business and such litigation shall be governed by laws of that jurisdiction, without reference to its conflict-of-law provisions. Nothing in this Section shall prevent a party's ability to bring cross-claims or counter-claims.

9. Miscellaneous

This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not be used to construe this License against a Contributor.

10. Versions of the License

10.1. New Versions

Mozilla Foundation is the license steward. Except as provided in Section 10.3, no one other than the license steward has the right to modify or publish new versions of this License. Each version will be given a distinguishing version number.

10.2. Effect of New Versions

You may distribute the Covered Software under the terms of the version of the License under which You originally received the Covered Software, or under the terms of any subsequent version published by the license steward.

10.3. Modified Versions

If you create software not governed by this License, and you want to create a new license for such software, you may create and use a modified version of this License if you rename the license and remove any references to the name of the license steward (except to note that such modified license differs from this License).

10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses

If You choose to distribute Source Code Form that is Incompatible With Secondary Licenses under the terms of this version of the License, the notice described in Exhibit B of this License must be attached.

Exhibit A - Source Code Form License Notice

This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.

If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice.

You may add additional accurate notices of copyright ownership.

Exhibit B - "Incompatible With Secondary Licenses" Notice

This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0.

Sub-modules

digint.digint

digint …

digint.errors

errors …

digint.notation_format

notation_format …

digint.tests

init

digint.tools

tools …

digint.typings
digint.userint

userint …

Classes

class ExtendedBasedIntiger (value: Union[int, str, Iterable[Union[int, str]]] = 0, base: int = 10, *, notation_format: Optional[NotationFormat] = NotationFormat(value_symbols=('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'), undefined_symbol='?', positive_symbol='+', negative_symbol='-', radix_point_symbol='.', group_split_symbol=None, group_split_count=0, implicit_positive=True, implicit_negative=False))

ExtendedBasedIntiger

A mutable sequence type of intiger that has a explicitly set base, allowing for specific digit values to be get and set.

Supports any initger base starting at (and including) base 1. The base is presumed to be in the traditional place value format of value == d[x] * (base ** x) where value is the value the digit holds in the intiger, d is a single digit value form a sequence of digits, base is the base, and x is the specific index in question of the intiger.

However, base 1 produces inconsistencies with this specific format. Using this logic, one would also presume that the only digit in base 1 is 0 (or whatever relevant "naught" character would be used). This is why the extention of base 1 functionality is done in a child class of PositionalBasedIntiger.

Base 1, as implemented here, uses both the "naught" (with a value of 0) and "unity" (with a value of '1') digits, but in a tally system. This also means that the value of any given index of a digit means very little without the context of the rest of the defined digits as well. It is also implemented in such a way that the set digits will always be grouped together, ie. all "unity" digits will be grouped together, starting at the first digit and remaining completely set all the way to the digits index that matches the value of the number represented.

For these reason base 1 will have not functionality for functions that set or unset digits, as setting and unsetting digits are not relevant in unary. Attempting to do so will raise either a BaseInvalidOpperationError or a BaseValueError. Deleting digits are still possible, however.

Unary numbers are also fully compatible with negative values.

ex.

    1111111111 (base 1) == 10 (base 10)
    11111 (base 1) == 5 (base 10)
    111111 (base 1) == 11 (base 2)
    1101101 (base 1) == IMPOSSIBLE (the are unset digits in between the set digits)
      ^  ^
    000111000 (base 1) == IMPOSSIBLE (the first set digit is not the first digit in the number)
          ^^^

Effectively: base 1 (uniary) notation treates the value of the number as a sequence of a single type of digit that when counted add up to the value of the number.

NOTE: No functionality of any higher base is nor will modified in this class, making this class a superset of all proper positional based intiger notation formats and uniary.

Also supports customizable notation formats with the optional digint.notation_format attribute, including unary.

Expand source code
class ExtendedBasedIntiger(PositionalBasedIntiger):
    """
    `ExtendedBasedIntiger`

    A mutable sequence type of intiger that has a explicitly set base,
    allowing for specific digit values to be get and set.

    Supports any initger base starting at (and including) base 1.
    The base is presumed to be in the traditional place value format of
    `value == d[x] * (base ** x)` where `value` is the value the digit holds in the intiger,
    `d` is a single digit value form a sequence of digits,
    `base` is the base,
    and `x` is the specific index in question of the intiger.

    However, base 1 produces inconsistencies with this specific format.
    Using this logic, one would also presume that the only digit in base 1 is `0`
    (or whatever relevant "naught" character would be used).
    This is why the extention of base 1 functionality is done in a child class of
    `PositionalBasedIntiger`.

    Base 1, as implemented here, uses both the "naught" (with a value of `0`)
    and "unity" (with a value of '1') digits, but in a tally system.
    This also means that the value of any given index of a digit means very
    little without the context of the rest of the defined digits as well.
    It is also implemented in such a way that the set digits will always be grouped together,
    ie. all "unity" digits will be grouped together,
    starting at the first digit and remaining completely set
    all the way to the digits index that matches the value of the number represented.

    For these reason base 1 will have not functionality for functions that set or unset digits,
    as setting and unsetting digits are not relevant in unary.
    Attempting to do so will raise either a `BaseInvalidOpperationError` or a `BaseValueError`.
    Deleting digits are still possible, however.

    Unary numbers are also fully compatible with negative values.

    ex.
    ```
        1111111111 (base 1) == 10 (base 10)
        11111 (base 1) == 5 (base 10)
        111111 (base 1) == 11 (base 2)
        1101101 (base 1) == IMPOSSIBLE (the are unset digits in between the set digits)
          ^  ^
        000111000 (base 1) == IMPOSSIBLE (the first set digit is not the first digit in the number)
              ^^^
    ```

    Effectively: base 1 (uniary) notation treates the value of the number as a
    sequence of a single type of digit that when counted add up to the value of the number.

    NOTE: No functionality of any higher base is nor will modified in this class,
    making this class a superset of all proper positional based intiger notation formats and uniary.

    Also supports customizable notation formats with the optional `notation_format` attribute,
    including unary.
    """
    @override
    def __init__(self,
                 value:Union[int, str, Iterable[Union[int, str]]] = 0,
                 base:int = 10,
                 *,
                 notation_format:Optional[NotationFormat] = DEFAULT_FORMAT
                 ):

        self.__base:int = 2
        super().__init__(0 if base == 1 else value, base, notation_format=notation_format)

        if base == 1:
            self.base = 1
            if isinstance(value, int):
                self.x = value
            elif isinstance(value, str):
                value = value.lstrip("0")
                if not all(c == "1" for c in value):
                    raise BaseValueError(f"{value} cannot be represented in base 1")
                self.x = len(value)

    @override
    def copy(self,
             value:Optional[Union[int, str]] = None,
             base:Optional[int] = None,
             notation_format_override:Optional[NotationFormat] = None
             ) -> 'ExtendedBasedIntiger':
        """
        `copy`

        Creates a shallow copy of this object.

        Keyword Arguments:
            `value` -- When not `None`, will overrides the `x` value of the copy.
            `base` -- When not `None`, will overrides the `base` of the copy.
            `notation_format_override` -- When not `None`,
                will overrides the `notation_format` of the copy.

        Returns:
            The copy of the object.
        """
        if notation_format_override is not None:
            notation_format_override = self.notation_format
        return ExtendedBasedIntiger(value if value is not None else self.x,
                                    base if base is not None else self.base,
                                    notation_format = notation_format_override
                                    )
    __copy__ = copy

    @override
    def __deepcopy__(self, _ = None) -> 'ExtendedBasedIntiger':
        return self.copy(None,
                         None,
                         None if self.notation_format is None else self.notation_format.copy()
                         )

    @property
    @override
    def base(self) -> int:
        """
        `base`

        The base of this number. Must be greater than 0.
        Base 1 is handled particularly differently than other bases.
        """
        return self.__base

    @base.setter
    @override
    def base(self, value:int):
        if value <= 0:
            raise BaseValueError()
        self.__base = value

    @override
    def _get_single_digit(self, index:int) -> int:
        if self.base == 1:
            index = absindex(index, self.digit_length())
            return 1 if index < abs(self.x) else 0
        else:
            return super()._get_single_digit(index)

    @override
    def _pop_first(self) -> int:
        if self.base == 1:
            self.x -= 1
            return 1
        else:
            return super()._pop_first()

    @override
    def _mask_value_continuous(self, dindex: int, count: int = 1) -> int:
        if count <= 0:
            raise IndexError("The length of a continuous mask must be at least 1")

        if self.base == 1:
            return min(abs(self.x) - dindex, count)
        else:
            return super()._mask_value_continuous(dindex, count)

    @override
    def notate(self, notation_format:Optional[NotationFormat] = None) -> str:
        if self.base >= 2:
            return super().notate(notation_format)

        if notation_format is None:
            notation_format = self.notation_format

        if notation_format is None:
            raise BaseInvalidOpperationError("No format set, cannot notate")

        relevant_sign = ""
        if self.x < 0 and not notation_format.implicit_negative:
            if notation_format.negative_symbol is None:
                raise ValueError("Explicit negative values require a negative symbol")
            else:
                relevant_sign = notation_format.negative_symbol
        elif self.x > 0 and not notation_format.implicit_positive:
            if notation_format.positive_symbol is None:
                raise ValueError("Explicit positive values require a positive symbol")
            else:
                relevant_sign = notation_format.positive_symbol

        if notation_format.unity is None:
            raise NotationError("Cannot notate base 1 without a digit for unity")
        return relevant_sign + ((notation_format.unity) * self.x)

    @overload
    @override
    def set_digit(self, index:int, value:Union[int,str]): ...
    @overload
    @override
    def set_digit(self, index:Union[slice,range,Iterable[int]], value:Iterable[Union[int,str]]): ...
    @override # noqa:301
    def set_digit(self,
                  index:Union[int,slice,range,Iterable[int]],
                  value:Union[int,str,Iterable[Union[int,str]]]
                  ):
        if self.base == 1:
            raise BaseInvalidOpperationError("Digits cannot be set in base 1")
        super().set_digit(index, value) # type:ignore[reportCallIssue]

    @override
    def unset_digit(self, index:Union[int,slice,range,Iterable[int]]):
        if self.base == 1:
            raise BaseInvalidOpperationError("Cannot unset digits in base 1")
        return super().unset_digit(index)

    # NOTE: just like python handles it in bit_length, a value of 0 will always have no digits
    @override
    def digit_length(self) -> int:
        if self.base == 1:
            return abs(self.x)
        return super().digit_length()

    @override
    def insert(self, index:int, value:Union[int,str,Iterable[Union[int,str]]]):
        if self.base == 1:
            raise BaseInvalidOpperationError(f"Cannot insert digits into a base {self.base} number")
        super().insert(index, value)

    @override
    def pop(self, index:int = -1) -> int:
        if self.base == 1:
            index = absindex(index, self.digit_length())
            self.x -= 1
            return 1
        else:
            return super().pop(index)

    # returns the count of non-zero (non-unset) digits
    @override
    def digit_count(self) -> int:
        if self.base == 1:
            return abs(self.x)
        return super().digit_count()

    @override
    def digit_shift_left(self, amount:int = 1):
        if self.base == 1:
            raise BaseInvalidOpperationError("This base cannot be shifted")
        super().digit_shift_left(amount)

    @override
    def digit_shift_right(self, amount:int = 1):
        if self.base == 1:
            raise BaseInvalidOpperationError("This base cannot be shifted")
        super().digit_shift_right(amount)

    @override
    def rstrip(self, value:Union[int,str,Iterable[Union[int,str]]]):
        if self.base == 1:
            raise BaseInvalidOpperationError("This base cannot be stripped")
        super().rstrip(value)

    @override
    def lstrip(self, value:Union[int,str,Iterable[Union[int,str]]]):
        if self.base == 1:
            raise BaseInvalidOpperationError("This base cannot be stripped")
        super().lstrip(value)

    @override
    def strip(self, value:Union[int,str,Iterable[Union[int,str]]]):
        if self.base == 1:
            raise BaseInvalidOpperationError("This base cannot be stripped")
        super().strip(value)

    @override
    def contains(self, value:Union[int, str]) -> bool:
        if self.base == 1:
            value = self._ensure_unnotated(value)
            return (value == 1 and self.x != 0) or (value == 0 and self.x == 0)
        return super().contains(value)

Ancestors

  • PositionalBasedIntiger
  • ExtendedUserInt
  • UserInt
  • typing.SupportsInt
  • typing.SupportsFloat
  • typing.SupportsAbs
  • typing.SupportsComplex
  • typing.SupportsRound
  • typing.SupportsIndex
  • typing.Protocol
  • typing.Generic
  • collections.abc.Hashable
  • collections.abc.MutableSequence
  • collections.abc.Sequence
  • collections.abc.Reversible
  • collections.abc.Collection
  • collections.abc.Sized
  • collections.abc.Iterable
  • collections.abc.Container

Instance variables

prop base : int

base

The base of this number. Must be greater than 0. Base 1 is handled particularly differently than other bases.

Expand source code
@property
@override
def base(self) -> int:
    """
    `base`

    The base of this number. Must be greater than 0.
    Base 1 is handled particularly differently than other bases.
    """
    return self.__base

Methods

def contains(self, value: Union[int, str]) ‑> bool

contains

Returns true if the digit value appears anywhere in this digit sequence. This will not include leading 0s, but will return true if the tested digit value is 0 and this intiger's value is also 0.

Arguments

value – The value to check for.

def copy(self, value: Union[int, str, ForwardRef(None)] = None, base: Optional[int] = None, notation_format_override: Optional[NotationFormat] = None) ‑> ExtendedBasedIntiger

copy

Creates a shallow copy of this object.

Keyword Arguments: value – When not None, will overrides the x value of the copy. base – When not None, will overrides the base of the copy. notation_format_override – When not None, will overrides the digint.notation_format of the copy.

Returns

The copy of the object.

def digit_count(self) ‑> int

digit_count

Similar to bit_count, but relitive to the current base. Not to be confused with digit_length.

Returns

The amount of non-zero (non-unset) digits in the value.

def digit_length(self) ‑> int

digit_length

Similar to bit_length, but relitive to the current base. Not to be confused with digit_count.

Returns

The minimum necessary about of digits needed to display the number in full.

def digit_shift_left(self, amount: int = 1)

digit_shift_left

Similar to a binary shift left, shifts the value left according to the set base.

Arguments

amount – The amount to shift left. Will shift right when negative.

def digit_shift_right(self, amount: int = 1)

digit_shift_right

Similar to a binary shift right, shifts the value right according to the set base.

Arguments

amount – The amount to shift right. Will shift left when negative.

def insert(self, index: int, value: Union[int, str, Iterable[Union[int, str]]])

insert

Inserts the given value (or values, following the order in which they are supplied) before the given index. When given multiple values, each value will be inserted before the given index in order.

Arguments

index – The index (or indexes) to be inserted before. value – The value (or values) to insert.

def lstrip(self, value: Union[int, str, Iterable[Union[int, str]]])

rstrip

Removes the given digit (or digits) from the left hand (greatest place value) spot. Works similarly to str.lstrip, except for the fact that when value is a single string (not a iterable of strings) it will be treated as a single digit instead of a iterable of digits. This will not strip any leading 0 digit values, as these are already not notated by default nor would effect the digit length of this intiger.

Arguments

value – The value (or iterable of values) to strip. Can be either a intiger digit value, or a string corelating to a single digit.

def notate(self, notation_format: Optional[NotationFormat] = None) ‑> str

notate Notates the intiger, using the given notation format if possible, or the digint.notation_format set in the object's attributes if the paramater is not set.

Keyword Arguments: notation_format – A notation format to use over the one set in self.notation_format, if not None.

Raises

NotationError
Raised when both the argument and attribute digint.notation_format are None; or when other errors are raised during notation.

Returns

The final notation of the intiger.

def pop(self, index: int = -1) ‑> int

pop

Gets the value at the given index while popping it.

Keyword Arguments: index – The target index to pop. Defaults to -1.

Returns

The value at the given index before removal.

def rstrip(self, value: Union[int, str, Iterable[Union[int, str]]])

rstrip

Removes the given digit (or digits) from the right hand (units) spot. Works similarly to str.rstrip, except for the fact that when value is a single string (not a iterable of strings) it will be treated as a single digit instead of a iterable of digits.

Arguments

value – The value (or iterable of values) to strip. Can be either a intiger digit value, or a string corelating to a single digit.

def set_digit(self, index: Union[int, slice, range, Iterable[int]], value: Union[int, str, Iterable[Union[int, str]]])

set_digit

Sets the digit (or digits) at the given index (or indexes) to the given value (or values). With multiple indexes and, there must be a matching quantity of values to set at those indexes.

Arguments

index – The index (or indexes) to set. value – The value (or values) to set the index (or indexes) to.

Raises

ValueError: Raised when the given value is out of bounds of the current base.

def strip(self, value: Union[int, str, Iterable[Union[int, str]]])

rstrip

Removes the given digit (or digits) from both sides. Works similarly to str.strip, except for the fact that when value is a single string (not a iterable of strings) it will be treated as a single digit instead of a iterable of digits. This will not strip any leading 0 digit values, as these are already not notated by default nor would effect the digit length of this intiger.

Arguments

value – The value (or iterable of values) to strip. Can be either a intiger digit value, or a string corelating to a single digit.

def unset_digit(self, index: Union[int, slice, range, Iterable[int]])

unset_digit

Unsets (set to 0) the value at the given index (or indexes).

Arguments

index – The index (or indexes) digit to be unset.

class digitint (value: Union[int, str, Iterable[Union[int, str]]] = 0, base: int = 10, *, notation_format: Optional[NotationFormat] = NotationFormat(value_symbols=('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'), undefined_symbol='?', positive_symbol='+', negative_symbol='-', radix_point_symbol='.', group_split_symbol=None, group_split_count=0, implicit_positive=True, implicit_negative=False))

ExtendedBasedIntiger

A mutable sequence type of intiger that has a explicitly set base, allowing for specific digit values to be get and set.

Supports any initger base starting at (and including) base 1. The base is presumed to be in the traditional place value format of value == d[x] * (base ** x) where value is the value the digit holds in the intiger, d is a single digit value form a sequence of digits, base is the base, and x is the specific index in question of the intiger.

However, base 1 produces inconsistencies with this specific format. Using this logic, one would also presume that the only digit in base 1 is 0 (or whatever relevant "naught" character would be used). This is why the extention of base 1 functionality is done in a child class of PositionalBasedIntiger.

Base 1, as implemented here, uses both the "naught" (with a value of 0) and "unity" (with a value of '1') digits, but in a tally system. This also means that the value of any given index of a digit means very little without the context of the rest of the defined digits as well. It is also implemented in such a way that the set digits will always be grouped together, ie. all "unity" digits will be grouped together, starting at the first digit and remaining completely set all the way to the digits index that matches the value of the number represented.

For these reason base 1 will have not functionality for functions that set or unset digits, as setting and unsetting digits are not relevant in unary. Attempting to do so will raise either a BaseInvalidOpperationError or a BaseValueError. Deleting digits are still possible, however.

Unary numbers are also fully compatible with negative values.

ex.

    1111111111 (base 1) == 10 (base 10)
    11111 (base 1) == 5 (base 10)
    111111 (base 1) == 11 (base 2)
    1101101 (base 1) == IMPOSSIBLE (the are unset digits in between the set digits)
      ^  ^
    000111000 (base 1) == IMPOSSIBLE (the first set digit is not the first digit in the number)
          ^^^

Effectively: base 1 (uniary) notation treates the value of the number as a sequence of a single type of digit that when counted add up to the value of the number.

NOTE: No functionality of any higher base is nor will modified in this class, making this class a superset of all proper positional based intiger notation formats and uniary.

Also supports customizable notation formats with the optional digint.notation_format attribute, including unary.

Expand source code
class ExtendedBasedIntiger(PositionalBasedIntiger):
    """
    `ExtendedBasedIntiger`

    A mutable sequence type of intiger that has a explicitly set base,
    allowing for specific digit values to be get and set.

    Supports any initger base starting at (and including) base 1.
    The base is presumed to be in the traditional place value format of
    `value == d[x] * (base ** x)` where `value` is the value the digit holds in the intiger,
    `d` is a single digit value form a sequence of digits,
    `base` is the base,
    and `x` is the specific index in question of the intiger.

    However, base 1 produces inconsistencies with this specific format.
    Using this logic, one would also presume that the only digit in base 1 is `0`
    (or whatever relevant "naught" character would be used).
    This is why the extention of base 1 functionality is done in a child class of
    `PositionalBasedIntiger`.

    Base 1, as implemented here, uses both the "naught" (with a value of `0`)
    and "unity" (with a value of '1') digits, but in a tally system.
    This also means that the value of any given index of a digit means very
    little without the context of the rest of the defined digits as well.
    It is also implemented in such a way that the set digits will always be grouped together,
    ie. all "unity" digits will be grouped together,
    starting at the first digit and remaining completely set
    all the way to the digits index that matches the value of the number represented.

    For these reason base 1 will have not functionality for functions that set or unset digits,
    as setting and unsetting digits are not relevant in unary.
    Attempting to do so will raise either a `BaseInvalidOpperationError` or a `BaseValueError`.
    Deleting digits are still possible, however.

    Unary numbers are also fully compatible with negative values.

    ex.
    ```
        1111111111 (base 1) == 10 (base 10)
        11111 (base 1) == 5 (base 10)
        111111 (base 1) == 11 (base 2)
        1101101 (base 1) == IMPOSSIBLE (the are unset digits in between the set digits)
          ^  ^
        000111000 (base 1) == IMPOSSIBLE (the first set digit is not the first digit in the number)
              ^^^
    ```

    Effectively: base 1 (uniary) notation treates the value of the number as a
    sequence of a single type of digit that when counted add up to the value of the number.

    NOTE: No functionality of any higher base is nor will modified in this class,
    making this class a superset of all proper positional based intiger notation formats and uniary.

    Also supports customizable notation formats with the optional `notation_format` attribute,
    including unary.
    """
    @override
    def __init__(self,
                 value:Union[int, str, Iterable[Union[int, str]]] = 0,
                 base:int = 10,
                 *,
                 notation_format:Optional[NotationFormat] = DEFAULT_FORMAT
                 ):

        self.__base:int = 2
        super().__init__(0 if base == 1 else value, base, notation_format=notation_format)

        if base == 1:
            self.base = 1
            if isinstance(value, int):
                self.x = value
            elif isinstance(value, str):
                value = value.lstrip("0")
                if not all(c == "1" for c in value):
                    raise BaseValueError(f"{value} cannot be represented in base 1")
                self.x = len(value)

    @override
    def copy(self,
             value:Optional[Union[int, str]] = None,
             base:Optional[int] = None,
             notation_format_override:Optional[NotationFormat] = None
             ) -> 'ExtendedBasedIntiger':
        """
        `copy`

        Creates a shallow copy of this object.

        Keyword Arguments:
            `value` -- When not `None`, will overrides the `x` value of the copy.
            `base` -- When not `None`, will overrides the `base` of the copy.
            `notation_format_override` -- When not `None`,
                will overrides the `notation_format` of the copy.

        Returns:
            The copy of the object.
        """
        if notation_format_override is not None:
            notation_format_override = self.notation_format
        return ExtendedBasedIntiger(value if value is not None else self.x,
                                    base if base is not None else self.base,
                                    notation_format = notation_format_override
                                    )
    __copy__ = copy

    @override
    def __deepcopy__(self, _ = None) -> 'ExtendedBasedIntiger':
        return self.copy(None,
                         None,
                         None if self.notation_format is None else self.notation_format.copy()
                         )

    @property
    @override
    def base(self) -> int:
        """
        `base`

        The base of this number. Must be greater than 0.
        Base 1 is handled particularly differently than other bases.
        """
        return self.__base

    @base.setter
    @override
    def base(self, value:int):
        if value <= 0:
            raise BaseValueError()
        self.__base = value

    @override
    def _get_single_digit(self, index:int) -> int:
        if self.base == 1:
            index = absindex(index, self.digit_length())
            return 1 if index < abs(self.x) else 0
        else:
            return super()._get_single_digit(index)

    @override
    def _pop_first(self) -> int:
        if self.base == 1:
            self.x -= 1
            return 1
        else:
            return super()._pop_first()

    @override
    def _mask_value_continuous(self, dindex: int, count: int = 1) -> int:
        if count <= 0:
            raise IndexError("The length of a continuous mask must be at least 1")

        if self.base == 1:
            return min(abs(self.x) - dindex, count)
        else:
            return super()._mask_value_continuous(dindex, count)

    @override
    def notate(self, notation_format:Optional[NotationFormat] = None) -> str:
        if self.base >= 2:
            return super().notate(notation_format)

        if notation_format is None:
            notation_format = self.notation_format

        if notation_format is None:
            raise BaseInvalidOpperationError("No format set, cannot notate")

        relevant_sign = ""
        if self.x < 0 and not notation_format.implicit_negative:
            if notation_format.negative_symbol is None:
                raise ValueError("Explicit negative values require a negative symbol")
            else:
                relevant_sign = notation_format.negative_symbol
        elif self.x > 0 and not notation_format.implicit_positive:
            if notation_format.positive_symbol is None:
                raise ValueError("Explicit positive values require a positive symbol")
            else:
                relevant_sign = notation_format.positive_symbol

        if notation_format.unity is None:
            raise NotationError("Cannot notate base 1 without a digit for unity")
        return relevant_sign + ((notation_format.unity) * self.x)

    @overload
    @override
    def set_digit(self, index:int, value:Union[int,str]): ...
    @overload
    @override
    def set_digit(self, index:Union[slice,range,Iterable[int]], value:Iterable[Union[int,str]]): ...
    @override # noqa:301
    def set_digit(self,
                  index:Union[int,slice,range,Iterable[int]],
                  value:Union[int,str,Iterable[Union[int,str]]]
                  ):
        if self.base == 1:
            raise BaseInvalidOpperationError("Digits cannot be set in base 1")
        super().set_digit(index, value) # type:ignore[reportCallIssue]

    @override
    def unset_digit(self, index:Union[int,slice,range,Iterable[int]]):
        if self.base == 1:
            raise BaseInvalidOpperationError("Cannot unset digits in base 1")
        return super().unset_digit(index)

    # NOTE: just like python handles it in bit_length, a value of 0 will always have no digits
    @override
    def digit_length(self) -> int:
        if self.base == 1:
            return abs(self.x)
        return super().digit_length()

    @override
    def insert(self, index:int, value:Union[int,str,Iterable[Union[int,str]]]):
        if self.base == 1:
            raise BaseInvalidOpperationError(f"Cannot insert digits into a base {self.base} number")
        super().insert(index, value)

    @override
    def pop(self, index:int = -1) -> int:
        if self.base == 1:
            index = absindex(index, self.digit_length())
            self.x -= 1
            return 1
        else:
            return super().pop(index)

    # returns the count of non-zero (non-unset) digits
    @override
    def digit_count(self) -> int:
        if self.base == 1:
            return abs(self.x)
        return super().digit_count()

    @override
    def digit_shift_left(self, amount:int = 1):
        if self.base == 1:
            raise BaseInvalidOpperationError("This base cannot be shifted")
        super().digit_shift_left(amount)

    @override
    def digit_shift_right(self, amount:int = 1):
        if self.base == 1:
            raise BaseInvalidOpperationError("This base cannot be shifted")
        super().digit_shift_right(amount)

    @override
    def rstrip(self, value:Union[int,str,Iterable[Union[int,str]]]):
        if self.base == 1:
            raise BaseInvalidOpperationError("This base cannot be stripped")
        super().rstrip(value)

    @override
    def lstrip(self, value:Union[int,str,Iterable[Union[int,str]]]):
        if self.base == 1:
            raise BaseInvalidOpperationError("This base cannot be stripped")
        super().lstrip(value)

    @override
    def strip(self, value:Union[int,str,Iterable[Union[int,str]]]):
        if self.base == 1:
            raise BaseInvalidOpperationError("This base cannot be stripped")
        super().strip(value)

    @override
    def contains(self, value:Union[int, str]) -> bool:
        if self.base == 1:
            value = self._ensure_unnotated(value)
            return (value == 1 and self.x != 0) or (value == 0 and self.x == 0)
        return super().contains(value)

Ancestors

  • PositionalBasedIntiger
  • ExtendedUserInt
  • UserInt
  • typing.SupportsInt
  • typing.SupportsFloat
  • typing.SupportsAbs
  • typing.SupportsComplex
  • typing.SupportsRound
  • typing.SupportsIndex
  • typing.Protocol
  • typing.Generic
  • collections.abc.Hashable
  • collections.abc.MutableSequence
  • collections.abc.Sequence
  • collections.abc.Reversible
  • collections.abc.Collection
  • collections.abc.Sized
  • collections.abc.Iterable
  • collections.abc.Container

Instance variables

prop base : int

base

The base of this number. Must be greater than 0. Base 1 is handled particularly differently than other bases.

Expand source code
@property
@override
def base(self) -> int:
    """
    `base`

    The base of this number. Must be greater than 0.
    Base 1 is handled particularly differently than other bases.
    """
    return self.__base
prop limit_high : Optional[int]

Inherited from: PositionalBasedIntiger.limit_high

limit_high …

prop limit_low : Optional[int]

Inherited from: PositionalBasedIntiger.limit_low

limit_low …

var on_changed

Inherited from: PositionalBasedIntiger.on_changed

on_changed

prop radix : int

Inherited from: PositionalBasedIntiger.radix

radix …

prop sign : int

Inherited from: PositionalBasedIntiger.sign

sign

prop x : int

Inherited from: PositionalBasedIntiger.x

x

Methods

def bit_count(self) ‑> int

Inherited from: PositionalBasedIntiger.bit_count

bit_count

def bit_length(self) ‑> int

Inherited from: PositionalBasedIntiger.bit_length

bit_count

def contains(self, value: Union[int, str]) ‑> bool

Inherited from: PositionalBasedIntiger.contains

contains

def copy(self, value: Union[int, str, ForwardRef(None)] = None, base: Optional[int] = None, notation_format_override: Optional[NotationFormat] = None) ‑> ExtendedBasedIntiger

copy

Creates a shallow copy of this object.

Keyword Arguments: value – When not None, will overrides the x value of the copy. base – When not None, will overrides the base of the copy. notation_format_override – When not None, will overrides the digint.notation_format of the copy.

Returns

The copy of the object.

def delete_digit(self, index: Union[int, slice, range, Iterable[int]])

Inherited from: PositionalBasedIntiger.delete_digit

delete_digit

def digit_count(self) ‑> int

Inherited from: PositionalBasedIntiger.digit_count

digit_count

def digit_length(self) ‑> int

Inherited from: PositionalBasedIntiger.digit_length

digit_length

def digit_rotate_left(self, amount: int = 1)

Inherited from: PositionalBasedIntiger.digit_rotate_left

digit_rotate_left

def digit_rotate_right(self, amount: int = 1)

Inherited from: PositionalBasedIntiger.digit_rotate_right

digit_rotate_right

def digit_shift_left(self, amount: int = 1)

Inherited from: PositionalBasedIntiger.digit_shift_left

digit_shift_left

def digit_shift_right(self, amount: int = 1)

Inherited from: PositionalBasedIntiger.digit_shift_right

digit_shift_right

def fixed_sign_and(self, value: int) ‑> int

Inherited from: PositionalBasedIntiger.fixed_sign_and

fixed_sign_and

def fixed_sign_invert(self) ‑> int

Inherited from: PositionalBasedIntiger.fixed_sign_invert

fixed_sign_invert

def fixed_sign_or(self, value: int) ‑> int

Inherited from: PositionalBasedIntiger.fixed_sign_or

fixed_sign_or

def fixed_sign_xor(self, value: int) ‑> int

Inherited from: PositionalBasedIntiger.fixed_sign_xor

fixed_sign_xor

def get_digit(self, index: Union[int, slice, range, Iterable[int]]) ‑> Union[int, List[int]]

Inherited from: PositionalBasedIntiger.get_digit

get_digit

def insert(self, index: int, value: Union[int, str, Iterable[Union[int, str]]])

Inherited from: PositionalBasedIntiger.insert

insert

def is_integer(self) ‑> bool

Inherited from: PositionalBasedIntiger.is_integer

is_integer

def iter_digits(self, at_least: int = 0) ‑> Iterator[int]

Inherited from: PositionalBasedIntiger.iter_digits

iter_digits Returns an iterable that iterates through the digit values of the integer, starting at the units spot. Will iterate 0 when all other …

def iter_symbols(self, at_least: int = 1) ‑> Iterator[str]

Inherited from: PositionalBasedIntiger.iter_symbols

iter_symbols

def lstrip(self, value: Union[int, str, Iterable[Union[int, str]]])

Inherited from: PositionalBasedIntiger.lstrip

rstrip

def mask(self, index: Union[int, slice, range, Iterable[int]]) ‑> int

Inherited from: PositionalBasedIntiger.mask

mask

def notate(self, notation_format: Optional[NotationFormat] = None) ‑> str

Inherited from: PositionalBasedIntiger.notate

notate Notates the intiger, using the given notation format if possible, or the digint.notation_format set in the object's attributes if the paramater is …

def pop(self, index: int = -1) ‑> int

Inherited from: PositionalBasedIntiger.pop

pop

def reversed_iter_digits(self, at_least: int = 0) ‑> Iterator[int]

Inherited from: PositionalBasedIntiger.reversed_iter_digits

reversed_iter_digits Returns an iterable that iterates through the digit values of the integer, ending at the units spot. Will iterate 0 when all …

def reversed_iter_symbols(self, at_least: int = 1) ‑> Iterator[str]

Inherited from: PositionalBasedIntiger.reversed_iter_symbols

reversed_iter_symbols

def rstrip(self, value: Union[int, str, Iterable[Union[int, str]]])

Inherited from: PositionalBasedIntiger.rstrip

rstrip

def set_digit(self, index: Union[int, slice, range, Iterable[int]], value: Union[int, str, Iterable[Union[int, str]]])

Inherited from: PositionalBasedIntiger.set_digit

set_digit

def strip(self, value: Union[int, str, Iterable[Union[int, str]]])

Inherited from: PositionalBasedIntiger.strip

rstrip

def to_bytes(self, length: , byteorder: Literal['little', 'big'], *, signed: bool = False) ‑> bytes

Inherited from: PositionalBasedIntiger.to_bytes

to_bytes

def unset_digit(self, index: Union[int, slice, range, Iterable[int]])

Inherited from: PositionalBasedIntiger.unset_digit

unset_digit

class NotationFormat (*value_symbols: str, undefined_symbol: Optional[str] = None, positive_symbol: Optional[str] = None, negative_symbol: Optional[str] = None, radix_point_symbol: Optional[str] = None, group_split_symbol: Optional[str] = None, group_split_count: int = 0, implicit_positive: bool = False, implicit_negative: bool = False)

NotationFormat

A dataclass that holds common notation formating information.

Expand source code
@dataclass(init=False)
class NotationFormat(SequenceABC, HashableABC):
    """
    `NotationFormat`

    A dataclass that holds common notation formating information.
    """

    value_symbols:Tuple[str, ...] = tuple()
    undefined_symbol:Optional[str] = None
    positive_symbol:Optional[str] = None
    negative_symbol:Optional[str] = None
    radix_point_symbol:Optional[str] = None
    group_split_symbol:Optional[str] = None
    group_split_count:int = 3
    implicit_positive:bool = False
    implicit_negative:bool = False

    def __init__(self,
                 *value_symbols:str,
                 undefined_symbol:Optional[str] = None,
                 positive_symbol:Optional[str] = None,
                 negative_symbol:Optional[str] = None,
                 radix_point_symbol:Optional[str] = None,
                 group_split_symbol:Optional[str] = None,
                 group_split_count:int = 0,
                 implicit_positive:bool = False,
                 implicit_negative:bool = False
                 ):
        self.__frozen:bool = False

        self.value_symbols:Tuple[str, ...] = value_symbols
        self.undefined_symbol:Optional[str] = undefined_symbol
        self.negative_symbol:Optional[str] = negative_symbol
        self.positive_symbol:Optional[str] = positive_symbol
        self.radix_point_symbol:Optional[str] = radix_point_symbol
        self.group_split_symbol:Optional[str] = group_split_symbol
        self.group_split_count:int = group_split_count
        self.implicit_positive:bool = implicit_positive
        self.implicit_negative:bool = implicit_negative

        self.__frozen = True

    def __setattribute__(self, name: str, value: Any):
        if self.__frozen:
            raise FrozenInstanceError(value, name, self)
        return super().__setattr__(name, value)

    @property
    def unity(self) -> Optional[str]:
        """
        `unity`

        Returns:
            The 'unity' symbol (the symbol associated with the value of 1),
            if defined, from the `value_symbols`. If not defined, returns `None`.
        """
        return self.value_symbols[1] if len(self.value_symbols) >= 1 else None

    @property
    def naught(self) -> Optional[str]:
        """
        `naught`

        Returns:
            The 'naught' symbol (the symbol associated with the value of 0),
            if defined, from the `value_symbols`. If not defined, returns `None`.
        """
        return self.value_symbols[0] if len(self.value_symbols) >= 0 else None

    def get_value(self, symbol:str) -> Optional[int]:
        """
        `get_value`

        Looks for the first instance of the given symbol in the `value_symbols`.

        Arguments:
            `symbol` -- The symbol to look up.

        Returns:
            The index (and therefroe the value) of the symbol in `value_symbols`
            if possible, otherwise returns `None`.
        """
        return None if symbol not in self.value_symbols else self.value_symbols.index(symbol)

    @overload
    def __getitem__(self, index:int) -> str: ...
    @overload
    def __getitem__(self, index:slice) -> Tuple[str, ...]: ...
    def __getitem__(self, index:Union[int, slice]) -> Union[str, Tuple[str, ...]]: # noqa:301
        return self.value_symbols[index]

    def get_digit(self, index:int) -> Optional[str]:
        """
        `get_digit`

        Looks for the symbol for the given value, if possible`.

        Arguments:
            `index` -- The index (the digit's value) to find the symbol of.

        Returns:
            The symbol of the given value from `value_symbols`
            if possible, otherwise returns `None`.
        """
        if index < 0 or index >= len(self.value_symbols):
            return self.undefined_symbol
        else:
            return self.value_symbols[index]

    def __len__(self):
        return len(self.value_symbols)

    def __iter__(self) -> Iterator[str]:
        return iter(self.value_symbols)

    def __reverse__(self) -> Iterator[str]:
        return reversed(self.value_symbols)

    def copy(self, *_) -> 'NotationFormat':
        """
        `copy`

        Returns a deep copy of the object.
        """

        return NotationFormat(*self.value_symbols,
                              undefined_symbol = self.undefined_symbol,
                              positive_symbol = self.positive_symbol,
                              negative_symbol = self.negative_symbol,
                              radix_point_symbol = self.radix_point_symbol,
                              group_split_symbol = self.group_split_symbol,
                              group_split_count = self.group_split_count,
                              implicit_positive = self.implicit_positive,
                              implicit_negative = self.implicit_negative
                              )
    __copy__ = copy
    __deepcopy__ = copy

    def __hash__(self) -> int:
        return hash(asdict(self))

Ancestors

  • collections.abc.Sequence
  • collections.abc.Reversible
  • collections.abc.Collection
  • collections.abc.Sized
  • collections.abc.Iterable
  • collections.abc.Container
  • collections.abc.Hashable

Class variables

var group_split_count : int
var group_split_symbol : Optional[str]
var implicit_negative : bool
var implicit_positive : bool
var negative_symbol : Optional[str]
var positive_symbol : Optional[str]
var radix_point_symbol : Optional[str]
var undefined_symbol : Optional[str]
var value_symbols : Tuple[str, ...]

Instance variables

prop naught : Optional[str]

naught

Returns

The 'naught' symbol (the symbol associated with the value of 0), if defined, from the value_symbols. If not defined, returns None.

Expand source code
@property
def naught(self) -> Optional[str]:
    """
    `naught`

    Returns:
        The 'naught' symbol (the symbol associated with the value of 0),
        if defined, from the `value_symbols`. If not defined, returns `None`.
    """
    return self.value_symbols[0] if len(self.value_symbols) >= 0 else None
prop unity : Optional[str]

unity

Returns

The 'unity' symbol (the symbol associated with the value of 1), if defined, from the value_symbols. If not defined, returns None.

Expand source code
@property
def unity(self) -> Optional[str]:
    """
    `unity`

    Returns:
        The 'unity' symbol (the symbol associated with the value of 1),
        if defined, from the `value_symbols`. If not defined, returns `None`.
    """
    return self.value_symbols[1] if len(self.value_symbols) >= 1 else None

Methods

def copy(self, *_) ‑> NotationFormat

copy

Returns a deep copy of the object.

def get_digit(self, index: int) ‑> Optional[str]

get_digit

Looks for the symbol for the given value, if possible`.

Arguments

index – The index (the digit's value) to find the symbol of.

Returns

The symbol of the given value from value_symbols if possible, otherwise returns None.

def get_value(self, symbol: str) ‑> Optional[int]

get_value

Looks for the first instance of the given symbol in the value_symbols.

Arguments

symbol – The symbol to look up.

Returns

The index (and therefroe the value) of the symbol in value_symbols if possible, otherwise returns None.

class PositionalBasedIntiger (value: Union[int, str, Iterable[Union[int, str]], bytes, bytearray, memoryview] = 0, base: int = 10, *, notation_format: Optional[NotationFormat] = NotationFormat(value_symbols=('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'), undefined_symbol='?', positive_symbol='+', negative_symbol='-', radix_point_symbol='.', group_split_symbol=None, group_split_count=0, implicit_positive=True, implicit_negative=False))

PositionalBasedIntiger

A mutable sequence type of intiger that has a explicitly set base, allowing for specific digit values to be get and set.

Supports any initger base starting at binary (base 2). These bases are presumed to be in the traditional place value format of value == d[x] * (base ** x) where value is the value the digit holds in the intiger, d is a single digit value form a sequence of digits, base is the base, and x is the specific index in question of the intiger.

Supports many operators and methods that built in integers suport, casting the type back into a default intiger type when possible.

Also supports customizable notation formats with the optional digint.notation_format attribute.

Expand source code
class PositionalBasedIntiger(*tuple(__POSITIONAL_BASED_INT_BASES)):
    """
    `PositionalBasedIntiger`

    A mutable sequence type of intiger that has a explicitly set base,
    allowing for specific digit values to be get and set.

    Supports any initger base starting at binary (base 2).
    These bases are presumed to be in the traditional place value format of
    `value == d[x] * (base ** x)` where `value` is the value the digit holds in the intiger,
    `d` is a single digit value form a sequence of digits,
    `base` is the base,
    and `x` is the specific index in question of the intiger.

    Supports many operators and methods that built in integers suport,
    casting the type back into a default intiger type when possible.

    Also supports customizable notation formats with the optional `notation_format` attribute.
    """

    def __init__(self,
                 value:Union[int, str, Iterable[Union[int, str]], bytes, bytearray, memoryview] = 0,
                 base:int = 10,
                 *,
                 notation_format:Optional[NotationFormat] = DEFAULT_FORMAT
                 ):

        super().__init__(0)
        self.__base:int = 2

        self.x = 0
        self.base = base
        self.notation_format:Optional[NotationFormat] = notation_format
        if isinstance(value, int):
            self.x = value
        elif isinstance(value, str):
            self.x = int(value, base)
        else:
            for digit_value in value:
                digit_value = self._ensure_unnotated(digit_value)
                if digit_value >= base:
                    raise ValueError(f"Digit of value {digit_value} isn't possible in base {base}")
                self.x = (self.x * base) + digit_value

    def copy(self,
             value:Optional[Union[int, str]] = None,
             base:Optional[int] = None,
             notation_format_override:Optional[NotationFormat] = None
             ) -> 'PositionalBasedIntiger':
        """
        `copy`

        Creates a shallow copy of this object.

        Keyword Arguments:
            value -- When not `None`, will overrides the `x` value of the copy.
            base -- When not `None`, will overrides the `base` of the copy.
            notation_format_override -- When not `None`,
                will overrides the `notation_format` of the copy.

        Returns:
            The copy of the object.
        """
        if notation_format_override is not None:
            notation_format_override = self.notation_format
        return PositionalBasedIntiger(value if value is not None else self.x,
                                      base if base is not None else self.base,
                                      notation_format = notation_format_override
                                      )
    __copy__ = copy

    def __deepcopy__(self, _ = None) -> 'PositionalBasedIntiger':
        return self.copy(None,
                         None,
                         None if self.notation_format is None else self.notation_format.copy()
                         )

    @property
    def base(self) -> int:
        """
        `base`

        The base of the intiger.
        Must be at or above 2, as this class does not support any non-standard bases.
        """
        return self.__base

    @base.setter
    def base(self, value:int):
        if value < 2:
            raise ValueError("Invalid base", value)
        self.__base = value

    @property
    def radix(self) -> int:
        """
        radix

        Returns:
            The absolute value of the base.
            Will always return `base` when used in a `PositionalBasedIntiger`,
            but may differ when using with a extended type.
        """
        return abs(self.base)

    def _ensure_notated(self, value:Union[int,str]) -> str:
        if self.notation_format is None:
            raise NotationError("Cannot reference a symbol without a notation format")

        if isinstance(value, str):
            return value
        else:
            dig = self.notation_format.get_digit(value)
            if dig is None:
                raise NotationError(f"Could not find digit for {value} in current notation format")
            return dig

    def _ensure_unnotated(self, value:Union[int,str]) -> int:
        if self.notation_format is None:
            raise NotationError("Cannot reference a symbol without a notation format")

        if isinstance(value, int):
            return value

        v = self.notation_format.get_value(value)
        if v is None:
            raise NotationError(f"Given symbol '{value}' not found in current notation format")
        return v

    def _get_single_digit(self, index:int) -> int:
        index = absindex(index, self.digit_length())
        if self.base == 2:
            return (abs(self.x) >> index) % (0b1 << 1)
        else:
            return (abs(self.x) // (self.base ** index)) % self.base

    # this pops the digit in the units spot,
    # effectively shifts left once while returning units shifted out
    # slightly faster than the arbitrary pop method
    def _pop_first(self) -> int:
        dm = divmod(self.x, self.base)
        self.x = dm[0]
        return dm[1]

    # this pushes a value into the units place
    # effectively shifts right while appending a new value to the units
    # slightly faster than the arbitrary insert method
    def _prepend(self, value:Union[int,str]):
        value = self._ensure_unnotated(value)

        if value < 0 or value >= self.base:
            raise ValueError("Digit value out of bounds of base")

        if self.sign == -1:
            value *= -1

        self.x = (self.x * self.base) + value

    # effectively, this returns a continuous sequence
    # form the intiger with its place value still intact
    def _mask_value_continuous(self, dindex:int, count:int = 1) -> int:
        if count <= 0:
            raise IndexError("The length of a continuous mask must be at least 1")

        dindex = absindex(dindex, self.digit_length())

        if self.base == 2: # binary optimisable
            return abs(self.x) & (((0b1 << (count - 1)) - 1) << dindex)
        else:
            pv1 = self.base ** dindex
            pv2 = pv1 * (self.base ** count) # aka ```self.base ** (dindex + count)```
            return (abs(self.x) % pv2) - (abs(self.x) % pv1)

    def iter_digits(self, at_least:int = 0) -> Iterator[int]:
        """
        `iter_digits`
        Returns an iterable that iterates through the digit values of the integer,
        starting at the units spot.
        Will iterate 0 when all other digits are already iterated.

        Keyword Arguments:
            `at_least` -- Ensures that at least the given amount of digits are iterated, if above 1.

        Returns:
            An iterator that returns the digit values, starting at the units spot.

        Yields:
            The digits of the intiger, starting at the units spot.
        """
        indexes = range(max(self.digit_length(), at_least))
        return (self._get_single_digit(i) for i in indexes)
    __iter__ = iter_digits

    def reversed_iter_digits(self, at_least:int = 0) -> Iterator[int]:
        """
        `reversed_iter_digits`
        Returns an iterable that iterates through the digit values of the integer,
        ending at the units spot.
        Will iterate 0 when all other digits are already iterated.

        Keyword Arguments:
            `at_least` -- Ensures that at least the given amount of digits are iterated, if above 1.

        Returns:
            An iterator that returns the digit values, ending at the units spot.

        Yields:
            The digits of the intiger, ending at the units spot.
        """
        indexes = range(max(self.digit_length(), at_least)-1, -1, -1)
        return (self._get_single_digit(i) for i in indexes)
    __reverse__ = reversed_iter_digits

    def __bytes__(self):
        return bytes(self.iter_digits(0))

    def iter_symbols(self, at_least:int = 1) -> Iterator[str]:
        """
        `iter_symbols`

        Iterate the digit symbols starting at the units spot.
        When intending to use iteration for notation,
        it's suggested to use `reversed_iter_symbols` to avoid odering errors.

        Keyword Arguments:
            `at_least` -- ensures that at least the given amount of symbols are returned.
                Defaults to 1.

        Returns:
            An iterator of symbols.

        Yields:
            Digit symdols, starting at the units spot.
        """
        return (self._ensure_notated(x) for x in self.iter_digits(at_least))

    def reversed_iter_symbols(self, at_least:int = 1) -> Iterator[str]:
        """
        `reversed_iter_symbols`

        Iterate the digit symbols ending at the units spot.
        Intending to be used for notation purposes, as the ordering for string notation is correct.

        Keyword Arguments:
            `at_least` -- ensures that at least the given amount of symbols are returned.
                Defaults to 1.

        Returns:
            An iterator of symbols.

        Yields:
            Digit symdols, ending at the units spot.
        """
        return (self._ensure_notated(x) for x in self.reversed_iter_digits(at_least))

    def notate(self, notation_format:Optional[NotationFormat] = None) -> str:
        """
        `notate`
        Notates the intiger, using the given notation format if possible,
        or the `notation_format` set in the object's attributes if the paramater is not set.

        Keyword Arguments:
            notation_format -- A notation format to use
                over the one set in `self.notation_format`, if not `None`.

        Raises:
            NotationError: Raised when both the argument and attribute `notation_format` are `None`;
                or when other errors are raised during notation.

        Returns:
            The final notation of the intiger.
        """
        if notation_format is None:
            notation_format = self.notation_format

        if notation_format is None:
            raise NotationError("No format set, cannot notate")

        relevant_sign = ""
        if self.x < 0 and not notation_format.implicit_negative:
            if notation_format.negative_symbol is None:
                raise NotationError("Explicit negative values require a negative symbol")
            else:
                relevant_sign = notation_format.negative_symbol
        elif self.x > 0 and not notation_format.implicit_positive:
            if notation_format.positive_symbol is None:
                raise NotationError("Explicit positive values require a positive symbol")
            else:
                relevant_sign = notation_format.positive_symbol

        group_joint = ""
        if notation_format.group_split_symbol is not None:
            group_joint = notation_format.group_split_symbol
        group_count = notation_format.group_split_count

        if group_joint != "" and group_count > 0:
            groups = list(self.iter_symbols())
            group_indexes = range(0, self.digit_length(), group_count)
            groups = [''.join(groups[i:i+group_count])[::-1] for i in group_indexes]
            groups = groups[::-1]
        else:
            groups = self.reversed_iter_symbols()

        return relevant_sign + group_joint.join(groups)
    __str__ = notate
    __repr__ = notate

    @overload
    def get_digit(self, index:int) -> int: ...
    @overload
    def get_digit(self, index:Union[slice,range,Iterable[int]]) -> List[int]: ...
    def get_digit(self, # noqa:301
                  index:Union[int,slice,range,Iterable[int]]
                  ) -> Union[int,List[int]]:
        """
        `get_digit`

        Gets the specific digit's (or digits's) value at the specific index (or indexes).

        Arguments:
            `index` -- The index (or indexes) in question.

        Returns:
            The value (or values, contained in a `List`) found at the index.
        """
        if isinstance(index, int):
            return self._get_single_digit(index)

        if isinstance(index, slice):
            index = slice_to_range(index, self.digit_length())

        return list(self._get_single_digit(absindex(i, self.digit_length())) for i in index)
    __getitem__ = get_digit

    @overload
    def set_digit(self, index:int, value:Union[int,str]): ...
    @overload
    def set_digit(self, index:Union[slice,range,Iterable[int]], value:Iterable[Union[int,str]]): ...
    def set_digit(self, # noqa:301
                  index:Union[int,slice,range,Iterable[int]],
                  value:Union[int,str,Iterable[Union[int,str]]]
                  ):
        """
        `set_digit`

        Sets the digit (or digits) at the given index (or indexes) to the given value (or values).
        With multiple indexes and,
        there must be a matching quantity of values to set at those indexes.

        Arguments:
            `index` -- The index (or indexes) to set.
            `value` -- The value (or values) to set the index (or indexes) to.

        Raises:
            `ValueError`: Raised when the given value is out of bounds of the current `base`.
        """
        if isinstance(value, Iterable):
            value = (self._ensure_unnotated(v) for v in value)
        else:
            value = self._ensure_unnotated(value)

        if isinstance(value, int):
            value = [value]
        else:
            value = cast(List[int], list(value))

        if isinstance(index, int):
            index = (index, )
        elif isinstance(index, slice):
            index = slice_to_range(index, self.digit_length())

        sign = self.sign
        self.x = abs(self.x)

        for i, v in zip(index, value):
            if v < 0 or v >= self.base:
                raise ValueError("Digit value out of bounds of base")
            i = absindex(i, self.digit_length())
            if self.base == 2: # binary optimisable
                if v == 1:
                    self.x |= (0b1 << i)
                else:
                    self.x &= ~(0b1 << i)
            else:
                pv = self.base ** i
                mask = self._mask_value_continuous(i)
                self.x = (self.x - mask) + (v * pv)

        if sign != 0:
            self.x *= sign
    __setitem__ = set_digit

    def delete_digit(self, index:Union[int,slice,range,Iterable[int]]):
        """
        `delete_digit`

        Removes the value at the given index (or indexes).

        Arguments:
            `index` -- The index (or indexes) digit to be removed.
        """

        if isinstance(index, slice):
            index = slice_to_range(index, self.digit_length())

        if isinstance(index, int):
            index = (index, )

        for i in sorted(index, reverse=True):
            self.pop(i)
    __delitem__ = delete_digit

    def unset_digit(self, index:Union[int,slice,range,Iterable[int]]):
        """
        `unset_digit`

        Unsets (set to 0) the value at the given index (or indexes).

        Arguments:
            `index` -- The index (or indexes) digit to be unset.
        """
        if isinstance(index, slice):
            index = slice_to_range(index, self.digit_length())

        if isinstance(index, int):
            index = (index, )

        sign = self.sign
        self.x = abs(self.x)

        if self.base == 2: # binary optimization
            for i in index:
                i = absindex(i, self.digit_length())
                self.x = (self.x >> 1) << 1
        else:
            for i in index:
                i = absindex(i, self.digit_length())
                self.x -= self._mask_value_continuous(i)

        if sign != 0:
            self.x *= sign

    # NOTE: just like python handles it in bit_length, a value of 0 will always have no digits
    def digit_length(self) -> int:
        """
        `digit_length`

        Similar to `bit_length`, but relitive to the current `base`.
        Not to be confused with `digit_count`.

        Returns:
            The minimum necessary about of digits needed to display the number in full.
        """
        if self.x == 0:
            return 0

        if self.base == 2:
            return abs(self.x).bit_length()
        else:
            c = 0
            v = abs(self.x)
            while v != 0:
                c += 1
                v //= self.base
            return c
    __len__ = digit_length

    def insert(self, index:int, value:Union[int,str,Iterable[Union[int,str]]]):
        """
        `insert`

        Inserts the given value (or values, following the order in which they are supplied)
        before the given index.
        When given multiple values, each value will be inserted before the given index in order.

        Arguments:
            `index` -- The index (or indexes) to be inserted before.
            `value` -- The value (or values) to insert.
        """

        # not the current length, but the length this will have after the insert
        index = absindex(index, self.digit_length() + 1)

        if isinstance(value, Iterable):
            value = (self._ensure_unnotated(v) for v in value)
        else:
            value = self._ensure_unnotated(value)

        if isinstance(value, int):
            value = (value, )

        for v in value:
            if v < 0 or v >= self.base:
                raise ValueError("Digit value out of bounds of base")

            restore_sign = self.sign
            self.x = abs(self.x)

            if index == 0:
                self._prepend(v)
                continue

            high = self.copy()
            high.unset_digit(slice(0, index))

            if high.x != 0:
                self.x -= high.x
                high.x *= high.base
                self.x += high.x

            v = self.copy(v)
            v.x *= (v.base ** (index))
            self.x += v.x

            self.x *= restore_sign

    def pop(self, index:int = -1) -> int:
        """
        `pop`

        Gets the value at the given index while popping it.

        Keyword Arguments:
            index -- The target index to pop. Defaults to -1.

        Returns:
            The value at the given index before removal.
        """
        index = absindex(index, self.digit_length())

        if index == 0:
            return self._pop_first()

        popped = self._get_single_digit(index)
        high = self.copy()

        self.unset_digit(range(index, len(self)))
        high.unset_digit(range(0, index+1))

        if int(high) != 0:
            high.x //= high.base
            self.x += int(high)

        return popped

    def digit_count(self) -> int:
        """
        `digit_count`

        Similar to `bit_count`, but relitive to the current `base`.
        Not to be confused with `digit_length`.

        Returns:
            The amount of non-zero (non-unset) digits in the value.
        """
        if self.x == 0:
            return 0

        if self.base == 2:
            return abs(self.x).bit_count()
        else:
            c = 0
            v = abs(self.x)
            while v != 0:
                if v % self.base != 0:
                    c += 1
                v //= self.base
            return c

    # gets the specified digits with their place value as an int
    # a higher level implementation of the concept of bit masking done with binary numbers
    def mask(self, index:Union[int,slice,range,Iterable[int]]) -> int:
        """
        `mask`

        Similar to the concept of a 'bit mask', but on a arbitrary base.
        Returns a value with all digits unset except for the given index (or indexes).

        Arguments:
            index -- The index (or indexes) to mask the digits of.

        Returns:
            The value with all digits unset except for the given index (or indexes).
        """
        if isinstance(index, int):
            return self._mask_value_continuous(index)

        slices = []

        if isinstance(index, slice) and index.step == 1:
            slices = [index]
        else:
            if isinstance(index, slice):
                index = slice_to_range(index, self.digit_length())
            slices = iter_to_slices(cast(Iterable[int], index), self.digit_length())

        return sum(self._mask_value_continuous(s.start, s.stop - s.start) for s in slices)

    def digit_shift_left(self, amount:int = 1):
        """
        `digit_shift_left`

        Similar to a binary shift left, shifts the value left according to the set base.

        Arguments:
            amount -- The amount to shift left. Will shift right when negative.
        """
        if amount < 0:
            self.digit_shift_right(-amount)
            return

        if self.base == 2:
            self.x = (abs(self.x) << amount) * self.sign
        else:
            self.x *= (self.base**amount)

    def digit_shift_right(self, amount:int = 1):
        """
        `digit_shift_right`

        Similar to a binary shift right, shifts the value right according to the set base.

        Arguments:
            amount -- The amount to shift right. Will shift left when negative.
        """
        if amount < 0:
            self.digit_shift_left(-amount)
            return

        if self.base == 2:
            self.x = (abs(self.x) >> amount) * self.sign
        else:
            self.x //= (self.base**amount)

    def digit_rotate_left(self, amount:int = 1):
        """
        `digit_rotate_left`

        Similar to a binary rotate left, rotates the value left according to the set base.
        This will pop the digit at the largest signifiant place value and inserts
        it at the smallest place value spot.

        Arguments:
            amount -- The amount to rotate left. Will rotate right when negative.
        """

        if amount < 0:
            self.digit_rotate_right(-amount)

        for _ in range(amount):
            popped = self.pop(-1)
            self.digit_shift_left(1)
            self.x += popped

    def digit_rotate_right(self, amount:int = 1):
        """
        `digit_rotate_right`

        Similar to a binary rotate right, rotates the value right according to the set base.
        This will pop the digit at the smallest signifiant place value and inserts
        it at the largest place value spot.

        Arguments:
            amount -- The amount to rotate right. Will rotate left when negative.
        """

        if amount < 0:
            self.digit_rotate_left(-amount)

        for _ in range(amount):
            popped = self.pop(0)
            self.digit_shift_right(1)
            self.append(popped)

    def rstrip(self, value:Union[int,str,Iterable[Union[int,str]]]):
        """
        `rstrip`

        Removes the given digit (or digits) from the right hand (units) spot.
        Works similarly to ``str.rstrip``,
        except for the fact that when ``value`` is a single string
        (**not** a iterable of strings)
        it will be treated as a single digit instead of a iterable of digits.

        Arguments:
            value -- The value (or iterable of values) to strip.
                Can be either a intiger digit value, or a string corelating to a single digit.
        """

        if isinstance(value, Iterable):
            value = tuple(self._ensure_unnotated(v) for v in value)
        else:
            value = (self._ensure_unnotated(value), )

        if len(value) == 0:
            return

        while self.x != 0 and self._get_single_digit(0) in value:
            self._pop_first()

    def lstrip(self, value:Union[int,str,Iterable[Union[int,str]]]):
        """
        `rstrip`

        Removes the given digit (or digits) from the left hand (greatest place value) spot.
        Works similarly to ``str.lstrip``,
        except for the fact that when ``value`` is a single string
        (**not** a iterable of strings)
        it will be treated as a single digit instead of a iterable of digits.
        This will not strip any leading `0` digit values,
        as these are already not notated by default
        nor would effect the digit length of this intiger.

        Arguments:
            value -- The value (or iterable of values) to strip.
                Can be either a intiger digit value, or a string corelating to a single digit.
        """
        if isinstance(value, Iterable):
            value = [self._ensure_unnotated(v) for v in value]
        else:
            value = [self._ensure_unnotated(value)]

        value.remove(0)

        if len(value) == 0:
            return

        while self.x != 0 and self._get_single_digit(-1) in value:
            self.pop(-1)

    def strip(self, value:Union[int,str,Iterable[Union[int,str]]]):
        """
        `rstrip`

        Removes the given digit (or digits) from both sides.
        Works similarly to ``str.strip``,
        except for the fact that when ``value`` is a single string
        (**not** a iterable of strings)
        it will be treated as a single digit instead of a iterable of digits.
        This will not strip any leading `0` digit values,
        as these are already not notated by default
        nor would effect the digit length of this intiger.

        Arguments:
            value -- The value (or iterable of values) to strip.
                Can be either a intiger digit value, or a string corelating to a single digit.
        """
        self.rstrip(value)
        self.lstrip(value)

    def contains(self, value:Union[int, str]) -> bool:
        """
        `contains`

        Returns true if the digit value appears anywhere in this digit sequence.
        This will not include leading 0s,
        but will return true if the tested digit value is `0` and this intiger's value is also 0.

        Arguments:
            value -- The value to check for.
        """
        value = self._ensure_unnotated(value)

        if self.x == 0:
            return value == 0

        working_value = abs(self.x)
        while working_value > 0:
            if self.base == 2:
                if working_value & 0b1 == value:
                    return True
                working_value >>= 1
            else:
                if working_value % self.base == value:
                    return True
                working_value //= self.base
        return False
    __contains__ = contains

Ancestors

  • ExtendedUserInt
  • UserInt
  • typing.SupportsInt
  • typing.SupportsFloat
  • typing.SupportsAbs
  • typing.SupportsComplex
  • typing.SupportsRound
  • typing.SupportsIndex
  • typing.Protocol
  • typing.Generic
  • collections.abc.Hashable
  • collections.abc.MutableSequence
  • collections.abc.Sequence
  • collections.abc.Reversible
  • collections.abc.Collection
  • collections.abc.Sized
  • collections.abc.Iterable
  • collections.abc.Container

Subclasses

Instance variables

prop base : int

base

The base of the intiger. Must be at or above 2, as this class does not support any non-standard bases.

Expand source code
@property
def base(self) -> int:
    """
    `base`

    The base of the intiger.
    Must be at or above 2, as this class does not support any non-standard bases.
    """
    return self.__base
prop limit_high : Optional[int]

Inherited from: ExtendedUserInt.limit_high

limit_high …

prop limit_low : Optional[int]

Inherited from: ExtendedUserInt.limit_low

limit_low …

var on_changed

Inherited from: ExtendedUserInt.on_changed

on_changed

prop radix : int

radix

Returns

The absolute value of the base. Will always return base when used in a PositionalBasedIntiger, but may differ when using with a extended type.

Expand source code
@property
def radix(self) -> int:
    """
    radix

    Returns:
        The absolute value of the base.
        Will always return `base` when used in a `PositionalBasedIntiger`,
        but may differ when using with a extended type.
    """
    return abs(self.base)
prop sign : int

Inherited from: ExtendedUserInt.sign

sign

prop x : int

Inherited from: ExtendedUserInt.x

x

Methods

def bit_count(self) ‑> int

Inherited from: ExtendedUserInt.bit_count

bit_count

def bit_length(self) ‑> int

Inherited from: ExtendedUserInt.bit_length

bit_count

def contains(self, value: Union[int, str]) ‑> bool

contains

Returns true if the digit value appears anywhere in this digit sequence. This will not include leading 0s, but will return true if the tested digit value is 0 and this intiger's value is also 0.

Arguments

value – The value to check for.

def copy(self, value: Union[int, str, ForwardRef(None)] = None, base: Optional[int] = None, notation_format_override: Optional[NotationFormat] = None) ‑> PositionalBasedIntiger

copy

Creates a shallow copy of this object.

Keyword Arguments: value – When not None, will overrides the x value of the copy. base – When not None, will overrides the base of the copy. notation_format_override – When not None, will overrides the digint.notation_format of the copy.

Returns

The copy of the object.

def delete_digit(self, index: Union[int, slice, range, Iterable[int]])

delete_digit

Removes the value at the given index (or indexes).

Arguments

index – The index (or indexes) digit to be removed.

def digit_count(self) ‑> int

digit_count

Similar to bit_count, but relitive to the current base. Not to be confused with digit_length.

Returns

The amount of non-zero (non-unset) digits in the value.

def digit_length(self) ‑> int

digit_length

Similar to bit_length, but relitive to the current base. Not to be confused with digit_count.

Returns

The minimum necessary about of digits needed to display the number in full.

def digit_rotate_left(self, amount: int = 1)

digit_rotate_left

Similar to a binary rotate left, rotates the value left according to the set base. This will pop the digit at the largest signifiant place value and inserts it at the smallest place value spot.

Arguments

amount – The amount to rotate left. Will rotate right when negative.

def digit_rotate_right(self, amount: int = 1)

digit_rotate_right

Similar to a binary rotate right, rotates the value right according to the set base. This will pop the digit at the smallest signifiant place value and inserts it at the largest place value spot.

Arguments

amount – The amount to rotate right. Will rotate left when negative.

def digit_shift_left(self, amount: int = 1)

digit_shift_left

Similar to a binary shift left, shifts the value left according to the set base.

Arguments

amount – The amount to shift left. Will shift right when negative.

def digit_shift_right(self, amount: int = 1)

digit_shift_right

Similar to a binary shift right, shifts the value right according to the set base.

Arguments

amount – The amount to shift right. Will shift left when negative.

def fixed_sign_and(self, value: int) ‑> int

Inherited from: ExtendedUserInt.fixed_sign_and

fixed_sign_and

def fixed_sign_invert(self) ‑> int

Inherited from: ExtendedUserInt.fixed_sign_invert

fixed_sign_invert

def fixed_sign_or(self, value: int) ‑> int

Inherited from: ExtendedUserInt.fixed_sign_or

fixed_sign_or

def fixed_sign_xor(self, value: int) ‑> int

Inherited from: ExtendedUserInt.fixed_sign_xor

fixed_sign_xor

def get_digit(self, index: Union[int, slice, range, Iterable[int]]) ‑> Union[int, List[int]]

get_digit

Gets the specific digit's (or digits's) value at the specific index (or indexes).

Arguments

index – The index (or indexes) in question.

Returns

The value (or values, contained in a List) found at the index.

def insert(self, index: int, value: Union[int, str, Iterable[Union[int, str]]])

insert

Inserts the given value (or values, following the order in which they are supplied) before the given index. When given multiple values, each value will be inserted before the given index in order.

Arguments

index – The index (or indexes) to be inserted before. value – The value (or values) to insert.

def is_integer(self) ‑> bool

Inherited from: ExtendedUserInt.is_integer

is_integer

def iter_digits(self, at_least: int = 0) ‑> Iterator[int]

iter_digits Returns an iterable that iterates through the digit values of the integer, starting at the units spot. Will iterate 0 when all other digits are already iterated.

Keyword Arguments: at_least – Ensures that at least the given amount of digits are iterated, if above 1.

Returns

An iterator that returns the digit values, starting at the units spot.

Yields

The digits of the intiger, starting at the units spot.

def iter_symbols(self, at_least: int = 1) ‑> Iterator[str]

iter_symbols

Iterate the digit symbols starting at the units spot. When intending to use iteration for notation, it's suggested to use reversed_iter_symbols to avoid odering errors.

Keyword Arguments: at_least – ensures that at least the given amount of symbols are returned. Defaults to 1.

Returns

An iterator of symbols.

Yields

Digit symdols, starting at the units spot.

def lstrip(self, value: Union[int, str, Iterable[Union[int, str]]])

rstrip

Removes the given digit (or digits) from the left hand (greatest place value) spot. Works similarly to str.lstrip, except for the fact that when value is a single string (not a iterable of strings) it will be treated as a single digit instead of a iterable of digits. This will not strip any leading 0 digit values, as these are already not notated by default nor would effect the digit length of this intiger.

Arguments

value – The value (or iterable of values) to strip. Can be either a intiger digit value, or a string corelating to a single digit.

def mask(self, index: Union[int, slice, range, Iterable[int]]) ‑> int

mask

Similar to the concept of a 'bit mask', but on a arbitrary base. Returns a value with all digits unset except for the given index (or indexes).

Arguments

index – The index (or indexes) to mask the digits of.

Returns

The value with all digits unset except for the given index (or indexes).

def notate(self, notation_format: Optional[NotationFormat] = None) ‑> str

notate Notates the intiger, using the given notation format if possible, or the digint.notation_format set in the object's attributes if the paramater is not set.

Keyword Arguments: notation_format – A notation format to use over the one set in self.notation_format, if not None.

Raises

NotationError
Raised when both the argument and attribute digint.notation_format are None; or when other errors are raised during notation.

Returns

The final notation of the intiger.

def pop(self, index: int = -1) ‑> int

pop

Gets the value at the given index while popping it.

Keyword Arguments: index – The target index to pop. Defaults to -1.

Returns

The value at the given index before removal.

def reversed_iter_digits(self, at_least: int = 0) ‑> Iterator[int]

reversed_iter_digits Returns an iterable that iterates through the digit values of the integer, ending at the units spot. Will iterate 0 when all other digits are already iterated.

Keyword Arguments: at_least – Ensures that at least the given amount of digits are iterated, if above 1.

Returns

An iterator that returns the digit values, ending at the units spot.

Yields

The digits of the intiger, ending at the units spot.

def reversed_iter_symbols(self, at_least: int = 1) ‑> Iterator[str]

reversed_iter_symbols

Iterate the digit symbols ending at the units spot. Intending to be used for notation purposes, as the ordering for string notation is correct.

Keyword Arguments: at_least – ensures that at least the given amount of symbols are returned. Defaults to 1.

Returns

An iterator of symbols.

Yields

Digit symdols, ending at the units spot.

def rstrip(self, value: Union[int, str, Iterable[Union[int, str]]])

rstrip

Removes the given digit (or digits) from the right hand (units) spot. Works similarly to str.rstrip, except for the fact that when value is a single string (not a iterable of strings) it will be treated as a single digit instead of a iterable of digits.

Arguments

value – The value (or iterable of values) to strip. Can be either a intiger digit value, or a string corelating to a single digit.

def set_digit(self, index: Union[int, slice, range, Iterable[int]], value: Union[int, str, Iterable[Union[int, str]]])

set_digit

Sets the digit (or digits) at the given index (or indexes) to the given value (or values). With multiple indexes and, there must be a matching quantity of values to set at those indexes.

Arguments

index – The index (or indexes) to set. value – The value (or values) to set the index (or indexes) to.

Raises

ValueError: Raised when the given value is out of bounds of the current base.

def strip(self, value: Union[int, str, Iterable[Union[int, str]]])

rstrip

Removes the given digit (or digits) from both sides. Works similarly to str.strip, except for the fact that when value is a single string (not a iterable of strings) it will be treated as a single digit instead of a iterable of digits. This will not strip any leading 0 digit values, as these are already not notated by default nor would effect the digit length of this intiger.

Arguments

value – The value (or iterable of values) to strip. Can be either a intiger digit value, or a string corelating to a single digit.

def to_bytes(self, length: , byteorder: Literal['little', 'big'], *, signed: bool = False) ‑> bytes

Inherited from: ExtendedUserInt.to_bytes

to_bytes

def unset_digit(self, index: Union[int, slice, range, Iterable[int]])

unset_digit

Unsets (set to 0) the value at the given index (or indexes).

Arguments

index – The index (or indexes) digit to be unset.