I have a list that is a key value paired list. Something like the following
key1 value1 key2 value2 key3 value3
I would like to map this to an array or a dictionary.
Currently, my code looks like this
for {set i 0} {$i < [llength $list]} {incr i} {
if {[expr {fmod($i,2)}] == 0} {
set key [lindex $list $i]
} else {
set v_array(${key}) [lindex $list $i]
}
}
In perl, I know this can be assigned into a key value dictionary in one pass. Is there such simpler method in Tcl?
You can create an array in one line (I'm using one line to define the list):
% set list [list key1 value1 key2 value2 key3 value3]
key1 value1 key2 value2 key3 value3
% array set v_array $list
And if you want to check the contents, you can use parray (Tcl 8.5 and later):
% parray v_array
v_array(key1) = value1
v_array(key2) = value2
v_array(key3) = value3
And the documentation for the other array commands can be found here with examples for each.
If you somehow cannot avoid a loop, then using foreach would be easier (be sure the list has an even number of elements):
foreach {a b} $list {
set v_array($a) $b
}
(Here foreach is taking the elements in $list two at a time and assign them to a and b)
You can use dict command for creating/manipulating dictionaries in Tcl.
% set mydict [dict create key1 value1 key2 value2 key3 value3]
key1 value1 key2 value2 key3 value3
% dict get $mydict
key1 value1 key2 value2 key3 value3
% dict get $mydict key3
value3
% dict get $mydict key1
value1
%
Even without the dict create command, you can straightway fetch/access the keys and values even from a list as long as it is in key-value form. i.e. even number of elements.
For example,
% set mylist {key1 value1 key2 value2 key3 value3}
key1 value1 key2 value2 key3 value3
% dict get $mylist key2
value2
As you can notice, I have not used dict create command here , but still able to access the dictionary items.
Reference : dict
Easiest solution:
set mylist {key1 value1 key2 value2 key3 value3}
array set arr $mylist
Thats it.
Now, do a parray to check.
file: t3
#
set list [list key1 value1 key2 value2 key3 value3]
array set arr $list
parray arr
#
Execute the file: tclsh t3
arr(key1) = value1
arr(key2) = value2
arr(key3) = value3
Related
I'm trying to parse an ascii text file that looks like this.
KEY1 VAL1
KEY2 VAL2
KEY3 VAL3
KEY4 VAL4
KEY5 VAL5
KEY6 VAL6
KEY7 VAL7
KEY8 VAL8
KEY9 VAL9
I would like to convert this to a flat table of values from KEYs 1,5,7. I have a very ugly brute force algorithm that loops through the file and sets flags to read values, but that doesn't appear most efficient.
something like:
set f [open $filename]
set data [split [read $f] "\n"]
foreach line $data {
if {[string match KEY1* $line] ==1} {set key1match 1}
if {($keymatch1==1) && ([string match KEY5* $line] ==1} {set key5match 1}
...
Is there a more elegant way to generate this mapping?
Is this what you wanted?
set keylist {}
set keyset {KEY1 KEY5 KEY7}
set flatDict {}
foreach line [split [string trim $input] \n] {
if {[regexp {(\s*)(\w+)\s*(.*)} $line -> indent key val] && $key in $keyset} {
set level [expr {[string length $indent] / 2}]
set keylist [lrange $keylist 0 $level]
lappend keylist $key
dict set flatDict $keylist $val
}
}
% set flatDict
KEY1 VAL1 {KEY1 KEY5} VAL5 {KEY1 KEY5 KEY7} VAL7
This code keeps a list of keys, keylist, that grows (by lappend) and contracts (by lrange) according to indentation (and is completely dependent on indentation being correct). Only keys in a given set, keyset are considered. For each value added to the dictionary, the current $keylist is used as a key (the dict command can deal with key hierarchies, but then the keys must be separate and not inside a list (e.g. dict set myDict foo bar 123).
Documentation:
&& (operator),
/ (operator),
dict,
expr,
foreach,
if,
in (operator),
lappend,
lrange,
regexp,
set,
split,
string,
Syntax of Tcl regular expressions
Afterthought: with your selection of keys there is actually no need to contract the key list. If you only use keys that follow a single line of descent from the root, you could use this code:
set keylist {}
set flatDict {}
foreach line [split [string trim $input] \n] {
set val [lassign [split [string trim $line]] key]
if {$key in $keyset} {
lappend keylist $key
dict set flatDict $keylist $val
}
}
% set flatDict
KEY1 VAL1 {KEY1 KEY5} VAL5 {KEY1 KEY5 KEY7} VAL7
Note that in both examples, I have provided for values that might contain whitespace. The code can be made a little more regular if the value is always atomic.
Here's some code to parse that data into a dictionary:
set indent_width 2
set d [dict create]
set fh [open [lindex $argv 0] r]
while {[gets $fh line] != -1} {
regexp {^(\s*)(\S+)\s*(.*)} $line -> indent key value
if {$key eq ""} continue
set level [expr {[string length $indent] / $indent_width}]
dict set d $key level $level
dict set d $key value $value
dict set d $key children [list]
dict set d $key parent ""
dict set d last $level $key
set prev_level [expr {$level - 1}]
if {$prev_level >= 0} {
set parent_key [dict get $d last $prev_level]
dict update d $parent_key item {
dict lappend item children $key
}
dict set d $key parent $parent_key
}
}
dict unset d last
dict for {key value} $d {puts [list $key $value]}
outputs
KEY1 {level 0 value VAL1 children {KEY2 KEY5} parent {}}
KEY2 {level 1 value VAL2 children KEY3 parent KEY1}
KEY3 {level 2 value VAL3 children KEY4 parent KEY2}
KEY4 {level 3 value VAL4 children {} parent KEY3}
KEY5 {level 1 value VAL5 children KEY6 parent KEY1}
KEY6 {level 2 value VAL6 children KEY7 parent KEY5}
KEY7 {level 3 value VAL7 children {} parent KEY6}
KEY8 {level 0 value VAL8 children KEY9 parent {}}
KEY9 {level 1 value VAL9 children {} parent KEY8}
I have a nested array as below:
array set arrayA {0 {1 a 2 b 3 c 4 d}}
If I want to update the arrayA like this:
set arrayA(0)(1) "update"
It can't get {0 {1 update 2 b...}}, how to get it? Thanks!
Tcl arrays can't be nested that way, but your code is still valid. In arrayA, the value of element 0 is a dict, so you can get and set members in it with dict operations:
% dict get $arrayA(0) 1
a
% dict set arrayA(0) 1 update
1 update 2 b 3 c 4 d
Another alternative is to use composite names for the array members:
array set arrayA {0.1 a 0.2 b 0.3 c 0.4 d 1.1 aa 1.2 ab}
and access them with arrayA(0.1), arrayA(0.$foo) etc. Which separator character to use is mostly a question of preference, the only rule is that the name must be a proper list. You don't even really need a separator, as long as you always keep the element name in a variable:
% array set arrayA {{0 1} a {0 2} b}
% set idx {0 1}
0 1
% set arrayA($idx)
a
Documentation:
array,
dict
I have a simple JSON file which I'm attempting to coerce into an R data.frame.
json = "
{ \"objects\":
{
\"object_one\": {
\"key1\" : \"value1\",
\"key2\" : \"value2\",
\"key3\" : \"0\",
\"key4\" : \"value3\",
\"key5\" : \"False\",
\"key6\" : \"False\"
},
\"object_two\": {
\"key1\" : \"0.5\",
\"key2\" : \"0\",
\"key3\" : \"343\",
\"key4\" : \"value4\",
\"key5\" : \"True\",
\"key6\" : \"True\"
}
}
}
"
and I simply want to extract the name of each object as a index key (or rowname), create column names from the keys and spread the values.
Unfortunately I've had no luck unpicking the syntax. Can anyone help?
Thanks
Stuart
There are two ways to do this with tidyjson, the first is to use tidyjson::append_values_string and then tidyr::spread:
library(tidyjson)
library(dplyr)
library(tidyr)
json %>%
enter_object("objects") %>%
gather_keys("object") %>%
gather_keys("key") %>%
append_values_string("value") %>%
tbl_df %>% spread(key, value)
#> # A tibble: 2 x 8
#> document.id object key1 key2 key3 key4 key5 key6
#> * <int> <chr> <chr> <chr> <chr> <chr> <chr> <chr>
#> 1 1 object_one value1 value2 0 value3 False False
#> 2 1 object_two 0.5 0 343 value4 True True
The other way is to use tidyjson::spread_values to specific each key separately:
json %>%
enter_object("objects") %>%
gather_keys("object") %>%
spread_values(
key1 = jstring("key1"),
key2 = jstring("key2"),
key3 = jnumber("key3"),
key4 = jstring("key4"),
key5 = jstring("key5"),
key6 = jstring("key6")
)
#> document.id object key1 key2 key3 key4 key5 key6
#> 1 1 object_one value1 value2 0 value3 False False
#> 2 1 object_two 0.5 0 343 value4 True True
The advantage of the second approach is that you can (a) specify the types of each column and (b) will be guaranteed to get the same data.frame structure even if the keys change (or are missing) in some documents or objects.
Not entirely sure on your desired output, but you can use jsonlite::fromJSON to extract the data, and data.table::rbindlist to put it into a data.table
library(jsonlite)
library(data.table)
rbindlist(fromJSON(json))
# object_one object_two
# 1: value1 0.5
# 2: value2 0
# 3: 0 343
# 4: value3 value4
# 5: False True
# 6: False True
Based on your comment, another approach that involves some reshaping
library(jsonlite)
library(reshape2)
lst <- fromJSON(json)
lst <- lapply(lst[[1]], unlist)
df <- as.data.frame(lst)
df$key <- rownames(df)
df <- melt(df, id = "key")
df <- dcast(df, formula = variable ~ key)
df
# variable key1 key2 key3 key4 key5 key6
# 1 object_one value1 value2 0 value3 False False
# 2 object_two 0.5 0 343 value4 True True
I currently have something like this
(Desktop) 1 % dict set mymap keyA "KeyA value"
keyA {KeyA value}
(Desktop) 2 % dict set mymap keyB "KeyB value"
keyA {KeyA value} keyB {KeyB value}
(Desktop) 3 % dict get $mymap keyB
KeyB value
(Desktop) 4 % dict set mymap keyC {KeyD "keyD Value"}
keyA {KeyA value} keyB {KeyB value} keyC {KeyD "keyD Value"}
(Desktop) 5 % dict get $mymap keyC.keyD
key "keyC.keyD" not known in dictionary
The keyC basically contains another dictionary. That dictionary has a key called keyD with a value "KeyD Value" . How can i obtain the value of the keyD ? I tried keyC.keyD
Update :
The reason I need this is because I have something like this
(Desktop) 20 % puts $mymap
{$schema} http://json-schema.org/draft-04/schema# title Product description {A product from Acme's catalog} type object properties {id {description {The unique identifier for a product} type integer}} required id
And I require the value of description from within id. The above is a json object converted to a dictionary
Just put the keys one after each other as mentioned in the docs:
dict get $mymap keyC KeyD
But do be careful about the case, keyD and KeyD are not the same thing.
proc test {a b c } {
puts $a
puts $b
puts $c
}
set test_dict [dict create a 2 b 3 c 4 d 5]
Now I want to pass dict into test like this:
test $test_dict
How to make test only selects three elements in the dict with the same name of its parameters (the keys). The expected output should be:
2
3
4
Because it selects a b c in the dictionary but not d. How can I do this? I saw some code does like this but I can't make it work.
I think you should use dict get:
proc test {test_dic} {
puts [dict get $test_dic a]
puts [dict get $test_dic b]
puts [dict get $test_dic c]
}
set test_dict [dict create a 2 b 3 c 4 d 5]
test $test_dict
Edit:
Another variant would be to use dict with:
proc test {test_dic} {
dict with test_dic {
puts $a
puts $b
puts $c
}
}
set test_dict [dict create a 2 b 3 c 4 d 5]
test $test_dict
But test gets still a list.