Category talk:Wren-array

From Rosetta Code
Revision as of 15:28, 25 March 2024 by PureFox (talk | contribs) (Added ByteArray class.)

Source code

/* Module "array.wren" */

import "meta" for Meta
import "./check" for Check

/*
   Array represents a List whose size cannot be changed after it has been constructed
   but whose elements can be changed. If an array is created from a list, the
   list is shallow-copied, not cloned.
*/
class Array is Sequence {
    // Constructs a new array from a List or other Sequence.
    construct from(a) {
        Check.seq("Argument", a)
        _a = a.toList // create a list or shallow copy if the argument is already a list.
    }

    // Constructs a new array from a List or other Sequence by fitting it to a given size
    // truncating if it's too big or filling out with a given value if it's too small.
    construct fit(size, a, v) {
        Check.nonNegInt("Size", size)
        Check.seq("Second argument", a)
        a = a.toList
        if (a.count == size) {
            _a = a
        } else if (a.count > size) {
            _a = a[0...size]
        } else {
            _a = a
            for (i in a.count...size) _a.add(v)
        }
    }

    // Convenience version of 'fit' which uses a default value of null.
    static fit(size, a) { fit(size, a, null) }

    // Constructs a new array of a given size and sets all elements to the same value 'v'.
    construct new(size, v) {
        Check.nonNegInt("Size", size)
        _a = List.filled(size, v)
    }

    // Convenience version of 'new' which sets all elements to null.
    static new(size) { new(size, null) }

    // Property
    count { _a.count }  // returns the number of elements in the array

    // Creates a shallow copy of the current instance.
    copy() { Array.from(_a) }

    // Resets all elements of the array to 'v'.
    reset(v) {
        for (i in 0..._a.count) _a[i] = v
    }

    // Gets the element at 'index.' If index is negative, it counts backwards
    // from the end of the array where -1 is the last element.
    // If index is a range it creates a new array from the appropriate elements.
    [index] { (index is Range) ? Array.from(_a[index]) : _a[index] }

    // Sets the element at 'index'. Negative indices are treated as in the getter.
    [index]=(v) { _a[index] = v }

    // Returns the index of 'value' in the current instance or -1 if 'value' is not found.
    indexOf(value) { _a.indexOf(value) }

    // Returns the index of the last occurrence of 'value' in the current instance
    // or -1 if 'value' is not found.
    lastIndexOf(value) {
        if (_a.count == 0) return 0
        for (i in _a.count-1..0) {
            if (_a[i] == value) return i
        }
        return -1
    }

    // Replaces all occurrences of 'old' by 'new' in the current instance
    // and returns ['old', 'new'].
    replace(old, new) {
        for (i in 0..._a.count) {
            if (_a[i] == old) _a[i] = new
        }
        return [old, new]
    }

    // Sorts the elements of the array in place and both overloads work in exactly
    // the same manner as the corresponding methods in the List class.
    sort()         { _a.sort() }
    sort(comparer) { _a.sort(comparer) }

    // Swaps the elements at index1 and index2 within the array.
    swap(index1, index2) { _a.swap(index1, index2) }

    // Applies a function to each element of the array.
    apply(fn) {
        Check.func("fn", fn, 1)
        for (i in 0..._a.count) _a[i] = fn.call(_a[i])
    }

    // Iterator protocol methods.
    iterate(iterator) { _a.iterate(iterator) }
    iteratorValue(iterator) { _a.iteratorValue(iterator) }

    // Returns the string representation of the underlying list.
    toString { _a.toString }
}

/*
    ArrayType creates a named class which inherits from Array and always has the same
    size and default values. The named class has four constructors:
    1. new(v)    - sets all elements to 'v'
    2. new()     - sets all elements to the default value
    3. fit(a, v) - fits the sequence 'a' to 'size' filling out with 'v' if too short
    4. fit(a)    - as (3) but fills out with the default value if too short 
    and four instance methods of its own:
    5. default   - returns the default value
    6. toArray   - converts the current instance to an Array
    7. copy()    - creates a shallow copy of the current instance
                 - overriding the copy() method inherited from Array
    8. reset()   - resets all elements to the default value.
*/
class ArrayType {
    // Creates a class for the ArrayType (with an underscore after the name), with a
    // given size and default value for its elements, and returns a reference to it.
    static create(name, size, default) {
        Check.ident("Name", name)
        Check.nonNegInt("Size", size)
        name = name +  "_"
        var s = "class %(name) is Array {\n"
        s = s + "    construct new(v) {\n"
        s = s + "        super(%(size), v)\n"
        s = s + "    }\n"
        s = s + "    construct new()  {\n"
        s = s + "        super(%(size), %(default))\n"
        s = s + "    }\n"
        s = s + "    construct fit(a, v) {\n"
        s = s + "        super(%(size), a, v)\n"
        s = s + "    }\n"
        s = s + "    construct fit(a) {\n"
        s = s + "        super(%(size), a, %(default))\n"
        s = s + "    }\n"
        s = s + "    default { %(default) }\n"
        s = s + "    toArray() { Array.from(this) }\n"
        s = s + "    copy() {\n"
        s = s + "        var d = %(name).new()\n"
        s = s + "        for (i in 0...%(size)) d[i] = this[i]\n"
        s = s + "        return d\n"
        s = s + "    }\n"
        s = s + "    reset() { reset(%(default)) }\n}\n"
        s = s + "return %(name)"
        return Meta.compile(s).call()
    }

    // Convenience version of 'create' which always uses a default value of null.
    static create(name, size ) { create(name, size, null) }
}

/*
   BitArray represents a List<Bool> whose size cannot be changed after it has been constructed
   but whose elements can be changed. It uses only 1/32nd as much memory as a 'normal' List<Bool>
   but is around 4 times slower to index. Also, unlike List<Bool>, BitArray is not a Sequence.
*/
class BitArray {
    // Constructs a new BitArray of a given size and sets all elements to the same value 'v'.
    // 'size' is rounded to the higher multiple of 32 where necessary.
    construct new(size, v) {
        Check.posInt("size", size)
        Check.bool("value", v)
        _len = (size / 32).ceil
        _a = List.filled(_len, v ? 4294967295 : 0)
    }

    // Convenience version of 'new' which sets all elements to false.
    static new(size) { new(size, false) }

    // Returns the number of elements in the BitArray.
    count { 32 * _len }

    // Creates a copy of the current instance.
    copy() { 
        var c = BitArray.new(count, false)
        for (i in 0...count) c[i] = this[i]
        return c
    }

    // Resets all elements of the BitArray to 'v'.
    reset(v) {
        Check.bool("value", v)
        var value = v ? 4294967295 : 0
        for (i in 0..._len) _a[i] = value
    }

    // Gets the element at 'index'. If index is negative, it counts backwards
    // from the end of the array where -1 is the last element.
    // To maximize access speed, this method doesn't validate the index.
    // Use the 'get' method instead if you need to do that.
    [index] {
        if (index < 0) index = count + index
        var ix = (index/32).floor
        var bit = index%32
        return ((_a[ix] >> bit) & 1) == 1
    }

    // Sets the element at 'index'. Negative indices are treated as in the getter.
    // To maximize access speed, this method doesn't validate the index nor the new value.
    // Use the 'set' method instead if you need to do that.
    [index]=(v) {
        if (index < 0) index = count + index
        var ix = (index/32).floor
        var bit = index%32
        _a[ix] = v ?  _a[ix] | (1 << bit) : _a[ix] & ~(1 << bit)
    }

    // As [index] method but validates the index. 
    get(index) {
         Check.int("index", index, -count, count-1)
         return this[index]
    }

    // As [index]=(v) method but validates the index and the new value.
    set(index, v) {
         Check.int("index", index, -count, count-1)
         Check.bool("value", v)
         this[index] = v
    }
 
    // Returns a List<Bool> using the normal 8 bytes for each element.
    toList {
        var bools = List.filled(count, false)
        for (i in 0...count) bools[i] = this[i]
        return bools
    }

    // Returns an Array<Bool> using the normal 8 bytes for each element.
    toArray {
        var bools = Array.new(count, false)
        for (i in 0...count) bools[i] = this[i]
        return bools
    }

    // Returns a bit string representation of this BitArray.
    toString {
        var bytes = List.filled(count, 0)
        for (i in 0...count) if (this[i]) bytes[i] = 1
        return bytes.join()
    }
}

/*
   ByteArray represents a List<Byte> whose size cannot be changed after it has been constructed
   but whose elements can be changed. A 'Byte' for this purpose is an integral Num with a value
   between 0 and 255 inclusive. It uses only a quarter as much memory as a 'normal' List<Byte>
   but is around 4 times slower to index. Also, unlike List<Byte>, ByteArray is not a Sequence.
*/
class ByteArray {
    // Constructs a new ByteArray of a given size and sets all elements to the same value 'v'.
    // 'size' is rounded to the higher multiple of 4 where necessary.
    construct new(size, v) {
        Check.posInt("size", size)
        Check.int("value", v, 0, 255)
        _len = (size / 4).ceil
        // convert 'v' to a little-endian 32-bit unsigned integer.
        v = (v == 0) ? 0 : v | v << 8 | v << 16 | v << 24
        _a = List.filled(_len, v)
    }

    // Constructs a new ByteArray from a List<Byte>, optionally checking that the byte
    // values are valid. Where necessary, the size of the ByteArray is rounded to the
    // higher multiple of 4 and filled out with zero values.
    construct fromList(a, checkBytes) {
        Check.typedList("a", a, "Int", 1)
        Check.bool("checkBytes", checkBytes)
        _len = (a.count / 4).ceil
        _a = List.filled(_len, 0)
        if (checkBytes) {
            for (i in 0...a.count) {
                if (!(a[i].isInteger && a[i] >= 0 && a[i] < 256)) {
                    Fiber.abort("a[%(i)] = %(a[i]) is not a byte.")
                }
            }
        }
        for (i in 0..._len) {
            var j = i * 4
            if (i < _len - 1) {
                _a[i] = a[j] | a[j+1] << 8 | a[j+2] << 16 | a[j+3] << 24
            } else {
                var b2 = (j + 1 < a.count) ? a[j+1] : 0
                var b3 = (j + 2 < a.count) ? a[j+2] : 0
                var b4 = (j + 3 < a.count) ? a[j+3] : 0
                _a[i] = a[j] | b2 << 8 | b3 << 16 | b4 << 24
            }
        }
    }

    // Constructs a new ByteArray from a lower case hexadecimal string.
    // Where necessary, the size of the ByteArray is rounded to the
    // higher multiple of 4 and filled out with zero values.
    static fromHexString(hs) {
        Check.str("hs", hs, 2)
        if (hs.count % 2 != 0) {
            Fiber.abort("'hs' must contain an even number of hex digits >= 2.")
        }
        var digits = "0123456789abcdef"
        var bytes = List.filled(hs.count/2, 0)
        var i = 0
        while (i < hs.count-1) {
            bytes[i/2] = digits.indexOf(hs[i]) * 16 + digits.indexOf(hs[i+1])
            i = i + 2
        }
        return fromList(bytes, false)
    }

    // As 'fromList' except constructs the new ByteArray from an Array<Byte> instead.
    static fromArray(a, checkBytes) { fromList(a.toList, checkBytes) }

    // Convenience version of 'new' which sets all elements to zero.
    static new(size) { new(size, 0) }

    // Convenience version of 'fromList' which does not check that the byte values are valid.
    static fromList(a) { fromList(a, false) }

    // Convenience version of 'fromArray' which does not check that the byte values are valid.
    static fromArray(a) { fromList(a.toList, false) }

    // Returns the number of elements in the ByteArray.
    count { 4 * _len }

    // Creates a copy of the current instance.
    copy() {
        var c = ByteArray.new(count, 0)
        for (i in 0...count) c[i] = this[i]
        return c
    }

    // Resets all elements of the ByteArray to 'v'.
    reset(v) {
        Check.int("value", v, 0, 255)
        v = (v == 0) ? 0 : v | v << 8 | v << 16 | v << 24
        for (i in 0..._len) _a[i] = v
    }

    // Gets the element at 'index'. If index is negative, it counts backwards
    // from the end of the array where -1 is the last element.
    // To maximize access speed, this method doesn't validate the index.
    // Use the 'get' method instead if you need to do that.
    [index] {
        if (index < 0) index = count + index
        var ix = (index/4).floor
        var bit = (index%4) * 8
        return (_a[ix] >> bit) & 255
    }

    // Sets the element at 'index'. Negative indices are treated as in the getter.
    // To maximize access speed, this method doesn't validate the index nor the new value.
    // Use the 'set' method instead if you need to do that.
    [index]=(v) {
        if (index < 0) index = count + index
        var ix = (index/4).floor
        var bit = (index%4) * 8
        _a[ix] = (_a[ix] & ~(255 << bit)) | (v << bit)
    }

    // As [index] method but validates the index.
    get(index) {
         Check.int("index", index, -count, count-1)
         return this[index]
    }

    // As [index]=(v) method but validates the index and the new value.
    set(index, v) {
         Check.int("index", index, -count, count-1)
         Check.int("value", v, 0, 255)
         this[index] = v
    }

    // Returns a List<Byte> using the normal 8 bytes for each element.
    toList {
        var bytes = List.filled(count, 0)
        for (i in 0...count) bytes[i] = this[i]
        return bytes
    }

    // Returns an Array<Byte> using the normal 8 bytes for each element.
    toArray {
        var bytes = Array.new(count, 0)
        for (i in 0...count) bytes[i] = this[i]
        return bytes
    }

    // Returns a string representation of this instance as if it were a list.
    toString { toList.toString }

    // Returns a lower case hex string representation of this instance.
    toHexString {
        var digits = "0123456789abcdef"
        return toList.reduce("") { |acc, b| acc + digits[b>>4] + digits[b%16] }
    }
}