String Length changes if it is pulled from file - file

I'm coding in Game Maker Studio (Toby Fox used it for undertale) and I'm starting to try and work with files. Effectivly, I'm trying to build a custom "level" editor because the built in one for GMS lacks some features I want. So far I have set most of it up, but I still have to make it regurgitate the saved "levels" from the files. It's almost completly working, but there's one problem. Here's some info you should know ahead of time:
GMS uses really nonstandard syntax. I apologize ahead of time for that.
GMS is weird so arrays don't work with JSON formatting
as a result of No. 2, I have coded my own JSON formatting, which I reffer to as GSON (Gamemaker Studio Object Notation)
I am using GSON not only to store levels (and their respective components) but I also want to build a copy paste functionality, which I would use GSON for (copy the component and it just gives you a GSON string which is then interpreted back when you paste it, so that I can copy and paste not only the component type (i.e. a solid vs the player), but the variable values as well).
So, into the meat of it...
global.lineBreak = "\n"
global.sectionSign = "§"
{ // gsonStringifySelf
function gsonStringifySelf(varnamearray, exception = undefined){
/*
"exception" should be set to the variable you will use to store the returned string. if you decide to instead just upload it
directly to the file without a middleman variable, then leave the field empty. e.g.
gsonstring = gsonStringifySelf(varnamearray, "gsonstring")
varnamearray should be an array with the names of all the variables you want to save. I could hardwire it to use
variable_instance_get_names(self)
but this doesn't get instance_variables, so instead its best to have an input, especially so you can leave out
instance variables you don't care about. If you want to put in all the instance variables,you have to mannually add their names to the array, which could be done via the following:
var names = variable_instance_get_names(self) // (var means it is a strictly local variable, so it can't be accessed by any other objects)
var i = array_length(names) // array indexes start at 0 but array_length starts at 1, so I is refrencing the unset index that is closest to index 0
names[i++] = "id" // i++ returns i **then** increments it, causing it to refrence the correct array index, as mentioned above.
names[i++] = "visible"
names[i++] = "solid"
names[i++] = "persistent"
names[i++] = "depth"
names[i++] = "layer"
names[i++] = "alarm"
names[i++] = "toString"
names[i++] = "direction"
names[i++] = "friction"
names[i++] = "gravity"
names[i++] = "gravity_direction"
names[i++] = "hspeed"
names[i++] = "vspeed"
names[i++] = "speed"
names[i++] = "xstart"
names[i++] = "ystart"
names[i++] = "x"
names[i++] = "y"
names[i++] = "xprevious"
names[i++] = "yprevious"
names[i++] = "image_xscale"
names[i++] = "image_yscale"
strvars = gsonStringifySelf(names, strvars)
i++ returns the value of i, then increments it.
The gson formatting works as follows:
first it tells you what kind of object it is:
<objectname>{\n
(\n is the code for "new line")
then it adds the variables, which are formatted as follows:
<type>:<variablename>:<value>\n
then it puts "}\n" on the very end,
all together it looks like this:
<objectname>{\n
<type>:<variablename>:<value>\n
}\n
the types are as follows:
A = Array (list of entries)
B = Boolian (true/false)
R = Real (any and all numbers, yes this is unstandard)
S = String (Letters and Characters)
U = Undefined (Built in Variable "Undefined")
so these two are effectivly equivalent:
number = 20
"R:number:20"
Arrays are a special case:
A:<arrayname>:{,<type>:<value1>,<type>:<value2>...}\n
In this way multi dimentional arrays are instead shown as nested one dimensional arrays.
Multi dimensional arrays and nested arrays respectively look like this:
2 dimensional array that is 2x1 in size
A:<arrayname>:{A:{<type>:<value>},A:{<type>:<value>}}\n
2 length 1 dimensional array, with a 2 length array nested inside
A:<arrayname>:{A:{<type>:<value>,<type>:<value>}, <type>:<value>}\n
I mention this because in a case like this:
a = [
10,
5,
false
]
b = [
a,
"nope"
]
there will be no connection between b[0] and a. Instead, b[0] will be only store the values, and as such changing b[0][1] will not affect the value of a[1]
note that in scenarios like this:
array[0] = 1
array[2] = "hello"
array[1] will also be part of the string, so it will look like this:
A:array:{R:1;R:0;S:hello}\n
This is because of a quirk with GMS. If you set a location in an array "past" an undefined location, then the undefined location is set to 0 when it is accessed.
*/
static lineBreak = "\n"
static sectionSign = "§"
var str = sectionSign + object_get_name(object_index) + "{" + lineBreak // Make the first line = "§<objectname>{\n"
var str = global.sectionSign + object_get_name(object_index) + "{" + global.lineBreak // Make the first line = "§<objectname>{\n"
var names = varnamearray
for(var i = 0; i < array_length(names); i++){
if(names[i] == string(exception)){
continue
}
var tempstr = ""
var r = variable_instance_get(self, names[i])
if(is_array(r)){
tempstr = "A:" + names[i] + ":"
tempstr += string(gsonStringifyArray(r)) + global.lineBreak
} else {
tempstr = gsonValueTypeNotate(r, names[i]) + global.lineBreak
}
str += tempstr
}
str += "}" + global.lineBreak
return str
}
function gsonValueTypeNotate(val, name){
if(name == undefined){
var a = ""
name = ""
} else {
var a = ":"
}
if(is_bool(val)){
var str = "B:" + name + a + string(val)
} else if(is_numeric(val)){
var str = "R:" + name + a + string(val)
} else if(is_string(val)){
var str = "S:" + name + a + val
} else if(is_undefined(val)){
var str = "U:" + name + a + string(val)
}
return str
}
function gsonStringifyArray(array){
var str = "{" + global.sectionSign + ","
var len = array_length(array)
var len1 = len - 1
for(var i = 0; i < len1; i++){
if(is_array(array[i])){
str += "A:" + gsonStringifyArray(array[i]) + global.sectionSign + ","
continue
} else {
str += gsonValueTypeNotate(array[i], undefined) + global.sectionSign + ","
}
}
if(is_array(array[i])){
str += gsonStringifyArray(array[i])
} else {
str += gsonValueTypeNotate(array[i], undefined)
}
str += global.sectionSign + ",}"
return str
}
}
{ // gsonParse
function gsonParseObject(str){
var array
array = stringLineify(str)
var a1 = array[0]
var a2 = string_length(a1)
var a = string_copy(array[0], 2, string_length(array[0]) - 3)
var b = instance_create_depth(0, 0, 0, asset_get_index(a))
var c
for(var i = 1; i < array_length(array) - 1; i++){
if(string_copy(array[i], 1, 1) = "A"){
for(var j = 3; string_copy(array[i], j, 1) != ":"; j++){
}
c[0] = string_copy(array[i], 3, j - 3)
c[1] = parseArrayValue(array[i])
variable_instance_set(b, c[0], c[1])
} else {
c = getVarNameValue(array[i])
if(string_copy(array[i], 1, 1) = "B"){
variable_instance_set(b, c[0], bool(c[1]))
} else if(string_copy(array[i], 1, 1) = "R"){
variable_instance_set(b, c[0], real(c[1]))
} else if(string_copy(array[i], 1, 1) = "S"){
variable_instance_set(b, c[0], c[1])
} else if(string_copy(array[i], 1, 1) = "U"){
variable_instance_set(b, c[0], undefined)
}
}
}
}
function getVarNameValue(str){
for(var i = 3; string_copy(str, i, 1) != ":"; i++){
}
var a
a[0] = string_copy(str, 3, i - 3)
var ind = ++i
for(; string_copy(str, i, 1) != global.lineBreak; i++){
}
a[1] = string_copy(str, ind, i - ind)
return a
}
function parseArrayValue(str){
var array
array[0] = ""
var l
var b = 0
var c = 1
for(var i = 2; i <= string_length(str); i++){
var l = string_copy(str, i, 1)
if(l == global.sectionSign){
i += 2
var l = string_copy(str, i, 1)
if(l == "A"){
c = i + 2
for(var j = c; string_copy(str, j, 1) != "}"; ++j){
}
show_debug_message(string_copy(str, c, ++j))
array[b++] = parseArrayValue(string_copy(str, c, j - c))
i = j - 1
} else if(l == "}"){
break
} else {
for(var j = i; string_copy(str, j, 1) != global.sectionSign and j <= string_length(str); ++j){
var l = string_copy(str, j, 1)
}
array[b++] = getArrValue(string_copy(str, i, j - i))
i = j - 1
}
}
}
return array
}
function getArrValue(str){
var v = string_copy(str, 3, string_length(str))
if(string_copy(str, 1, 1) = "B"){
return bool(v)
} else if(string_copy(str, 1, 1) = "R"){
return real(v)
} else if(string_copy(str, 1, 1) = "S"){
return string(v)
} else if(string_copy(str, 1, 1) = "U"){
return undefined
}
}
}
{ // Save GSON to file
function gsonRoomUnload(file){
global.roomUnload = file
with(all){
event_user(0)
}
event_user(0)
}
function saveString(fname, str){
var b = file_text_open_append(fname)
file_text_write_string(b, str)
file_text_close(b)
}
}
{ // Load GSON from file
function gsonRoomLoad(fname){
if(file_exists(fname)){
var file = file_text_open_read(fname)
var a = ""
var b = ""
for(var i = 0; ; i++){
b = file_text_readln(file)
a += b
if(string_copy(b, 1, 1) == "}"){
//gsonParseObject(a)
a = ""
}
if(string_copy(b, 2, 1) == ""){
break
}
}
return true
} else {
return false
}
}
}
function stringLineify(str){
var strings
strings[0] = ""
var a
a[1] = 0
a[2] = ""
for(var i = 0; a[2] != false; i++){
a = stringLine(str, a[1])
strings[i] = a[0]
}
return strings
}
function stringLine(str, index){
var carriage = "\n"
for(var i = 1; ; i++){
var b = string_copy(str, index + i, 1)
if(b == carriage){
return [string_copy(str, index, i), index + i + 1, true]
} else if(index + i >= string_length(str)){
return [string_copy(str, index, i), index + i + 1, false]
}
}
}
There are a couple of functions you'll notice aren't defined. Those functions are built into GMS. I've linked their documentation at the bottom of the question.
Okay, so here's my problem:
If I run gsonStringifySelf() then take the string it returns and put it in gsonParseObject() then this line:
string_copy(array[0], 2, string_length(array[0]) - 3)
Is correct. Remember, what this does is take this example string:
"§<type>{\n"
and turn it into this:
"<type>"
string_copy(array[0], 2, string_length(array[0]) - 3)
copies from the second location in the string (I will be using "|" to inducate where the code is selecting in the string)
"§|<type>{\n"
and string_length returns the length of the string. so its trying to copy within the "|" in the string
"§|<type>{\n|"
But it is actually trying to copy 1 past the final location of the string, because we skipped the first location of the string but told it to copy the length of the string. Thus, when we tell it how many spaces to copy, we must subtract 3. We subtract 1 so it is only copying up to the final point in the string, and another 2 so that "{\n" isn't included in the final string. (\n is newline, and is considered one character even though it is represented with 2)
so base string:
"§<type>{\n"
copy from second point
"§|<type>{\n"
copy to the end of the string +1
"§|<type>{\n|"
subtract one from how many we are copying to shave the extra space off the end
"§|<type>{\n|"
and then subtract another 2 from how many we are copying to remove "{\n"
"§|<type>|{\n"
and return it:
"<type>"
Here's the thing. If I run gsonStringifySelf() and put it in a file then when I pull it down from the file and put that into gsonParseObject(), instead of this being correct:
string_copy(array[0], 2, string_length(array[0]) - 3)
this is correct:
string_copy(array[0], 2, string_length(array[0]) - 4)
So somehow, in all the file beeswax, an extra character is being added on, and I can't figure out where.
Thanks in advance for the help.
bool(n)
https://manual.yoyogames.com/GameMaker_Language/GML_Reference/Variable_Functions/bool.htm
real(n)
https://manual.yoyogames.com/GameMaker_Language/GML_Reference/Strings/real.htm
is_bool(n)
https://manual.yoyogames.com/GameMaker_Language/GML_Reference/Variable_Functions/is_bool.htm
is_numeric(n)
https://manual.yoyogames.com/GameMaker_Language/GML_Reference/Variable_Functions/is_numeric.htm
is_string(n)
https://manual.yoyogames.com/GameMaker_Language/GML_Reference/Variable_Functions/is_string.htm
is_undefined(n)
https://manual.yoyogames.com/GameMaker_Language/GML_Reference/Variable_Functions/is_string.htm
asset_get_index(str)
https://manual.yoyogames.com/GameMaker_Language/GML_Reference/Asset_Management/Assets_And_Tags/asset_get_index.htm
string_copy(str, index, count)
https://manual.yoyogames.com/GameMaker_Language/GML_Reference/Strings/string_copy.htm
string_length(str)
https://manual.yoyogames.com/GameMaker_Language/GML_Reference/Strings/string_length.htm
with(id)
https://manual.yoyogames.com/GameMaker_Language/GML_Overview/Language_Features/with.htm
event_user(int)
https://manual.yoyogames.com/GameMaker_Language/GML_Reference/Asset_Management/Objects/Object_Events/event_user.htm
instance_create_depth()
https://manual.yoyogames.com/GameMaker_Language/GML_Reference/Asset_Management/Instances/instance_create_depth.htm
variable_instance_get()
https://manual.yoyogames.com/GameMaker_Language/GML_Reference/Variable_Functions/variable_instance_get.htm
variable_instance_set()
https://manual.yoyogames.com/GameMaker_Language/GML_Reference/Variable_Functions/variable_instance_set.htm
file_exists(fname)
https://manual.yoyogames.com/GameMaker_Language/GML_Reference/File_Handling/File_System/file_exists.htm
file_text_open_append(fname)
https://manual.yoyogames.com/GameMaker_Language/GML_Reference/File_Handling/Text_Files/file_text_open_append.htm
file_text_write_string(file)
https://manual.yoyogames.com/GameMaker_Language/GML_Reference/File_Handling/Text_Files/file_text_write_string.htm
file_text_open_read(fname)
https://manual.yoyogames.com/GameMaker_Language/GML_Reference/File_Handling/Text_Files/file_text_open_read.htm
file_text_readln(file)
https://manual.yoyogames.com/GameMaker_Language/GML_Reference/File_Handling/Text_Files/file_text_readln.htm
file_text_close(file)
https://manual.yoyogames.com/GameMaker_Language/GML_Reference/File_Handling/Text_Files/file_text_close.htm
Ive tried most everything I can think of. The extra Character is an invisible character, and because of that, itis also invisible when I try to check the value with the debugger. I could bodge it, but if I can i'd rather avoid that, since it'll just cause me problems later anyway.

So after a bit of testing, I've realized that with just how long it takes to load a single instance, it's not worth using. Presumably I would use this for loading the areas during gameplay as well, but it takes well over 10 seconds to load a single instance, and a couple of minutes for more than 4 objects. I'll just have to avoid using arrays. Thanks! (bit annoying that I spent so much time on it for no reason tho...)

GMS is weird so arrays don't work with JSON formatting
The not-so-recently-added json_stringify does. The following
var thing = {
an_int: 1,
a_float: 1.5,
a_bool: true,
a_string: "hi",
an_array: [1, 2, "oh"],
a_struct: { x: 1, y: 2, name: "me", arr: [3, 4, undefined] },
};
show_debug_message(json_stringify(thing));
would output
{ "a_struct": { "x": 1.0, "y": 2.0, "name": "me", "arr": [ 3.0, 4.0, null ] }, "an_int": 1.0, "a_float": 1.5, "a_bool": true, "a_string": "hi", "an_array": [ 1.0, 2.0, "oh" ] }
With help of variable_instance_get_names/variable_struct_get_names, you could assemble a struct with all of the instance's variables (and desired built-in ones), encode that, and upon decoding write them back in.
As for your encoder-decoder, a few implementation caveats plague it:
GameMaker strings are immutable, meaning that adding two strings together generally allocates a new, third one. This makes your encoding slower than it could have been.
Writing to a buffer with kind=buffer_grow is a good way to limit memory re-allocations (as it will double the size whenever it runs out of space).
GameMaker strings are encoded as UTF-8, meaning that string_char_at isn't as simple as adding an index to the string pointer. This makes your decoding slower than it could have been.
Reading from a buffer is a common way around this, but take care - since you'd be storing a UTF-8 string in the buffer, a character will not necessarily be a single byte. Fortunately, you can usually completely ignore this detail so long as you read/write a string as a whole.
You are storing strings verbatim, which probably means that it's going to catch fire the moment one of the strings contains a } or other delimiter.
You are doing loops searching for characters often instead of calling string_pos[_ext] to let the engine do this [quicker].
Although technically recursive, its reliance on grabbing a substring with an encoded value instead of having a read position makes the process of reading nested values far messier than it should be (if I were to guess, it fails after misattributing one of the closing })
But also, you know, if it's your format, who said that it has to be a string? You can work with bytes instead, sparing yourself of a number of problems at once - no need for delimiters when you are writing/reading data in the same order.
A simple buffer-based encoder-decoder fits in just a little over 50 lines of code:
enum BinType { Undefined, Bool, Float, Int, String, Array, Struct };
function buffer_write_value(_buf, _val) {
if (is_real(_val)) {
buffer_write(_buf, buffer_u8, BinType.Float);
buffer_write(_buf, buffer_f64, _val);
} else if (is_bool(_val)) {
buffer_write(_buf, buffer_u8, BinType.Bool);
buffer_write(_buf, buffer_bool, _val);
} else if (is_numeric(_val)) {
buffer_write(_buf, buffer_u8, BinType.Int);
buffer_write(_buf, buffer_u64, _val); // u64 writes signed int64s fine
} else if (is_string(_val)) {
buffer_write(_buf, buffer_u8, BinType.String);
buffer_write(_buf, buffer_string, _val);
} else if (is_struct(_val)) {
buffer_write(_buf, buffer_u8, BinType.Struct);
var _names = variable_struct_get_names(_val);
var _count = array_length(_names);
buffer_write(_buf, buffer_u32, _count);
for (var i = 0; i < _count; i++) {
var _name = _names[i];
buffer_write(_buf, buffer_string, _name);
buffer_write_value(_buf, _val[$ _name]);
}
} else if (is_array(_val)) {
buffer_write(_buf, buffer_u8, BinType.Array);
var _count = array_length(_val);
buffer_write(_buf, buffer_u32, _count);
for (var i = 0; i < _count; i++) buffer_write_value(_buf, _val[i]);
} else buffer_write(_buf, buffer_u8, BinType.Undefined);
}
function buffer_read_value(_buf) {
switch (buffer_read(_buf, buffer_u8)) {
case BinType.Bool: return buffer_read(_buf, buffer_bool);
case BinType.Float: return buffer_read(_buf, buffer_f64);
case BinType.Int: return buffer_read(_buf, buffer_u64);
case BinType.String: return buffer_read(_buf, buffer_string);
case BinType.Array:
var _count = buffer_read(_buf, buffer_u32);
var _arr = array_create(_count);
for (var i = 0; i < _count; i++) _arr[i] = buffer_read_value(_buf);
return _arr;
case BinType.Struct:
var _struct = {};
repeat (buffer_read(_buf, buffer_u32)) {
var _name = buffer_read(_buf, buffer_string);
_struct[$ _name] = buffer_read_value(_buf);
}
return _struct;
default: return undefined;
}
}
and will successfully process the nested struct from the beginning of my answer if you write a value to a buffer, rewind it, and read it back. Add a little logic to detect instances or special cases, and you'll have a solution fit to your specific problems.
Further reading:
Haxe's JsonPrinter is a great example of a recursive JSON encoder.
SNAP is a GameMaker library with a number of encoders-decoders (following the aforementioned buffer principles, often non-recursive) that is both an example of good code and a solution to problems that you might be thinking of having.
Debugger (for stepping through and figuring out where you go wrong)
Profiler (for figuring out what's slow)
YYC (for performance)

Related

Need Help to solve an string challenge C#, Java or JavaScript

I have been trying to solve this challenge, and I cannot find a way to do it. I have solved similar problems before, but I found this particularly difficult, more even trying to solve while keeping a linear time complexity. This was part of an interview assessment that I obviously failed.
I will appreciate any help.
Thanks
Here is the challenge
You are to parse the string into pieces that are no more than "pieceLength" characters long
INCLUDING the commas.
If pieceLength = 3 the result for the above test string would be
result[0] = "1,2"
result[1] = ",3,"
result[2] = "5,8"
result[3] = ","
result[4] = "131"
result[5] = ",21"
result[6] = ",34"
You can look at the test above. This is a sample string and result, however
your code will be run with multiple input strings and piece length parameters.
Note how result[3] is just ",". Including any more characters would break apart
the next number, 131, and that's not allowed.
One way to do this is to iterate through the string, looking at the character beyond the next piecelength. If this is past end of string, or is a comma, there is no problem and you can push all the characters up to that point. Otherwise iterate backwards from that position until you find a comma and push the string up to the comma. If a comma is not found, the portion of the string was all digits and can be pushed to the array:
let str = "1,2,3,5,8,131,21,34";
let pieces = (str, piecelength) => {
let res = [];
for (let i = 0; i < str.length;) {
if (i + piecelength >= str.length || str[i + piecelength] == ',') {
// no problem, just grab the characters
res.push(str.substring(i, Math.min(i + piecelength, str.length)));
i += piecelength;
} else {
// search back for the last comma
for (j = piecelength - 1; j >= 0; j--) {
if (str[i + j] == ',') {
res.push(str.substr(i, j + 1));
i += j + 1;
break;
}
}
if (j == -1) {
// didn't find a comma, everything must be a digit
res.push(str.substr(i, piecelength));
i += piecelength;
}
}
}
return res;
}
console.log(pieces(str, 3));
console.log(pieces(str, 4));

Get an element with its index counting from the end in Kotlin

Is there a simple function to get an element with its index counting from the end in a Kotlin Array, List, or String? In other words, is there a Kotlin equivalent of negative index slicing in Python?
There is no direct function for slicing, but one can write an user defined function or easily simulate using reversed function, that works with string, lists and arrays.
Strings In Python
pyString = 'Python'
sObject = slice(-1, -4, -1)
print(pyString[sObject]) # output: noh
Strings In Kotlin
val pyString = "Python"
val sObject = pyString.reversed().substring(0, 3).reversed() // index 3 excluded
println(pyString) // output: noh
List(or Arrays) in Kotlin
var py = arrayOf(1, 2, 3, 4, 5, 6, 7, 8)
var sObject = py.reversed().slice(0..2).reversed()
println(sObject)
However you can do method or function overload, using this as implicit object
For instance, you can program reverse substring, but here you cannot use negative numbers, because we need a different parameters profile regarding to the original method:
fun String.substring(a: Int, b: Int = 0, rev: Boolean): String {
if (rev == true)
if (b == 0)
return this.substring(0, this.length - a)
else
return this.substring(this.length - b, this.length - a)
else
if (b == 0)
return this.substring(a)
else
return this.substring(a, b)
}
So "whale".substring(0,2,true) is "le"
You can use similar technique to extend slice method.
I implemented a function similar to python's slicing mechanism
import kotlin.math.abs
fun String.substring(startingIndex: Int, endingIndex: Int, step: Int=1): String {
var start = startingIndex
var end = endingIndex
var string = this
if (start < 0) {
start = string.length + start
}
if (end < 0) {
end = string.length + end
}
if (step < 0) {
string = string.reversed()
}
if (start >= string.length) {
throw Exception("Index out of bounds.")
}
var outString = ""
for ((index, character) in string.withIndex()) {
if (index % abs(step) == 0) {
if (index >= start && index <= end) {
outString += character
}
}
}
return outString
}
Can be used like this:
println("This is some text.".substring(8, -6)) // some
There is but on problem with my function, it is that a negative step messes it up if you also use negative indexes.
These answers are quite complicated. If you just want to use a negative index, all you have to do is
exampleList[exampleList.lastIndex - exampleIndex] which will (basically) do the same as exampleList[-exampleindex] in python.
So for example you would do:
fun main(args: Array<String>) {
val exampleList = mutableListOf<Int>(1, 2, 3, 4, 5)
//Now we want exampleList[-1] or 4. I know in python it would be writen as exampleList[-2], but don't worry about that!
println(exampleList[exampleList.lastIndex - 1])
}
the lastIndex parameter is the key thing to remember.

Dart. Cant' change elements of list

I can't change elements of array using anonymous function:
var collection=[0, 1, 2];
collection.forEach((c)=> c+=1);
print(collection);
This variant doesn't work eithter:
var collection = [0, 1, 2];
for (var x in collection) {
x=x+1;
}
print(collection);
If you wish to replace/update all items in a list (List<int> items = [1, 2, 3]):
items = items.map((x) => x + 1).toList();
for (int i = 0; i < items.length; i++) items[i] += 1;
items = List<int>.generate(items.length, (i) => items[i] + 1);
List<int> tempItems = [];
items.forEach((x) => tempItems.add(x + 1));
items = tempItems;
You can't change the arguments to a function or var-in loop. You can call methods on them, which might have the side effect of mutating them, but assigning directly to them will not alter the value from which they are effectively copied.
collection.forEach((c)=> c+=1);
In above line you are incrementing parameter by 1 and that will not affect to the list.
It's equavalent to:
collection.forEach((int c) {// in this case int because list contains int's
c = c + 1;
});
As you can see, no point of changing the value of a parameter(local variable).
Since it's 0,1,2, you can do like this: collection.forEach((c)=> collection[c] = c+1);. That is stupid way because whenever you change any value, you will get an error(out of range).
So, here are some ways that you can change list values:
The easy way:
void main() {
var collection=[0, 1, 2];
for(int i = 0; i < collection.length; i++) {
collection[i] += 1;
}
print(collection);
}
Using replaceRange:
void main() {
var collection=[0, 1, 2];
var temp = collection.map((c) => c+1).toList();
collection.replaceRange(0, collection.length, temp);
print(collection);
}

ActionScript - Array.sortOn() For Non-English Data?

this is an array of objects that i want to alphabetize:
var streets:Array = new Array();
streets.push({name:"Édouard-Montpetit"});
streets.push({name:"Alexandre de Sève"});
streets.push({name:"Van Horne"});
streets.push({name:"Atwater"});
now i'll sort my array:
streets.sortOn("name", Array.CASEINSENSITIVE);
//Sorted
Alexandre de Sève
Atwater
Van Horne
Édouard-Montpetit
the accent above the E in Édouard-Montpetit, and any other first letter with a non-english accent is sorted after Z.
any ideas how i can sort this correctly? i do not have access to the named data.
I know this is late, but for anyone going through this answer, you could pass a Collator
object to the Array.sort() method. A simple example from the documentation:
var words:Array = new Array("coté", "côte");
var sorter:Collator = new Collator("fr-FR", CollatorMode.SORTING);
words.sort(sorter.compare);
trace(words);// côte,coté
Hope this helps
I don't think you can do it with sortOn, as there's no way to tell flash to use a particular collaction for sorting text (at least, not that I'm aware of).
However, you could use sort and a custom sort function.
In this sort function, basically you want to strip all accents and do a case insensitive comparation. Replacing diacritics is easy and after that, you can safely use < and > for comparing the strings. A sort function is called by sort with two of the items to be sorted at a time. It should return a negative number if the first passed items sorts first , a possitive number if the second comes first and 0 if they sort equal.
function sortText(obj1:Object,obj2:Object):int {
var a:String = replaceDiacritics(obj1.name);
var b:String = replaceDiacritics(obj2.name);
if(a < b) {
return -1;
} else if(b < a) {
return 1;
} else {
return 0;
}
}
function replaceDiacritics(str:String):String {
str = str.toLowerCase();
str = str.replace(/á/g,"a");
str = str.replace(/é/g,"e");
str = str.replace(/í/g,"i");
str = str.replace(/ó/g,"o");
str = str.replace(/ú/g,"u");
str = str.replace(/à/g,"a");
str = str.replace(/è/g,"e");
str = str.replace(/ì/g,"i");
str = str.replace(/ò/g,"o");
str = str.replace(/ù/g,"u");
return str;
}
streets.sort(sortText);
A couple of notes about this. I know this method won't work for Spanish, as you have ñ, which is considered a letter on its own (not a regular n with a funny mark) and comes after n and before o. So, it's not possible to just replace accents and do a < / > compare. I think this is not a problem in French, but I could be wrong (not sure how Ç / ç is considered for sort purposes, for instance). Also, note that I haven't replaced all possible diacritics, so you'd want to add circumflexes (^) and umlauts (¨) to replaceDiacritics as necessary.
Edit
For a table based approach you could try something like the following. Each letter is assigned a number that reflects the sort order. As long as you can assume that any letter will have an absolute sort order (that is, context won't change how this works, which is not the case on some languages), it should give you good results.
Out of laziness, I built the table with a loop and just did what was neccesary to put "Ñ" between "n" and "o". I'm not considering any diacritics for sorting purposes, so they have the same value than their unaccented counterpart. But you could change this table as neccesary. Also, this table probably should be hardcoded for the required locale, but this code is just to give you an idea of how you could do this, not a full implementation (and it's probably not entirely correct from a purist perspective, but I think it could do the job). Also, in case we find a character that is not mapped, I'm falling back to its code point to determine how it sorts.
var sortTable:Object = buildSortTable();
function buildSortTable():Object {
var sortTable:Object = {};
var char:String;
var offset:int = 0;
for(var i:int = 1; i < 256; i++) {
char = String.fromCharCode(i);
if(char == "Ñ" || char == "ñ") {
offset--;
continue;
}
sortTable[char] = i + offset;
if(char == "N") {
sortTable["Ñ"] = sortTable["N"] + 1;
offset++;
}
if(char == "n") {
sortTable["ñ"] = sortTable["n"] + 1;
offset++;
}
}
sortTable["Á"] = sortTable["À"] = sortTable["Ä"] = sortTable["Â"] = sortTable["A"];
sortTable["É"] = sortTable["È"] = sortTable["Ë"] = sortTable["Ê"] = sortTable["E"];
sortTable["Í"] = sortTable["Ì"] = sortTable["Ï"] = sortTable["Î"] = sortTable["I"];
sortTable["Ó"] = sortTable["Ò"] = sortTable["Ö"] = sortTable["Ô"] = sortTable["O"];
sortTable["Ú"] = sortTable["Ì"] = sortTable["Ü"] = sortTable["Û"] = sortTable["U"];
sortTable["á"] = sortTable["à"] = sortTable["ä"] = sortTable["â"] = sortTable["a"];
sortTable["é"] = sortTable["è"] = sortTable["ë"] = sortTable["ê"] = sortTable["e"];
sortTable["í"] = sortTable["ì"] = sortTable["ï"] = sortTable["î"] = sortTable["i"];
sortTable["ó"] = sortTable["ò"] = sortTable["ö"] = sortTable["ô"] = sortTable["o"];
sortTable["ú"] = sortTable["ù"] = sortTable["ü"] = sortTable["û"] = sortTable["u"];
return sortTable;
}
function sortText(obj1:Object,obj2:Object):int {
var a:String = obj1.name.toLowerCase();
var b:String = obj2.name.toLowerCase();
var len_a:int = a.length;
var len_b:int = b.length;
var char_a:String;
var char_b:String;
var val_a:Number;
var val_b:Number;
for(var i = 0; i < len_a && i < len_b; i++) {
char_a = a.charAt(i);
char_b = b.charAt(i);
val_a = sortTable[char_a];
val_b = sortTable[char_b];
// this is just in case we have a letter that we haven't mapped...
// let's fall back to using its code point
if(isNaN(val_a)) {
val_a = char_a.charCodeAt(0);
}
if(isNaN(val_b)) {
val_b = char_b.charCodeAt(0);
}
if(val_a < val_b) {
return -1;
} else if(val_a > val_b) {
return 1;
}
}
// both strings are equal so far; so the sorter one (if any) must sort first
if(len_a < len_b) {
return -1;
} else if(len_a > len_b) {
return 1;
} else {
return 0;
}
}

Generating All Permutations of Character Combinations when # of arrays and length of each array are unknown

I'm not sure how to ask my question in a succinct way, so I'll start with examples and expand from there. I am working with VBA, but I think this problem is non language specific and would only require a bright mind that can provide a pseudo code framework. Thanks in advance for the help!
Example:
I have 3 Character Arrays Like So:
Arr_1 = [X,Y,Z]
Arr_2 = [A,B]
Arr_3 = [1,2,3,4]
I would like to generate ALL possible permutations of the character arrays like so:
XA1
XA2
XA3
XA4
XB1
XB2
XB3
XB4
YA1
YA2
.
.
.
ZB3
ZB4
This can be easily solved using 3 while loops or for loops. My question is how do I solve for this if the # of arrays is unknown and the length of each array is unknown?
So as an example with 4 character arrays:
Arr_1 = [X,Y,Z]
Arr_2 = [A,B]
Arr_3 = [1,2,3,4]
Arr_4 = [a,b]
I would need to generate:
XA1a
XA1b
XA2a
XA2b
XA3a
XA3b
XA4a
XA4b
.
.
.
ZB4a
ZB4b
So the Generalized Example would be:
Arr_1 = [...]
Arr_2 = [...]
Arr_3 = [...]
.
.
.
Arr_x = [...]
Is there a way to structure a function that will generate an unknown number of loops and loop through the length of each array to generate the permutations? Or maybe there's a better way to think about the problem?
Thanks Everyone!
Recursive solution
This is actually the easiest, most straightforward solution. The following is in Java, but it should be instructive:
public class Main {
public static void main(String[] args) {
Object[][] arrs = {
{ "X", "Y", "Z" },
{ "A", "B" },
{ "1", "2" },
};
recurse("", arrs, 0);
}
static void recurse (String s, Object[][] arrs, int k) {
if (k == arrs.length) {
System.out.println(s);
} else {
for (Object o : arrs[k]) {
recurse(s + o, arrs, k + 1);
}
}
}
}
(see full output)
Note: Java arrays are 0-based, so k goes from 0..arrs.length-1 during the recursion, until k == arrs.length when it's the end of recursion.
Non-recursive solution
It's also possible to write a non-recursive solution, but frankly this is less intuitive. This is actually very similar to base conversion, e.g. from decimal to hexadecimal; it's a generalized form where each position have their own set of values.
public class Main {
public static void main(String[] args) {
Object[][] arrs = {
{ "X", "Y", "Z" },
{ "A", "B" },
{ "1", "2" },
};
int N = 1;
for (Object[] arr : arrs) {
N = N * arr.length;
}
for (int v = 0; v < N; v++) {
System.out.println(decode(arrs, v));
}
}
static String decode(Object[][] arrs, int v) {
String s = "";
for (Object[] arr : arrs) {
int M = arr.length;
s = s + arr[v % M];
v = v / M;
}
return s;
}
}
(see full output)
This produces the tuplets in a different order. If you want to generate them in the same order as the recursive solution, then you iterate through arrs "backward" during decode as follows:
static String decode(Object[][] arrs, int v) {
String s = "";
for (int i = arrs.length - 1; i >= 0; i--) {
int Ni = arrs[i].length;
s = arrs[i][v % Ni] + s;
v = v / Ni;
}
return s;
}
(see full output)
Thanks to #polygenelubricants for the excellent solution.
Here is the Javascript equivalent:
var a=['0'];
var b=['Auto', 'Home'];
var c=['Good'];
var d=['Tommy', 'Hilfiger', '*'];
var attrs = [a, b, c, d];
function recurse (s, attrs, k) {
if(k==attrs.length) {
console.log(s);
} else {
for(var i=0; i<attrs[k].length;i++) {
recurse(s+attrs[k][i], attrs, k+1);
}
}
}
recurse('', attrs, 0);
EDIT: Here's a ruby solution. Its pretty much the same as my other solution below, but assumes your input character arrays are words: So you can type:
% perm.rb ruby is cool
~/bin/perm.rb
#!/usr/bin/env ruby
def perm(args)
peg = Hash[args.collect {|v| [v,0]}]
nperms= 1
args.each { |a| nperms *= a.length }
perms = Array.new(nperms, "")
nperms.times do |p|
args.each { |a| perms[p] += a[peg[a]] }
args.each do |a|
peg[a] += 1
break if peg[a] < a.length
peg[a] = 0
end
end
perms
end
puts perm ARGV
OLD - I have a script to do this in MEL, (Maya's Embedded Language) - I'll try to translate to something C like, but don't expect it to run without a bit of fixing;) It works in Maya though.
First - throw all the arrays together in one long array with delimiters. (I'll leave that to you - because in my system it rips the values out of a UI). So, this means the delimiters will be taking up extra slots: To use your sample data above:
string delimitedArray[] = {"X","Y","Z","|","A","B","|","1","2","3","4","|"};
Of course you can concatenate as many arrays as you like.
string[] getPerms( string delimitedArray[]) {
string result[];
string delimiter("|");
string compactArray[]; // will be the same as delimitedArray, but without the "|" delimiters
int arraySizes[]; // will hold number of vals for each array
int offsets[]; // offsets will holds the indices where each new array starts.
int counters[]; // the values that will increment in the following loops, like pegs in each array
int nPemutations = 1;
int arrSize, offset, nArrays;
// do a prepass to find some information about the structure, and to build the compact array
for (s in delimitedArray) {
if (s == delimiter) {
nPemutations *= arrSize; // arrSize will have been counting elements
arraySizes[nArrays] = arrSize;
counters[nArrays] = 0; // reset the counter
nArrays ++; // nArrays goes up every time we find a new array
offsets.append(offset - arrSize) ; //its here, at the end of an array that we store the offset of this array
arrSize=0;
} else { // its one of the elements, not a delimiter
compactArray.append(s);
arrSize++;
offset++;
}
}
// put a bail out here if you like
if( nPemutations > 256) error("too many permutations " + nPemutations+". max is 256");
// now figure out the permutations
for (p=0;p<nPemutations;p++) {
string perm ="";
// In each array at the position of that array's counter
for (i=0;i<nArrays ;i++) {
int delimitedArrayIndex = counters[i] + offsets[i] ;
// build the string
perm += (compactArray[delimitedArrayIndex]);
}
result.append(perm);
// the interesting bit
// increment the array counters, but in fact the program
// will only get to increment a counter if the previous counter
// reached the end of its array, otherwise we break
for (i = 0; i < nArrays; ++i) {
counters[i] += 1;
if (counters[i] < arraySizes[i])
break;
counters[i] = 0;
}
}
return result;
}
If I understand the question correctly, I think you could put all your arrays into another array, thereby creating a jagged array.
Then, loop through all the arrays in your jagged array creating all the permutations you need.
Does that make sense?
it sounds like you've almost got it figured out already.
What if you put in there one more array, call it, say ArrayHolder , that holds all of your unknown number of arrays of unknown length. Then, you just need another loop, no?

Resources