Category talk:Wren-array: Difference between revisions

From Rosetta Code
Content added Content deleted
(Added ByteArray class.)
(Added indexOf(value, start) method to all classes.)
 
(2 intermediate revisions by the same user not shown)
Line 66: Line 66:
// Returns the index of 'value' in the current instance or -1 if 'value' is not found.
// Returns the index of 'value' in the current instance or -1 if 'value' is not found.
indexOf(value) { _a.indexOf(value) }
indexOf(value) { _a.indexOf(value) }

// As indexOf(value) method but starts the search from index 'start'. If 'start' is
// negative it counts backwards from the end of the array.
indexOf(value, start) {
if (count == 0) return -1
if (start < 0) start = count + start
if (start >= count) Fiber.abort("'start' is out of bounds.")
for (i in start...count) {
if (_a[i] == value) return i
}
return -1
}


// Returns the index of the last occurrence of 'value' in the current instance
// Returns the index of the last occurrence of 'value' in the current instance
Line 161: Line 173:
BitArray represents a List<Bool> whose size cannot be changed after it has been constructed
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 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.
but is around 4 times slower to index.
*/
*/
class BitArray {
class BitArray is Sequence {
// Constructs a new BitArray of a given size and sets all elements to the same value 'v'.
// 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.
// 'size' is rounded to the higher multiple of 32 where necessary.
Line 171: Line 183:
_len = (size / 32).ceil
_len = (size / 32).ceil
_a = List.filled(_len, v ? 4294967295 : 0)
_a = List.filled(_len, v ? 4294967295 : 0)
_rng = 0..._len * 32
}
}


Line 182: Line 195:
copy() {
copy() {
var c = BitArray.new(count, false)
var c = BitArray.new(count, false)
for (i in 0...count) c[i] = this[i]
for (i in _rng) c[i] = this[i]
return c
return c
}
}
Line 226: Line 239:
this[index] = v
this[index] = v
}
}

// Returns the index of 'v' in the current instance or -1 if 'v' is not found.
indexOf(v) {
for (i in _rng) if (this[i] == v) return i
return -1
}

// As indexOf(v) method but starts the search from index 'start'. If 'start' is
// negative it counts backwards from the end of the array.
indexOf(v, start) {
if (start < 0) start = count + start
if (start >= _rng.to) Fiber.abort("'start' is out of bounds.")
for (i in start..._rng.to) {
if (this[i] == v) return i
}
return -1
}

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

// Swaps the elements at index1 and index2 within the BitArray.
swap(index1, index2) {
var t = this[index1]
this[index1] = this[index2]
this[index2] = t
}

// Iterator protocol methods.
iterate(iterator) { _rng.iterate(iterator) }
iteratorValue(iterator) { this[iterator] }

// Returns a List<Bool> using the normal 8 bytes for each element.
// Returns a List<Bool> using the normal 8 bytes for each element.
toList {
toList {
var bools = List.filled(count, false)
var bools = List.filled(count, false)
for (i in 0...count) bools[i] = this[i]
for (i in _rng) bools[i] = this[i]
return bools
return bools
}
}
Line 237: Line 285:
toArray {
toArray {
var bools = Array.new(count, false)
var bools = Array.new(count, false)
for (i in 0...count) bools[i] = this[i]
for (i in _rng) bools[i] = this[i]
return bools
return bools
}
}
Line 244: Line 292:
toString {
toString {
var bytes = List.filled(count, 0)
var bytes = List.filled(count, 0)
for (i in 0...count) if (this[i]) bytes[i] = 1
for (i in _rng) if (this[i]) bytes[i] = 1
return bytes.join()
return bytes.join()
}
}
Line 253: Line 301:
but whose elements can be changed. A 'Byte' for this purpose is an integral Num with a value
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>
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.
but is around 4 times slower to index.
*/
*/
class ByteArray {
class ByteArray is Sequence {
// Constructs a new ByteArray of a given size and sets all elements to the same value 'v'.
// 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.
// 'size' is rounded to the higher multiple of 4 where necessary.
Line 265: Line 313:
v = (v == 0) ? 0 : v | v << 8 | v << 16 | v << 24
v = (v == 0) ? 0 : v | v << 8 | v << 16 | v << 24
_a = List.filled(_len, v)
_a = List.filled(_len, v)
_rng = 0...4 *_len
}
}


Line 293: Line 342:
}
}
}
}
_rng = 0...4 *_len
}
}


Line 331: Line 381:
copy() {
copy() {
var c = ByteArray.new(count, 0)
var c = ByteArray.new(count, 0)
for (i in 0...count) c[i] = this[i]
for (i in _rng) c[i] = this[i]
return c
return c
}
}
Line 375: Line 425:
this[index] = v
this[index] = v
}
}

// Returns the index of 'v' in the current instance or -1 if 'v' is not found.
indexOf(v) {
for (i in _rng) if (this[i] == v) return i
return -1
}
// As indexOf(v) method but starts the search from index 'start'. If 'start' is
// negative it counts backwards from the end of the array.
indexOf(v, start) {
if (start < 0) start = count + start
if (start >= _rng.to) Fiber.abort("'start' is out of bounds.")
for (i in start..._rng.to) {
if (this[i] == v) return i
}
return -1
}

// Returns the index of the last occurrence of 'v' in the current instance
// or -1 if 'v' is not found.
lastIndexOf(v) {
for (i in count-1..0) if (this[i] == v) 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 _rng) if (this[i] == old) this[i] = new
return [old, new]
}

// Swaps the elements at index1 and index2 within the ByteArray.
swap(index1, index2) {
var t = this[index1]
this[index1] = this[index2]
this[index2] = t
}

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

// Iterator protocol methods.
iterate(iterator) { _rng.iterate(iterator) }
iteratorValue(iterator) { this[iterator] }


// Returns a List<Byte> using the normal 8 bytes for each element.
// Returns a List<Byte> using the normal 8 bytes for each element.
toList {
toList {
var bytes = List.filled(count, 0)
var bytes = List.filled(count, 0)
for (i in 0...count) bytes[i] = this[i]
for (i in _rng) bytes[i] = this[i]
return bytes
return bytes
}
}
Line 386: Line 483:
toArray {
toArray {
var bytes = Array.new(count, 0)
var bytes = Array.new(count, 0)
for (i in 0...count) bytes[i] = this[i]
for (i in _rng) bytes[i] = this[i]
return bytes
return bytes
}
}
Line 398: Line 495:
return toList.reduce("") { |acc, b| acc + digits[b>>4] + digits[b%16] }
return toList.reduce("") { |acc, b| acc + digits[b>>4] + digits[b%16] }
}
}
}

/*
CharArray represents a List<Char> whose size cannot be changed after it has been constructed
but whose elements can be changed. A 'Char' for this purpose is a unicode character with a
codepoint less than 256 (i.e. Latin-1). Internally, a ByteArray is used for storage.
This means that it only uses a quarter as much memory as a 'normal' List<Byte> (or about an
eighth as much as a List of single character strings) but is around 5 times slower to index.
*/
class CharArray is Sequence {
// Constructs a new CharArray of a given size and sets all elements to the same Char 'c'.
// As a ByteArray is used for storage, its size is rounded to the higher multiple of 4
// where necessary.
construct new(size, c) {
Check.char("c", c, 0, 255)
_a = ByteArray.new(size, c.codePoints[0])
_rng = 0..._a.count
}

// Constructs a new CharArray from a List<Char>, checking that the character values
// are valid. As a ByteArray is used for storage, its size is rounded to the
// higher multiple of 4 and filled out with space characters, where necessary.
construct fromList(a) {
Check.typedList("a", a, String, 1)
var size = (a.count / 4).ceil * 4
var ca = List.filled(size, 32)
for (i in 0...a.count) {
Check.char("a[%(i)]", a[i], 0, 255)
ca[i] = a[i].codePoints[0]
}
_a = ByteArray.fromList(ca, false)
_rng = 0...size
}

// Constructs a new ByteArray from a string of Chars.
// As a ByteArray is used for storage, its size is rounded to the higher
// multiple of 4 and filled out with space characters, where necessary.
construct fromString(s) {
Check.str("s", s, 1)
var size = (s.count / 4).ceil * 4
if (s.count < size) s = s + " " * (size - s.count)
_a = ByteArray.fromList(s.codePoints.toList, true)
_rng = 0...size
}

// As 'fromList' except constructs the new CharArray from an Array<Char> instead.
static fromArray(a) { fromList(a.toList) }

// Convenience version of 'new' which sets all elements to the space character.
static new(size) { new(size, " ") }

// Returns the number of elements in the CharArray.
count { _a.count }

// Creates a copy of the current instance.
copy() {
var ca = CharArray.new(count, " ")
for (i in _rng) ca[i] = this[i]
return ca
}

// Resets all elements of the CharArray to 'c'.
reset(c) {
Check.char("c", c, 0, 255)
_a.reset(c.codePoints[0])
}

// 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] { String.fromCodePoint(_a[index]) }

// 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]=(c) {
_a[index] = c.codePoints[0]
}

// As [index] method but validates the index.
get(index) { String.fromCodePoint(_a.get(index)) }

// As [index]=(c) method but validates the index and the new value.
set(index, c) {
Check.char("c", c)
_a.set(index, c.codePoints[0])
}
// Writes the string 's' into the CharArray buffer starting from index 'start'.
// Throws an error if 'start' is out of range, if 's' is too long to fit into
// the buffer or if it contains an invalid Char.
write(start, s) {
Check.str("s", s, 1)
var i = 0
for (cp in s.codePoints) {
_a.set(start + i, cp)
i = i + 1
}
}

// Returns the index of 'c' in the current instance or -1 if 'c' is not found.
// Throws an error if 'c' is an invalid Char.
indexOf(c) {
Check.char("c", c, 0, 255)
return _a.indexOf(c.codePoints[0])
}

// As indexOf(c) method but starts the search from index 'start'. If 'start' is
// negative it counts backwards from the end of the array.
indexOf(c, start) {
Check.char("c", c, 0, 255)
return _a.indexOf(c.codePoints[0], start)
}

// Returns the index of the last occurrence of 'c' in the current instance
// or -1 if 'c' is not found. Throws an error if 'c' is an invalid Char.
lastIndexOf(c) {
Check.char("c", c, 0, 255)
for (i in count-1..0) if (this[i] == c) return i
return -1
}

// Replaces all occurrences of 'old' by 'new' in the current instance
// and returns ['old', 'new'].
replace(old, new) {
Check.char("old", old, 0, 255)
Check.char("new", new, 0, 255)
for (i in _rng) if (this[i] == old) this[i] = new
return [old, new]
}

// Swaps the elements at index1 and index2 within the CharArray.
swap(index1, index2) {
var t = this[index1]
this[index1] = this[index2]
this[index2] = t
}

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

// Converts 'in place' all upper case Chars in this instance to lower case.
toLower {
for (i in _rng) {
var c = this[i].codePoints[0]
if ((c >= 65 && c <= 90) || (c >= 192 && c <= 214) || (c >= 216 && c <= 222)) {
this[i] = String.fromCodePoint(c + 32)
}
}
}

// Converts 'in place' all lower case Chars in this instance to upper case.
toUpper {
for (i in _rng) {
var c = this[i].codePoints[0]
if ((c >= 97 && c <= 122) || (c >= 224 && c <= 246) || (c >= 248 && c <= 254)) {
this[i] = String.fromCodePoint(c - 32)
}
}
}

// Capitalizes 'in place' the first Char of this instance.
capitalize {
var c = this[0].codePoints[0]
if ((c >= 97 && c <= 122) || (c >= 224 && c <= 246) || (c >= 248 && c <= 254)) {
this[0] = String.fromCodePoint(c - 32)
}
}

// Iterator protocol methods.
iterate(iterator) { _rng.iterate(iterator) }
iteratorValue(iterator) { this[iterator] }

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

// Returns an Array<Char> using the normal 8 bytes for each element.
toArray {
var chars = List.filled(count, null)
for (i in _rng) chars[i] = this[i]
return chars
}

// Returns a string representation of this instance, optionally trimming trailing
// whitespace (space, tab, carriage return, and line feed characters).
toString(trimEnd) {
var res = toList.join()
return trimEnd ? res : res.trimEnd()
}

// Returns a string representation of this instance with trailing whitespace removed.
toString { toList.join() }

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

Latest revision as of 10:29, 30 March 2024

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) }

    // As indexOf(value) method but starts the search from index 'start'. If 'start' is
    // negative it counts backwards from the end of the array.
    indexOf(value, start) {
        if (count == 0) return -1
        if (start < 0) start = count + start
        if (start >= count) Fiber.abort("'start' is out of bounds.")
        for (i in start...count) {
            if (_a[i] == value) return i
        }
        return -1
    }

    // 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.
*/
class BitArray is Sequence {
    // 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)
        _rng = 0..._len * 32
    }

    // 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 _rng) 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 the index of 'v' in the current instance or -1 if 'v' is not found.
    indexOf(v) {
         for (i in _rng) if (this[i] == v) return i
         return -1
    }

    // As indexOf(v) method but starts the search from index 'start'. If 'start' is
    // negative it counts backwards from the end of the array.
    indexOf(v, start) {
        if (start < 0) start = count + start
        if (start >= _rng.to) Fiber.abort("'start' is out of bounds.")
        for (i in start..._rng.to) {
            if (this[i] == v) return i
        }
        return -1
    }

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

    // Swaps the elements at index1 and index2 within the BitArray.
    swap(index1, index2) { 
        var t = this[index1]
        this[index1] = this[index2]
        this[index2] = t        
    }

    // Iterator protocol methods.
    iterate(iterator)       { _rng.iterate(iterator) }
    iteratorValue(iterator) { this[iterator] }

    // Returns a List<Bool> using the normal 8 bytes for each element.
    toList {
        var bools = List.filled(count, false)
        for (i in _rng) 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 _rng) bools[i] = this[i]
        return bools
    }

    // Returns a bit string representation of this BitArray.
    toString {
        var bytes = List.filled(count, 0)
        for (i in _rng) 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.
*/
class ByteArray is Sequence {
    // 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)
        _rng = 0...4 *_len
    }

    // 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
            }
        }
        _rng = 0...4 *_len
    }

    // 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 _rng) 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 the index of 'v' in the current instance or -1 if 'v' is not found.
    indexOf(v) {
         for (i in _rng) if (this[i] == v) return i
         return -1
    }
    // As indexOf(v) method but starts the search from index 'start'. If 'start' is
    // negative it counts backwards from the end of the array.
    indexOf(v, start) {
        if (start < 0) start = count + start
        if (start >= _rng.to) Fiber.abort("'start' is out of bounds.")
        for (i in start..._rng.to) {
            if (this[i] == v) return i
        }
        return -1
    }

    // Returns the index of the last occurrence of 'v' in the current instance
    // or -1 if 'v' is not found.
    lastIndexOf(v) {
        for (i in count-1..0) if (this[i] == v) 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 _rng) if (this[i] == old) this[i] = new
        return [old, new]
    }

    // Swaps the elements at index1 and index2 within the ByteArray.
    swap(index1, index2) { 
        var t = this[index1]
        this[index1] = this[index2]
        this[index2] = t        
    }

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

    // Iterator protocol methods.
    iterate(iterator)       { _rng.iterate(iterator) }
    iteratorValue(iterator) { this[iterator] }

    // Returns a List<Byte> using the normal 8 bytes for each element.
    toList {
        var bytes = List.filled(count, 0)
        for (i in _rng) 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 _rng) 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] }
    }
}

/*
   CharArray represents a List<Char> whose size cannot be changed after it has been constructed
   but whose elements can be changed. A 'Char' for this purpose is a unicode character with a
   codepoint less than 256 (i.e. Latin-1). Internally, a ByteArray is used for storage.
   This means that it only uses a quarter as much memory as a 'normal' List<Byte> (or about an
   eighth as much as a List of single character strings) but is around 5 times slower to index.
*/
class CharArray is Sequence {
    // Constructs a new CharArray of a given size and sets all elements to the same Char 'c'.
    // As a ByteArray is used for storage, its size is rounded to the higher multiple of 4
    // where necessary.
    construct new(size, c) {
        Check.char("c", c, 0, 255)
        _a = ByteArray.new(size, c.codePoints[0])
        _rng = 0..._a.count
    }

    // Constructs a new CharArray from a List<Char>, checking that the character values
    // are valid. As a ByteArray is used for storage, its size is rounded to the
    // higher multiple of 4 and filled out with space characters, where necessary.
    construct fromList(a) {
        Check.typedList("a", a, String, 1)
        var size = (a.count / 4).ceil * 4
        var ca = List.filled(size, 32)
        for (i in 0...a.count) {
            Check.char("a[%(i)]", a[i], 0, 255)
            ca[i] = a[i].codePoints[0]
        }
        _a = ByteArray.fromList(ca, false)
        _rng = 0...size
    }

    // Constructs a new ByteArray from a string of Chars.
    // As a ByteArray is used for storage, its size is rounded to the higher
    // multiple of 4 and filled out with space characters, where necessary.
    construct fromString(s) {
        Check.str("s", s, 1)
        var size = (s.count / 4).ceil * 4
        if (s.count < size) s = s + " " * (size - s.count)
        _a = ByteArray.fromList(s.codePoints.toList, true)
        _rng = 0...size
    }

    // As 'fromList' except constructs the new CharArray from an Array<Char> instead.
    static fromArray(a) { fromList(a.toList) }

    // Convenience version of 'new' which sets all elements to the space character.
    static new(size) { new(size, " ") }

    // Returns the number of elements in the CharArray.
    count { _a.count }

    // Creates a copy of the current instance.
    copy() {
        var ca = CharArray.new(count, " ")
        for (i in _rng) ca[i] = this[i]
        return ca
    }

    // Resets all elements of the CharArray to 'c'.
    reset(c) {
        Check.char("c", c, 0, 255)
        _a.reset(c.codePoints[0])
    }

    // 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] { String.fromCodePoint(_a[index]) }

    // 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]=(c) {
        _a[index] = c.codePoints[0]
    }

    // As [index] method but validates the index.
    get(index) { String.fromCodePoint(_a.get(index)) }

    // As [index]=(c) method but validates the index and the new value.
    set(index, c) {
        Check.char("c", c)        
        _a.set(index, c.codePoints[0])
    }
    // Writes the string 's' into the CharArray buffer starting from index 'start'.
    // Throws an error if 'start' is out of range, if 's' is too long to fit into
    // the buffer or if it contains an invalid Char.
    write(start, s) {
        Check.str("s", s, 1)
        var i = 0
        for (cp in s.codePoints) {
            _a.set(start + i, cp)
            i = i + 1
        }
    }

    // Returns the index of 'c' in the current instance or -1 if 'c' is not found.
    // Throws an error if 'c' is an invalid Char.
    indexOf(c) {
        Check.char("c", c, 0, 255)
        return _a.indexOf(c.codePoints[0])
    }

    // As indexOf(c) method but starts the search from index 'start'. If 'start' is
    // negative it counts backwards from the end of the array.
    indexOf(c, start) {
        Check.char("c", c, 0, 255)
        return _a.indexOf(c.codePoints[0], start)
    }

    // Returns the index of the last occurrence of 'c' in the current instance
    // or -1 if 'c' is not found. Throws an error if 'c' is an invalid Char.
    lastIndexOf(c) {
        Check.char("c", c, 0, 255)
        for (i in count-1..0) if (this[i] == c) return i
        return -1
    }

    // Replaces all occurrences of 'old' by 'new' in the current instance
    // and returns ['old', 'new'].
    replace(old, new) {
        Check.char("old", old, 0, 255)
        Check.char("new", new, 0, 255)
        for (i in _rng) if (this[i] == old) this[i] = new
        return [old, new]
    }

    // Swaps the elements at index1 and index2 within the CharArray.
    swap(index1, index2) {
        var t = this[index1]
        this[index1] = this[index2]
        this[index2] = t
    }

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

    // Converts 'in place' all upper case Chars in this instance to lower case.
    toLower {
        for (i in _rng) {
            var c = this[i].codePoints[0]
            if ((c >= 65 && c <= 90) || (c >= 192 && c <= 214) || (c >= 216 && c <= 222)) {
                this[i] = String.fromCodePoint(c + 32)
            }
        }
    }

    // Converts 'in place' all lower case Chars in this instance to upper case.
    toUpper {
        for (i in _rng) {
            var c = this[i].codePoints[0]
            if ((c >= 97 && c <= 122) || (c >= 224 && c <= 246) || (c >= 248 && c <= 254)) {
                this[i] = String.fromCodePoint(c - 32)
            }
        }
    }

    // Capitalizes 'in place' the first Char of this instance.
    capitalize {
        var c = this[0].codePoints[0]
        if ((c >= 97 && c <= 122) || (c >= 224 && c <= 246) || (c >= 248 && c <= 254)) {
            this[0] = String.fromCodePoint(c - 32)
        }
    }

    // Iterator protocol methods.
    iterate(iterator)       { _rng.iterate(iterator) }
    iteratorValue(iterator) { this[iterator] }

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

    // Returns an Array<Char> using the normal 8 bytes for each element.
    toArray {
        var chars = List.filled(count, null)
        for (i in _rng) chars[i] = this[i]
        return chars
    }

    // Returns a string representation of this instance, optionally trimming trailing
    // whitespace (space, tab, carriage return, and line feed characters).
    toString(trimEnd) {
        var res = toList.join()
        return trimEnd ? res : res.trimEnd()
    }

    // Returns a string representation of this instance with trailing whitespace removed.
    toString { toList.join() }

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