Turning an integer into an array of its digits

Get help using and writing Nisus Writer Pro macros.
js
Posts: 242
Joined: 2007-04-12 14:59:36

Turning an integer into an array of its digits

Post by js » 2019-09-27 07:08:09

I would like to transform a multi digit integer into an array made of its digits.
Using the split function I can transform $int = 12345 into $list = ("1", "2", "3", "4", "5")

But $arr = Array.new($list) does not produce an array with 5 elements. Rather the macro understands that my $list has only 1 element (("1", "2", "3", "4", "5")). How can this be dealt with?

User avatar
phspaelti
Posts: 1033
Joined: 2007-02-07 00:58:12
Location: Japan

Re: Turning an integer into an array of its digits

Post by phspaelti » 2019-09-27 07:57:58

If you want to add the digits from $list into $arr you can use:

Code: Select all

$arr = Array.new
$arr.appendValuesFromArray $list
But $list is already an array. So the only reason to do this is if you want to combine several arrays together.
And as you can see from your own experiment, Nisus arrays can themselves have other arrays as elements/values.
philip

js
Posts: 242
Joined: 2007-04-12 14:59:36

Re: Turning an integer into an array of its digits

Post by js » 2019-09-27 08:54:16

Thank you Philipp. It seems though that I make a mistake somewhere.

My selection ("1", "2", "3", "4", "5") is put on the clipboard
Following your advice I execute:

$sel = Read Clipboard
$arr = Array.new
$arr.appendValuesFromArray $sel
prompt $arr

What I get is: "Expected a value of type Array, found Text."

User avatar
phspaelti
Posts: 1033
Joined: 2007-02-07 00:58:12
Location: Japan

Re: Turning an integer into an array of its digits

Post by phspaelti » 2019-09-27 09:28:39

Well, yes. Once you write out the array in the form '("1", "2", "3", "4", "5")' on the clipboard then it stops being an array, and now it's just a piece of text. So then you couldn't use the command .appendValuesFromArray. It's important to understand that internally an array is a complicated structure with the individual elements/values in separate locations. The '("1", "2", "3", "4", "5")' is just a convenient way to show the array.

Earlier you said you were using the .split command. I was under the impression that you knew how to do that. It should be said that the .split command only works on strings, not on integers.

So to recap the following ways to get an array would work:

Code: Select all

$arr = Array.new("1", "2", "3", "4", "5")

Write Clipboard '12345'
$sel = Read Clipboard
$arr = $sel.split('')

$int = 12345
$str = Cast to String $int
$arr = Array.new
$arr.appendValuesFromArray $str.split('')
philip

martin
Official Nisus Person
Posts: 4595
Joined: 2002-07-11 17:14:10
Location: San Diego, CA
Contact:

Re: Turning an integer into an array of its digits

Post by martin » 2019-09-27 09:34:09

Your question is a bit confusing. You asked how to transform an "integer into an array", but then said you already made such an array using the split function. It's true that strategy works just fine:

Code: Select all

$number = 12345
$text = "$number"
$list = $text.split("")
Prompt $list
At that point the $list variable already contains the array you wanted. You don't need to create another array.

In your second post it sounds like you're not really dealing with an integer, but rather a multi-part selection that happens to include digits. Maybe what you're after is an array that contains each individually selected piece of text? If so this code will do the trick:

Code: Select all

$doc = Document.active
$selection = $doc.selection
$list = $selection.substrings
Prompt $list
You also mentioned the clipboard. If you really need to go through copy-paste then you'll have to split the text you receive from the "Read Clipboard" command. That always returns a single chunk of text. You'll have to split it somehow, eg:

Code: Select all

$text = Read Clipboard
$list = $text.split("\n")
Prompt $list

js
Posts: 242
Joined: 2007-04-12 14:59:36

Re: Turning an integer into an array of its digits

Post by js » 2019-09-27 11:00:29

Thank you both Philipp and Martin.

My main error was to ignore that the split function creates an array without further ado, and a lot of confusion if you do ignore that.
As to the word integer I always thought that this is another word for a whole number. Is that not correct?
What I wanted was in fact as simple as this: to get the digits of a whole number onto an array, to later manipulate them, f.e. to turn the number upside down and the like.

In fact my purpose was dealing with the hexagrams of the Yijing which come as binary numbers, which are then transformed in various ways, as you probably know. To get the binary forms I use a hash, and that is also why I copy its results with the clipboard to make use of it in the actual macro manipulating them. (Or is there a better way to make use of the output of one macro in another macro?)
The reason why you can't the binaries directly from the 64 hexagrams themselves is because the office responsible for Unicode decided to place them not in their inherent numerical (binary) order, but in a transmitted historical order which nobody really understands. (Use Text.newWithCharacter beginning with 19904 to call them).

Thanks again for your help.

User avatar
phspaelti
Posts: 1033
Joined: 2007-02-07 00:58:12
Location: Japan

Re: Turning an integer into an array of its digits

Post by phspaelti » 2019-09-27 22:02:24

js wrote:
2019-09-27 11:00:29
To get the binary forms I use a hash, and that is also why I copy its results with the clipboard to make use of it in the actual macro manipulating them. (Or is there a better way to make use of the output of one macro in another macro?)
The best way to pass such info between macros is to use a Registry object. This is because registries can handle arrays and hashes without any fuss. On the clipboard you will have to join the values first, and then split them when retrieving them again.

To use a registry you have to: (1) decide which type of registry (macro, application, user) to use and (2) settle on a naming scheme for the variables you want to store. In your case you can rule out a macro registry, since they only last as long as the macro runs. For the naming scheme, adapting the advice in the Macro Language Reference, I tend to go for something like "usr.<macroname>.<variablename>". So if your variable is something like "binaryHash", then you could use 'usr.YijingMacro.binaryHash'.
So now you can save your variable like this:

Code: Select all

$reg = Registry.userRegistry
$reg.saveValueWithName ($binaryHash, 'usr.YijingMacro.binaryHash')
Then, in the other macro, in order to retrieve the hash, use:

Code: Select all

$reg = Registry.userRegistry
$binaryHash = $reg.loadValueWithName ('usr.YijingMacro.binaryHash')
philip

js
Posts: 242
Joined: 2007-04-12 14:59:36

Re: Turning an integer into an array of its digits

Post by js » 2019-09-29 04:19:35

Thank you, Philip. I tried this and it works well for me.
A thing I still don't quite understand is about hashes. There is a command to retrieve a value that goes with a key. But how can I retrieve the key if I know the value? Is it necessary to somehow apply the foreach function. Unfortunately I did not find a suitable example in the Macro Reference. I am still hoping that one day there will be something more of a kitchen book with lots of practical applications.

User avatar
phspaelti
Posts: 1033
Joined: 2007-02-07 00:58:12
Location: Japan

Re: Turning an integer into an array of its digits

Post by phspaelti » 2019-09-29 04:55:49

js wrote:
2019-09-29 04:19:35
But how can I retrieve the key if I know the value?
A hash is a one-way function. So, you cannot do that. In fact since many keys may map to the same value, it may be physically impossible to "reverse the hash".

If you did want to do that you would have to use a foreach loop and loop over the keys until you find the value. But if you are wanting to do this regularly, then this is not the right approach anyway. The better approach is to set up a reverse hash. And here's an interesting point about hashes. If your keys and values are fundamentally different things, you can use just a single hash to go both ways.

For example if your hash maps binary representation of hexagrams onto Unicode numbers like this:
binary Unicode
000000 4DC1
000001 4DD6
000010 4DC7
000011 4DD3
000100 4DCF
000101 4DE2
000110 4DEC

Then you could have your hash return "4DD6" for "000001" and vice versa. Since the two types of values are nothing alike it will be clear which is meant. If the keys and values overlap, then just make a second hash to go the other way. But remember that such a reverse hash will work correctly only if all values are distinct.

So assuming that this is the case and you already have one hash (called myHash for illustration) that maps keys onto distinct values, you can use the following code to create a reverse hash.

Code: Select all

$reverseHash = Hash.new
foreach $key, $value in $myHash
    $reverseHash{$value} = $key
end
philip

js
Posts: 242
Joined: 2007-04-12 14:59:36

Re: Turning an integer into an array of its digits

Post by js » 2019-09-29 05:38:18

Thank you Philip, for your good explaining, as well as for your example hash which I would recommend Nisus to include in the Reference file.

martin
Official Nisus Person
Posts: 4595
Joined: 2002-07-11 17:14:10
Location: San Diego, CA
Contact:

Re: Turning an integer into an array of its digits

Post by martin » 2019-09-30 13:27:00

js wrote:
2019-09-29 04:19:35
A thing I still don't quite understand is about hashes. There is a command to retrieve a value that goes with a key. But how can I retrieve the key if I know the value?
Philip already gave a good reply to this question, but if you're not concerned about performance then you don't have to pre-build the reverse hash. You could instead use a custom macro function to search the hash each time. Here's a custom function that builds an array of all hash keys with a particular value:

Code: Select all

Define Command HashKeysForValue( $hash, $findValue )
	$keys = Array.new
	ForEach $key, $value in $hash
		If $value == $findValue
			$keys.appendValue($key)
		End
	End
	Return $keys
End

$hash = Hash.new("a", 1, "b", 2, "c", 3, "AA", 1)
$keys = HashKeysForValue( $hash, 1 ) # yields the array ("a", "AA")

martin
Official Nisus Person
Posts: 4595
Joined: 2002-07-11 17:14:10
Location: San Diego, CA
Contact:

Re: Turning an integer into an array of its digits

Post by martin » 2019-09-30 13:34:21

phspaelti wrote:
2019-09-27 22:02:24
The best way to pass such info between macros is to use a Registry object. This is because registries can handle arrays and hashes without any fuss.
Another way to pass data between macros is using the argument hash. One macro can call another macro and pass it an arbitrary hash object containing whatever keys and values it wants.

As an example let's create two macros that share a simple message. The sending macro defines the message, and the receiving macro shows the arbitrary message in an alert. Let's create the receiving macro first:

Code: Select all

# This is the macro that receives the message.
$args = Macro.arguments
$message = $args{"message"}
If ! $message
	Exit "This macro expects some arguments."
End
Prompt "Hello World!", "The message was: $message"
Save the above macro to the Macros folder and Macros menu using the name "HelloWorld". We can now create the sending macro:

Code: Select all

# This is the sending macro
$args = Hash.new("message", "secret sender")
Macro.run("HelloWorld", $args)
The sending macro calls the "HelloWord" macro from the menu and passes it a Hash object.

js
Posts: 242
Joined: 2007-04-12 14:59:36

Re: Turning an integer into an array of its digits

Post by js » 2019-10-02 03:03:32

Interesting to have variant solutions. I tried all successfully. Thank you. I wonder if I may ask another, related question: If individual values of an hash contain several words or numbers, is there an easy way of creating a subhash with only pairs containing those words or numbers. Like

$myhash = hash.new("Anna", "born 1997", "Luis", "starts schooling 1997", "Peter", "born 1998")

looking for a subhash with values containing 1997.

User avatar
phspaelti
Posts: 1033
Joined: 2007-02-07 00:58:12
Location: Japan

Re: Turning an integer into an array of its digits

Post by phspaelti » 2019-10-02 04:41:18

If the hash sample you are giving us is representative you could achieve this with code like the following, which is just a variant of the code I provided earlier for creating a reverse hash.

Code: Select all

$subhash = Hash.new()
foreach $key, $value in $myhash
    if $value.find('1997')
        $subhash{$key} = $value
    end
end
This should work if all the values in $myhash are strings/text, so that you can do a find on them.

But why do you want to do this? If you want to find the keys that return such values, then why don't you just keep them (preferably in an array)? Or test on the fly? The only "advantage" of the subhash is that it will give you "key undefined" errors, if you ask about other keys.
Last edited by phspaelti on 2019-10-02 06:21:14, edited 2 times in total.
philip

js
Posts: 242
Joined: 2007-04-12 14:59:36

Re: Turning an integer into an array of its digits

Post by js » 2019-10-02 05:34:27

But why do you want to do this? If you want to find the keys that return such values, then why don't you just keep them (preferably in an array)? Or test on the fly? The only "advantage" of the subhash is that it will give you "key undefined" errors, if you ask about other keys.
Yes. All I want is to get an array of the keys that return such values. I thought I had to first create a subhash and then pull out the keys into an array. Is there a direct way to do this?

Post Reply