Map using custom type

qnw4ts7pge

Member
Licensed User
Longtime User
I've had some unpredictable results using a Map where the lookup object is a custom type.

Let's say I have a custom type that looks like this:

Type CType (s1 as string, s2 as string)
dim M as map
dim C as CType
C.initialize
C.s1="somevalue"
C.s2="someothervalue1"
M.put("key",C)


1. File.WriteMap writes a string like this:
Key = [s1=somevalue, s2=someothervalue]

This works great, but File.Readmap reads it as a string and refuses to convert it to the source type. I get a java error indicating a type conversion error.

So I wrote my own routine to convert C to a delimited string and store the string map:

key = somevalue|someothervalue

Gets parsed back in to the map indicated above.

2. When I put map entries, they get corrupted. I end up with multiple map entries that are identical:

C.initialize
C.s1="bob"
C.s2="bob is cool"
M.put("key1",C)
C.initialize
C.s1="tim"
C.s2="tim sucks"
M.put("key2",C)
log(M)

Prints.....

(MyMap) {key1=[s1=tim, s2=tim sucks], key2=[s1=tim, s2=tim sucks]}

As you can see, both key1 and key2 are the same....

Any ideas about problems 1 and 2?

Thanks in advance,

-JP
 
Last edited:

qnw4ts7pge

Member
Licensed User
Longtime User
Yep -- that works!

If I do

M.put(x,C) then M.put(y,C) both x and y = C

Per your suggestion, if I do

M.put(x,C) then M.put(y,D) then x=C and y=D as expected.

HOWEVER, I have "n" elements, and I'm using a loop :-(

I could use an array? But isn't this inefficient from a memory standpoint?

Any thoughts? I REALLY appreciate the help!

Thanks in advance,

-JP
 
Upvote 0

qnw4ts7pge

Member
Licensed User
Longtime User
....Also, any thoughts on type casting from a File.ReadMap?

Strings work great, and if I implement my own parser, I can create parsable strings, then parse them on read. BUT.... if I read a custom type, I get an error.

I'd appreciate any assistance or thoughts...

Thanks in advance,

-JP
 
Upvote 0

qnw4ts7pge

Member
Licensed User
Longtime User
Thanks, @warwound...

Since I've already got the parsing logic finished, I think I'll stick with File.WriteMap and File.Readmap. I like this approach, even if I have to read / write multiple files, because it's one single command for all the file IO.

I'm much more concerned with how a Map behaves with custom Types.

Maps are nice because they are dynamically-sized, and I don't have to worry about writing linked list handlers, or guessing about array sizes. As I get more elements, I just chunk them in to a map.

So without the use of custom types, I'll end up having to use a map for each attribute, which also means that my lazy approach to saving settings means I'll have to have a settings file for each attribute:

In s1.txt:
key1 = s1value1
key2 = s1value2

In s2.txt:
key1 = s2value1
key2 = s2value2

I've also run across some odd behavior if the map file doesn't already exist... even though I use File.exists to check it ahead of time -- what I ended up having to do is this:

Dim M as map

If File.Exists(File.DirDefaultExternal,"somefile.txt") then
M=file.readmap(File.DirDefaultExternal,"somefile.txt")
else
M.initialize
file.writemap(File.DirDefaultExternal,"somefile.txt")
end if

If I don't do it this way, I get a null pointer exception reading the file. The first iteration writes an empty (zero byte) file, and the second iteration results in M=null, BUT, I don't get any exceptions.

I'd love to keep using Map, but I may end up having to take another approach if Map is buggy.
 
Upvote 0

qnw4ts7pge

Member
Licensed User
Longtime User
I found another issue with WriteMap / ReadMap.

Dim i as int
Dim M as map
M.initialize

for i=0 to 2
M.Put(i,"A"&i)
next

File.WriteMap(File.DirInternal,"m.txt",M)

log("A: "&M)
M.initialize
M=File.ReadMap(File.DirInternal,"m.txt")
log("B: "&M)

'*** A and B are the same:
'*** (MyMap) {0=A0, 1=A1, 2=A2}

i=1
M.Put(i,"B1")

log("C: "&M)

'*** C should look like this:
'*** (MyMap) {0=A0, 1=B1, 2=A2}

'*** Instead, C looks like this:
'*** (MyMap) {0=A0, 1=A1, 2=A2, 1=B1}

-----------
This took me a while to figure out. The first "1" is written as an int to the file:
1=A1

When it's read, it's read as a string.

So when you put 1-as-int,"B2", it doesn't match the 1-as-string in the map.

:-(

I think a simple way to fix this would be to include a type hint in the file format. This could be done a couple of ways:

Option 1:

Use typecasting

1(int)=A1(string)

For types, it gets a bit more complicated:

Type AType (s1 as string, s2 as string)
Dim A as AType
A.s1="hi"
A.s2="Bob"
M.Put(1,A)
File.Writemap.......M

1(int)=[s1=hi(string), s2=Bob(string)](AType)

This would require escaping parens \( and \) but would allow the parser to read the hints to figure out what type should be stuffed in to the array, and avoid type conversion errors (not sure how the parser works, so not sure how much effort this is)

This could get complex, but the "rule" would be that the type marker would always relate to the closest value. I assume nesting types works this way (I don't even want to try this yet!)

I think late typecasting (where the type follows the value) would be easier to parse, but it could be done c-style, where the type precedes the value ("early" typecasting)


Option 2:

Use function-like syntax:

int(1)=string(A1)

int(1)=AType([s1=string(hi), s2=string(Bob)])

If the parser is flexible, you might even be able to use this, which is easier to read:

int(1)=AType[s1=string(hi), s2=string(Bob)]

Or, just use brackets explicitly
int[1]=AType[s1=string[hi], s2=string[Bob]]


Maps are very useful and very powerful -- I've used them in other languages and development platforms. I think this important feature can be fixed with a few tweaks!

P.S. YES I do realize that "type name" is probably internally converted to another symbol, so type "AType" in the file might be 1101101[string[hi], string[Bob]]. 1101101 means nothing, but as long as it always maps to "AType" then it doesn't matter how it looks in the file. If someone is really worried you could drop a comment in the file with the type-to-constant mapping:

#AType=1101101
 
Last edited:
Upvote 0

qnw4ts7pge

Member
Licensed User
Longtime User
Thanks, @Erel -- I'm "old school" and from the school of thought that on a resource-constrained device, you should forgo every byte of RAM you can. I don't really need SQLLite / NoSQL functions, so text files are the right answer.

Currently, I re-coded to use multiple maps, and I've written a frag / de-frag function to split the custom type in to multiple maps. This made it relatively easy to do search and replace in the rest of my code -- Not happy about storing the "keys" multiple times in multiple maps, but it works, and it won't take up extra resources with unneeded components.

I'm MORE concerned about situation #2 (see top of post), where

Type AType (s1 as string, s2 as string)
Dim A as AType

A.initialize
A.s1="hi"
A.s2="Bob"
M.put("key1",A)
A.initialize
A.s1="Tim"
A.s2="sucks"
M.put("key2",A)

Results in key1="Tim Sucks" and key2="Tim Sucks" :-o

I assume there is a cross-linked pointer somewhere...
 
Upvote 0

qnw4ts7pge

Member
Licensed User
Longtime User
By the way --- I think it's PARAMOUNT to mention this....

I ABSOLUTELY LOVE Basic4Android!

I'm a 3-week user now.... I purchased the full version after playing with the trial for about 8 hours.

It's SAD that Google doesn't have a "pick up and play" development environment for Android -- there should be "Flash-like" tools, considering that this isn't the 80's (last I checked).

Basic4Android provides true visual development and removes 90% of the "I don't care" complexity that you really don't need unless you are a 100% full-time Java-on-Android developer.

I've looked at several Android IDE platforms, and this is the BEST I've seen!

So please don't interpret any feedback as negative. IT'S ALL GOOD!
 
Upvote 0

Erel

B4X founder
Staff member
Licensed User
Longtime User
Thank you for your feedback :)

I assume there is a cross-linked pointer somewhere...
You should create a new object for each item.
This is done by calling Dim again:
B4X:
Dim A as AType

A.initialize
A.s1="hi"
A.s2="Bob"
M.put("key1",A)
Dim A As AType 'create a new object
A.initialize
A.s1="Tim"
A.s2="sucks"
M.put("key2",A)

See this link for more information: Variables and Subs visibility
 
Upvote 0
Top