Category talk:Wren-array: Difference between revisions
Content deleted Content added
→Source code: Numerous changes to BitArray and ByteArray classes including new methods. |
Added indexOf(value, start) method to all classes. |
||
(One intermediate revision 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 232: | Line 244: | ||
for (i in _rng) if (this[i] == v) return i |
for (i in _rng) if (this[i] == v) return i |
||
return -1 |
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 |
|||
} |
} |
||
Line 407: | Line 430: | ||
for (i in _rng) if (this[i] == v) return i |
for (i in _rng) if (this[i] == v) return i |
||
return -1 |
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 |
|||
} |
} |
||
Line 462: | 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 }
}