1# -*- coding: utf-8 -*-
2
3"""
4Integer Functions
5"""
6
7
8import sympy
9import string
10
11from mathics.version import __version__  # noqa used in loading to check consistency.
12
13from mathics.builtin.base import Builtin, SympyFunction
14from mathics.core.convert import from_sympy
15from mathics.core.expression import Integer, Integer0, String, Expression
16
17
18class Floor(SympyFunction):
19    """
20    <dl>
21      <dt>'Floor[$x$]'
22      <dd>gives the smallest integer less than or equal to $x$.
23
24      <dt>'Floor[$x$, $a$]'
25      <dd>gives the smallest multiple of $a$ less than or equal to $x$.
26    </dl>
27
28    >> Floor[10.4]
29     = 10
30    >> Floor[10/3]
31     = 3
32    >> Floor[10]
33     = 10
34    >> Floor[21, 2]
35     = 20
36    >> Floor[2.6, 0.5]
37     = 2.5
38    >> Floor[-10.4]
39     = -11
40
41    For complex $x$, take the floor of real an imaginary parts.
42    >> Floor[1.5 + 2.7 I]
43     = 1 + 2 I
44
45    For negative $a$, the smallest multiple of $a$ greater than or equal to $x$
46    is returned.
47    >> Floor[10.4, -1]
48     = 11
49    >> Floor[-10.4, -1]
50     = -10
51    """
52
53    attributes = ("Listable", "NumericFunction", "Protected")
54
55    sympy_name = "floor"
56    rules = {"Floor[x_, a_]": "Floor[x / a] * a"}
57
58    def apply_real(self, x, evaluation):
59        "Floor[x_]"
60        x = x.to_sympy()
61        if x is not None:
62            return from_sympy(sympy.floor(x))
63
64
65class Ceiling(SympyFunction):
66    """
67    <dl>
68       <dt>'Ceiling[$x$]'
69       <dd>gives the first integer greater than $x$.
70    </dl>
71
72    >> Ceiling[1.2]
73     = 2
74    >> Ceiling[3/2]
75     = 2
76
77    For complex $x$, take the ceiling of real an imaginary parts.
78    >> Ceiling[1.3 + 0.7 I]
79     = 2 + I
80    """
81
82    attributes = ("Listable", "NumericFunction", "Protected")
83
84    rules = {"Ceiling[x_, a_]": "Ceiling[x / a] * a"}
85
86    def apply(self, x, evaluation):
87        "Ceiling[x_]"
88        x = x.to_sympy()
89        if x is None:
90            return
91        return from_sympy(sympy.ceiling(x))
92
93
94class IntegerLength(Builtin):
95    """
96    <dl>
97    <dt>'IntegerLength[$x$]'
98        <dd>gives the number of digits in the base-10 representation of $x$.
99    <dt>'IntegerLength[$x$, $b$]'
100        <dd>gives the number of base-$b$ digits in $x$.
101    </dl>
102
103    >> IntegerLength[123456]
104     = 6
105    >> IntegerLength[10^10000]
106     = 10001
107    >> IntegerLength[-10^1000]
108     = 1001
109    'IntegerLength' with base 2:
110    >> IntegerLength[8, 2]
111     = 4
112    Check that 'IntegerLength' is correct for the first 100 powers of 10:
113    >> IntegerLength /@ (10 ^ Range[100]) == Range[2, 101]
114     = True
115    The base must be greater than 1:
116    >> IntegerLength[3, -2]
117     : Base -2 is not an integer greater than 1.
118     = IntegerLength[3, -2]
119
120    '0' is a special case:
121    >> IntegerLength[0]
122     = 0
123
124    #> IntegerLength /@ (10 ^ Range[100] - 1) == Range[1, 100]
125     = True
126    """
127
128    attributes = ("Listable", "Protected")
129
130    rules = {
131        "IntegerLength[n_]": "IntegerLength[n, 10]",
132    }
133
134    messages = {
135        "base": "Base `1` is not an integer greater than 1.",
136    }
137
138    def apply(self, n, b, evaluation):
139        "IntegerLength[n_, b_]"
140
141        n, b = n.get_int_value(), b.get_int_value()
142        if n is None or b is None:
143            evaluation.message("IntegerLength", "int")
144            return
145        if b <= 1:
146            evaluation.message("IntegerLength", "base", b)
147            return
148
149        if n == 0:
150            # special case
151            return Integer0
152
153        n = abs(n)
154
155        # O(log(digits))
156
157        # find bounds
158        j = 1
159        while b ** j <= n:
160            j *= 2
161        i = j // 2
162
163        # bisection
164        while i + 1 < j:
165            # assert b ** i <= n <= b ** j
166            k = (i + j) // 2
167            if b ** k <= n:
168                i = k
169            else:
170                j = k
171        return Integer(j)
172
173
174class BitLength(Builtin):
175    """
176    <dl>
177      <dt>'BitLength[$x$]'
178      <dd>gives the number of bits needed to represent the integer $x$. $x$'s sign is ignored.
179    </dl>
180
181    >> BitLength[1023]
182     = 10
183    >> BitLength[100]
184     = 7
185    >> BitLength[-5]
186     = 3
187    >> BitLength[0]
188     = 0
189    """
190
191    attributes = ("Listable", "Protected")
192
193    def apply(self, n, evaluation):
194        "BitLength[n_Integer]"
195        n = n.get_int_value()
196        if n < 0:
197            n = -1 - n
198        return Integer(n.bit_length())
199
200
201def _reversed_digits(
202    number, base
203):  # yield digits for number in base "base" in reverse order
204    number = abs(number)
205    if number == 0:
206        yield 0
207    else:
208        while number > 0:
209            rest, digit = divmod(number, base)
210            yield digit
211            number = rest
212
213
214def _pad(symbols, length, fill):  # pads "symbols" to length "length" using "fill"
215    pad_length = length - len(symbols)
216    if pad_length <= 0:
217        return symbols[-pad_length:]
218    else:
219        return fill * pad_length + symbols
220
221
222class IntegerString(Builtin):
223    """
224    <dl>
225    <dt>'IntegerString[$n$]'
226        <dd>returns the decimal representation of integer $x$ as string. $x$'s sign is ignored.
227    <dt>'IntegerString[$n$, $b$]'
228        <dd>returns the base $b$ representation of integer $x$ as string. $x$'s sign is ignored.
229    <dt>'IntegerString[$n$, $b$, $length$]'
230        <dd>returns a string of length $length$. If the number is too short, the string gets padded
231        with 0 on the left. If the number is too long, the $length$ least significant digits are
232        returned.
233    </dl>
234
235    For bases > 10, alphabetic characters a, b, ... are used to represent digits 11, 12, ... . Note
236    that base must be an integer in the range from 2 to 36.
237
238    >> IntegerString[12345]
239     = 12345
240    >> IntegerString[-500]
241     = 500
242    >> IntegerString[12345, 10, 8]
243     = 00012345
244    >> IntegerString[12345, 10, 3]
245     = 345
246    >> IntegerString[11, 2]
247     = 1011
248    >> IntegerString[123, 8]
249     = 173
250    >> IntegerString[32767, 16]
251     = 7fff
252    >> IntegerString[98765, 20]
253     = c6i5
254    """
255
256    attributes = ("Listable", "Protected")
257
258    rules = {
259        "IntegerString[n_Integer]": "IntegerString[n, 10]",
260    }
261
262    messages = {
263        "basf": "Base `` must be an integer in the range from 2 to 36.",
264    }
265
266    list_of_symbols = string.digits + string.ascii_letters
267
268    _python_builtin = {
269        16: lambda number: hex(abs(number))[2:],
270        10: lambda number: str(abs(number)),
271        2: lambda number: bin(abs(number))[2:],
272        # oct() changed definition for Python 3
273    }
274
275    def _symbols(self, n, b, evaluation):
276        builtin = IntegerString._python_builtin.get(b)
277        if builtin:
278            return builtin(n)
279        else:
280            list_of_symbols = IntegerString.list_of_symbols
281            if b > len(list_of_symbols) or b < 2:
282                evaluation.message("IntegerString", "basf", b)
283                return False
284            else:
285                return "".join(
286                    reversed([list_of_symbols[r] for r in _reversed_digits(n, b)])
287                )
288
289    def apply_n(self, n, b, evaluation):
290        "IntegerString[n_Integer, b_Integer]"
291        s = self._symbols(n.get_int_value(), b.get_int_value(), evaluation)
292        return String(s) if s else None
293
294    def apply_n_b_length(self, n, b, length, evaluation):
295        "IntegerString[n_Integer, b_Integer, length_Integer]"
296        s = self._symbols(n.get_int_value(), b.get_int_value(), evaluation)
297        return String(_pad(s, length.get_int_value(), "0")) if s else None
298
299
300class _IntBaseBuiltin(Builtin):
301    messages = {
302        "basf": "Base `` must be an integer greater than 1.",
303    }
304
305    def _valid_base(self, b, evaluation):
306        base = b.get_int_value()
307        if base < 2:
308            evaluation.message(self.get_name(), "basf", base)
309            return False
310        else:
311            return base
312
313
314class IntegerDigits(_IntBaseBuiltin):
315    """
316    <dl>
317    <dt>'IntegerDigits[$n$]'
318        <dd>returns the decimal representation of integer $x$ as list of digits. $x$'s sign is ignored.
319    <dt>'IntegerDigits[$n$, $b$]'
320        <dd>returns the base $b$ representation of integer $x$ as list of digits. $x$'s sign is ignored.
321    <dt>'IntegerDigits[$n$, $b$, $length$]'
322        <dd>returns a list of length $length$. If the number is too short, the list gets padded
323        with 0 on the left. If the number is too long, the $length$ least significant digits are
324        returned.
325    </dl>
326
327    >> IntegerDigits[12345]
328     = {1, 2, 3, 4, 5}
329    >> IntegerDigits[-500]
330     = {5, 0, 0}
331    >> IntegerDigits[12345, 10, 8]
332     = {0, 0, 0, 1, 2, 3, 4, 5}
333    >> IntegerDigits[12345, 10, 3]
334     = {3, 4, 5}
335    >> IntegerDigits[11, 2]
336     = {1, 0, 1, 1}
337    >> IntegerDigits[123, 8]
338     = {1, 7, 3}
339    >> IntegerDigits[98765, 20]
340     = {12, 6, 18, 5}
341    """
342
343    rules = {
344        "IntegerDigits[n_Integer]": "IntegerDigits[n, 10]",
345    }
346
347    _padding = [Integer0]
348
349    def apply_n_b(self, n, b, evaluation):
350        "IntegerDigits[n_Integer, b_Integer]"
351        base = self._valid_base(b, evaluation)
352        return (
353            Expression(
354                "List",
355                *[
356                    Integer(d)
357                    for d in reversed(list(_reversed_digits(n.get_int_value(), base)))
358                ]
359            )
360            if base
361            else None
362        )
363
364    def apply_n_b_length(self, n, b, length, evaluation):
365        "IntegerDigits[n_Integer, b_Integer, length_Integer]"
366        base = self._valid_base(b, evaluation)
367        return (
368            Expression(
369                "List",
370                *_pad(
371                    [
372                        Integer(d)
373                        for d in reversed(
374                            list(_reversed_digits(n.get_int_value(), base))
375                        )
376                    ],
377                    length.get_int_value(),
378                    self._padding,
379                )
380            )
381            if base
382            else None
383        )
384
385
386class DigitCount(_IntBaseBuiltin):
387    """
388    <dl>
389    <dt>'DigitCount[$n$, $b$, $d$]'
390        <dd>returns the number of times digit $d$ occurs in the base $b$ representation of $n$.
391    <dt>'DigitCount[$n$, $b$]'
392        <dd>returns a list indicating the number of times each digit occurs in the base $b$ representation of $n$.
393    <dt>'DigitCount[$n$, $b$]'
394        <dd>returns a list indicating the number of times each digit occurs in the decimal representation of $n$.
395    </dl>
396
397    >> DigitCount[1022]
398     = {1, 2, 0, 0, 0, 0, 0, 0, 0, 1}
399    >> DigitCount[Floor[Pi * 10^100]]
400     = {8, 12, 12, 10, 8, 9, 8, 12, 14, 8}
401    >> DigitCount[1022, 2]
402     = {9, 1}
403    >> DigitCount[1022, 2, 1]
404     = 9
405    """
406
407    rules = {
408        "DigitCount[n_Integer]": "DigitCount[n, 10]",
409    }
410
411    def apply_n_b_d(self, n, b, d, evaluation):
412        "DigitCount[n_Integer, b_Integer, d_Integer]"
413        base = self._valid_base(b, evaluation)
414        if not base:
415            return
416        match = d.get_int_value()
417        return Integer(
418            sum(
419                1
420                for digit in _reversed_digits(n.get_int_value(), base)
421                if digit == match
422            )
423        )
424
425    def apply_n_b(self, n, b, evaluation):
426        "DigitCount[n_Integer, b_Integer]"
427        base = self._valid_base(b, evaluation)
428        if not base:
429            return
430        occurence_count = [0] * base
431        for digit in _reversed_digits(n.get_int_value(), base):
432            occurence_count[digit] += 1
433        # result list is rotated by one element to the left
434        return Expression("List", *(occurence_count[1:] + [occurence_count[0]]))
435
436
437class IntegerReverse(_IntBaseBuiltin):
438    """
439    <dl>
440    <dt>'IntegerReverse[$n$]'
441        <dd>returns the integer that has the reverse decimal representation of $x$ without sign.
442    <dt>'IntegerReverse[$n$, $b$]'
443        <dd>returns the integer that has the reverse base $b$ represenation of $x$ without sign.
444    </dl>
445
446    >> IntegerReverse[1234]
447     = 4321
448    >> IntegerReverse[1022, 2]
449     = 511
450    >> IntegerReverse[-123]
451     = 321
452    """
453
454    rules = {
455        "IntegerReverse[n_Integer]": "IntegerReverse[n, 10]",
456    }
457
458    def apply_n_b(self, n, b, evaluation):
459        "IntegerReverse[n_Integer, b_Integer]"
460        base = self._valid_base(b, evaluation)
461        if not base:
462            return
463        value = 0
464        for digit in _reversed_digits(n.get_int_value(), base):
465            value = value * base + digit
466        return Integer(value)
467
468
469class FromDigits(Builtin):
470    """
471    <dl>
472    <dt>'FromDigits[$l$]'
473        <dd>returns the integer corresponding to the decimal representation given by $l$. $l$ can be a list of
474        digits or a string.
475    <dt>'FromDigits[$l$, $b$]'
476        <dd>returns the integer corresponding to the base $b$ representation given by $l$. $l$ can be a list of
477        digits or a string.
478    </dl>
479
480    >> FromDigits["123"]
481     = 123
482    >> FromDigits[{1, 2, 3}]
483     = 123
484    >> FromDigits[{1, 0, 1}, 1000]
485     = 1000001
486
487    FromDigits can handle symbolic input:
488    >> FromDigits[{a, b, c}, 5]
489     = c + 5 (5 a + b)
490
491    Note that FromDigits does not automatically detect if you are providing a non-decimal representation:
492    >> FromDigits["a0"]
493     = 100
494    >> FromDigits["a0", 16]
495     = 160
496
497    FromDigits on empty lists or strings returns 0:
498    >> FromDigits[{}]
499     = 0
500    >> FromDigits[""]
501     = 0
502
503    #> FromDigits[x]
504     : The input must be a string of digits or a list.
505     = FromDigits[x, 10]
506    """
507
508    rules = {"FromDigits[l_]": "FromDigits[l, 10]"}
509
510    messages = {"nlst": "The input must be a string of digits or a list."}
511
512    @staticmethod
513    def _parse_string(s, b):
514        code_0 = ord("0")
515        code_a = ord("a")
516        assert code_a > code_0
517
518        value = Integer0
519        for char in s.lower():
520            code = ord(char)
521            if code >= code_a:
522                digit = 10 + code - code_a
523            else:
524                digit = code - code_0
525            if 0 <= digit < 36:
526                value = Expression(
527                    "Plus", Expression("Times", value, b), Integer(digit)
528                )
529            else:
530                return None
531
532        return value
533
534    def apply(self, l, b, evaluation):
535        "FromDigits[l_, b_]"
536        if l.get_head_name() == "System`List":
537            value = Integer0
538            for leaf in l.leaves:
539                value = Expression("Plus", Expression("Times", value, b), leaf)
540            return value
541        elif isinstance(l, String):
542            value = FromDigits._parse_string(l.get_string_value(), b)
543            if value is None:
544                evaluation.message("FromDigits", "nlst")
545            else:
546                return value
547        else:
548            evaluation.message("FromDigits", "nlst")
549