Home > Mobile >  How can I parse a string in Netlogo?
How can I parse a string in Netlogo?

Time:12-07

Context

For my model I would like to have an input where the user can enter a series of values.

E.g.

enter image description here

What I would like to have from the input shown above is a list of five numbers, e.g. [0.5 0.2 0 0.2 0.5], so I can then use the numbers that they enter for some calculations.

The problem

Unfortunately, having an input set up like above will spit out "0.5 0.2 0 0.2 0.5" if I set the type to be a string. If I set the type to numeric, it will only allow a single number to be entered.

So, how can I parse the string the basis of a space (i.e. " ")? I am open to alternatives too, although I would prefer to keep it in Netlogo (e.g. not reading in a text file of values) to make it easier to change, as it is something that I suspect will get played around with a lot.

What I have tried

I have tried using read-from-string, but it also doesn't like a series of numbers entered like above. I also attempted to use the explode function from the string extension (https://github.com/NetLogo/String-Extension), but my version of Netlogo (6.2.0) did not like the API from that extension and wouldn't allow me to use it.

I am very new to NetLogo, so sorry if my question is silly or I have not made something clear!

CodePudding user response:

As per the docs on it, read-from-string can parse a list of literal values. The issue you are having is that a NetLogo list literal must have square brackets to open and close, as per the Constant Lists section of the Programming Guide. So all you need to do is add [ and ] to your user's input.

to test
  let s "0.5 0.2 0 0.2 0.5"
  let l read-from-string (word "[" s "]")
  show l
  show item 2 l
end

Output:

observer> test
observer: [0.5 0.2 0 0.2 0.5]
observer: 0

I would caution, though, that it would be very easy for users to enter numbers with a different format, like 0, 2, 3, 5.0, using commas to separate values. A check that the conversion actually worked would be wise, as the error message you'd get from the failed read-from-string probably wouldn't be helpful to the model user.

CodePudding user response:

You can do it with a combination of position, substring, read-from-string and fput.

This is the workflow:

  1. Create a loop that goes on as long as the string contains more than one number (= as long as it contains at least one space, checked using position " " string);
  2. Extract a substring that goes from the first character to the first space excluded (done with substring);
  3. Read that substring as a numeric value (with read-from-string) and add it to list-of-numbers (with fput);
  4. Drop the first number in the string (using position " " string, repeat and but-first) and start the loop again;
  5. When the loop condition evaluates as FALSE it means that only one number is left in the string. Add that last number (i.e. the whole remaining string) to list-of-numbers outside the loop, and it's all done.

The procedure below is a reporter procedure that executes this workflow and reports a list of values as read from the string (it only needs a user-string input box in the Interface):

to-report convert-user-string [str]
  let temp-string user-string
  let list-of-numbers (list)
  
  while [position " " temp-string != FALSE] [
   let next-number-as-string  (substring temp-string 0 position " " temp-string)
   set list-of-numbers lput (read-from-string next-number-as-string) (list-of-numbers)
   
   repeat (position " " temp-string   1) [
     set temp-string (but-first temp-string)
   ]
  ]
  
  set list-of-numbers lput (read-from-string temp-string) (list-of-numbers)
  
  report list-of-numbers
end

So for example:

observer> set user-string "0.5 0.2 0 0.2 0.5"
observer> show user-string
observer: "0.5 0.2 0 0.2 0.5"
observer> show convert-user-string user-string
observer: [0.5 0.2 0 0.2 0.5]


The procedure I posted above is the condensed version of the initial code I made, that I am leaving here below abundantly commented:

globals [
  list-of-numbers   ; The list where values from the input string will be stored.
  
  temp-string       ; A temporary variable being the alter-ego of 'user-list'. This is needed because
                    ; the 'trim-string-to-next-nonspace' procedure won't let me change the value of
                    ; 'user-string' directly (I am not sure why, anyone please feel free to say if I'm
                    ; missing something here) but also because you might want to keep the value of the
                    ; user input intact - hence we use this 'temp-string' to trim the string without worries.
]


to convert-user-string [str]
; As long as there are at least two numbers in the string (identified by the presence of at least one
; space), the while loop extracts the first number with 'substring' and then assigns it as a numeric
; value to 'list-of-numbers' by using 'read-from-string' and 'lput'. At that point, it trims the
; string up to the next non-space character.
; When there is only one number left in the string (identified by the absence of spaces in the string),
; the 'more-than-one-number-in-string? temp-string'condition evaluates as 'FALSE' and the while loop
; stops. At that point, the last line of code adds what is left of the string (i.e. the last number)
; to the 'list-of-numbers' list.
  
  set list-of-numbers (list)   ; Initiating this variable as a list in order to be able to use 'lput'.
  
  set temp-string user-string
  
  while [more-than-one-number-in-string? temp-string] [
   let next-number-as-string  (substring temp-string 0 position-of-next-space temp-string)
   set list-of-numbers lput (read-from-string next-number-as-string) (list-of-numbers)
   
   trim-string-to-next-nonspace temp-string
  ]
  
  set list-of-numbers lput (read-from-string temp-string) (list-of-numbers)
end


to-report more-than-one-number-in-string? [str]
; This reporter is needed as a condition for the while loop in 'convert-user-string'. The reason is that
; the 'position' command (used by the 'position-of-next-space' procedure) reports either a number (i.e.
; the position of the character in the given string) or 'FALSE' (in case the item is not present in the
; string). Therefore, this procedure is needed in order to get either TRUE or FALSE to be used in the
; while condition.
  
  ifelse (position-of-next-space str = FALSE)
   [report FALSE]
   [report TRUE]
end


to-report position-of-next-space [str]
; Simply reporting the position of the next space in the string. Note that positions (indexes) in NetLogo
; are numbered starting from 0.
  
  report position " " str
end


to trim-string-to-next-nonspace [str]
; By using 'but-first' repeatedly, this procedure gets rid of the first number (which has already been stored
; in 'list-of-numbers' by the 'convert-user-string' procedure) and the following space in the string.
; Note that the '  1' bit is needed because the count of positions in NetLogo starts from 0 for the first item.

  let x temp-string
  
  repeat (position-of-next-space temp-string   1) [
   set x (but-first x) 
  ]
  
  set temp-string x
end
  • Related