我最近发布了一个用于记录 JMP 每次启动的插件。
当我开发它时,我认为我通过将日志信息存储为关联数组。
以下是单个日志文件中内容的示例。
["Bitness" => "64-bit",
"Build Information" => "Jan 9 2017, 00:53:08, Release, JMP ",
"Date" => "06/06/2017 08:05:03 AM",
"Host" => "Windows",
"Hostname" => "computer1",
"JMP Version" => "13.1.0",
"Product Name" => "Standard",
"Username" => "joeuser"]
关联数组使日志能够自我记录,因为每条数据都被标记。 它还使日志可扩展。 因此,将来您可以轻松地向日志添加更多信息,任何现有程序都会适应或忽略它。
将充满日志文件的目录读取到关联数组列表中非常容易,如下所示:
logfiles = Files In Directory( "./Logs" );
logs = {};
For( i = 1, i <= N Items( logfiles ), i++,
Insert Into( logs, Parse( Load Text File( "./Logs/" || logfiles[i] ) ) )
);
这会产生如下所示的结构:
logs = {
["Bitness" => "64-bit",
"Build Information" => "Jan 9 2017, 00:53:08, Release, JMP ",
"Date" => "06/06/2017 02:53:43 PM",
"Host" => "Windows",
"Hostname" => "MFIDT0259",
"JMP Version" => "13.1.0",
"Product Name" => "Standard",
"Username" => "pol"],
["Bitness" => "64-bit",
"Build Information" => "Jan 9 2017, 00:53:08, Release, JMP ",
"Date" => "06/06/2017 08:30:03 AM",
"Host" => "Windows",
"Hostname" => "MFIDT0259",
"JMP Version" => "13.1.0",
"Product Name" => "Standard",
"Username" => "pol"]
};
但我没有想到的是,将该关联数组列表转换为数据表并不那么容易,该数据表中的列以键命名,列表中的每个项目都有一行。 它可以通过对列表和数组进行大量循环来完成,但我希望有一些更优雅的东西。
因此,我做了一些类似于你们许多人所做的事情,在讨论论坛中发布了一个问题。 我向我们的开发团队和 SAS 的其他专家发送了电子邮件,询问他们将如何做到这一点。
2017 年 6 月 6 日下午 6:16,杰夫·帕金森 (Jeff Perkinson) 写道:
关联数组列表 = {
[ “钥匙1” => “A” , “钥匙2” => “b” ],
[ “钥匙1” => “C” , “钥匙2” => “d” ],
[ “钥匙2” => “F” , “钥匙1” => “e” , “钥匙3” => “G” ],
};
您如何将其转换为该数据表?
请注意以下几点:
- 列根据数组的键命名
- 键以不同的顺序出现
- 每个关联数组中的键数量各不相同
我希望得到:
- JSL 阅读起来相对容易理解。
- JSL 相对容易编写,语句也最少。
- 无需大量循环即可提高效率。
@brady_brady有几个解决方案。
其中一个是创造性的黑客,依靠高度结构化的问题,将键和值转换为文本字符串,然后转换为他操作的数据表。
l = {
["key1" => "c", "key2" => "d", "key3" => "g2", "key4" => "h"],
["key1" => "a", "key2" => "e", "key3" => "g3", "key4" => "o"],
["key1" => "b", "key2" => "f", "key3" => "g1", "key4" => "i"],
["key1" => "c", "key2" => "a", "key3" => "g6", "key4" => "k"]
};
nt = New Table( "x", new column("Column 1", character), new column("Group", character) );
n_l = nitems(l);
n_k = nitems(l[1]<<GET keys="">,]")[2::2*n_l*n_k::2];
refs = words(char(l), "{} [\!"=>,]")[1::2*n_l*n_k-1::2];
column(nt,1)<<SET values=""></SET></GET>另一个,用他自己的话来说,并不令人兴奋,并且有一对嵌套循环。 但是,请注意最终语句中数据表脚本的使用。 您将在我们的获胜解决方案中再次看到这一点。
l = {
["key1" => "a", "key2" => "b"],
["key2" => "f", "key1" => "e", "key3" => "g"],
["key1" => "c", "key2" => "d", "key4" => "h"],
["key2" => "f2", "key1" => "e2", "key3" => "g2"]
};
nt = New Table( "challenge" );
colList = {};
For( i = 1, i <= N Items( l ), i++,
nt << addrows( 1 );
ki = l[i] << get keys;
For( j = 1, j <= N Items( ki ), j++,
If( !Contains( colList, ki[j] ),
nt << New Column( ki[j], character );
Insert Into( colList, ki[j] );
);
Column( nt, ki[j] )[i] = l[i][ki[j]];
);
);
nt << delete column( 1 );
l = {
["key1" => "a", "key2" => "b"],
["key1" =>"c", "key2" => "d"],
["key2" => "f", "key1" => "e", "key3" => "g"]
};
// get unique column names – alas, cannot just count items in associative arrays
colnm=l[1]; i=1;
j(nitems(l),1,colnm<<INSERT></INSERT>
这确实很聪明,因为它利用了隐式循环
J() 函数。 这是一个我不断忘记但总是努力记住的技巧。 然而,他确实有最后一个循环和一些
表达式()使阅读变得更加困难的功能。
此外,他承认假设密钥的名称一致是作弊的。 我会原谅这一点,因为这是一个可以通过他创建的数据结构的循环轻松克服的问题:
list = {
["key9" => "k9r1", "key2" => "k2r1"],
["key2" => "k2r2", "key1" => "k1r2", "key3" => "k3r2"],
["key1" => "k1r3", "key2" => "k2r3", "key4" => "k4r3"],
["key2" => "k2r4", "key1" => "k1r4", "key7" => "k7r4"]
};
nonunique = list<<GETKEYS></GETKEYS>
我喜欢这个,因为最后的循环是在数据表上,并使用我们第一次看到的数据表脚本
@brady_brady的解决方案。 您可能不同意,但对我来说,这比循环 JSL 数据结构感觉更好。 也许是因为它的字符较少,所以更容易阅读。
// Revised
list = {
["key9" => "k9r1", "key2" => "k2r1"],
["key2" => "k2r2", "key1" => "k1r2", "key3" => "k3r2"],
["key1" => "k1r3", "key2" => "k2r3", "key4" => "k4r3"],
["key2" => "k2r4", "key1" => "k1r4", "key7" => "k7r4"]
};
unique=list[1];
i=1;
j(nitems(list),1,unique<<INSERT></INSERT>
如果 J() 函数及其隐式循环使您更难以阅读, @约瑟夫_摩根指出它可以用 For() 循环替换。
for( i=2,i<=nitems(list),i++, unique<<INSERT></INSERT>
这对我研究每个解决方案并在这里看到一些我以后可能会使用的技巧确实很有启发。 花一些时间为自己做同样的事情。
与任何语言一样,有很多方法可以完成同一件事。 每个都有其优点。 诀窍是了解它们并将它们应用在最有利的地方。
你好呀。 我正在阅读这篇文章,感觉比需要的更复杂。 我能够得到下面的解决方案,代码量非常小,并且可以通过更多的努力进一步减少。
//perc_array is my associative array. it has two levels if you want to call them that. the first is a text string, the second is numeric;<BR /><BR />perc_array= [1 => "Sum(Qty 1kHz)", 2 => "Sum(Qty 1KHZ_LT50)", 3 => "Sum(Qty 50Hz_High)", 4 => "Sum(Qty 50Hz_Low)", 5 => "Sum(Qty DC)", 6 => "Sum(Qty ERROR)", 7 => "Sum(Qty GA1GA2)", 8 => "Sum(Qty Idd)", 9 => "Sum(Qty Idd_Low)", 10 => "Sum(Qty THD)", "Sum(Qty 1kHz)" => 0.00202316156420758, "Sum(Qty 1KHZ_LT50)" => 0.000757081600136874, "Sum(Qty 50Hz_High)" => 0.0176374349048836, "Sum(Qty 50Hz_Low)" => 0.00692494412780564, "Sum(Qty DC)" => 0.00482906851160752, "Sum(Qty ERROR)" => 0.00296416694290878, "Sum(Qty GA1GA2)" => 0.124725985649668, "Sum(Qty Idd)" => 0.00232257236652159, "Sum(Qty Idd_Low)" => 0.000327213233957462, "Sum(Qty THD)" => 0.000032079728819359];<BR /><BR />//the denominator below is how many "columns" of keys you have or how many levels;
ncheck=n items(perc_Array())/2;<BR /><BR />//xx is the variable I use to know how many "rows" i have in the associative array;
dt_sort=new table("SortFT", Add Rows( xx-1 ),
New Column( "Label", Character),
New Column( "%", Numeric, Format("Percent",2));
);
for(i=1, i<=ncheck, i++,
dt_sort:Label[i]=perc_array[i];
dt_sort:Name("%")[i]=perc_array[dt_sort:Label[i]];<BR />
);杰克
我在原始文章中推荐的方法中遇到了一些问题,与此循环相关,其中将值插入到每行的表中:
<SPAN class="token function">foreachrow</SPAN><SPAN class="token punctuation">(</SPAN>
i <SPAN class="token operator">=</SPAN> <SPAN class="token function">row</SPAN><SPAN class="token punctuation">(</SPAN><SPAN class="token punctuation">)</SPAN><SPAN class="token punctuation">;</SPAN>
dt<SPAN class="token punctuation">[</SPAN>i<SPAN class="token punctuation">,</SPAN>list<SPAN class="token punctuation">[</SPAN>i<SPAN class="token punctuation">]</SPAN><SPAN class="token operator"><</SPAN><SPAN class="token operator"><</SPAN><SPAN class="token messages">getkeys] </SPAN><SPAN class="token operator">=</SPAN> list<SPAN class="token punctuation">[</SPAN>i<SPAN class="token punctuation">]</SPAN><SPAN class="token operator"><</SPAN><SPAN class="token operator"><</SPAN><SPAN class="token messages">getvalues</SPAN><SPAN class="token punctuation">;</SPAN>
<SPAN class="token punctuation">)</SPAN><SPAN class="token punctuation">;</SPAN>
- 一旦在关联数组(行)之一中遇到缺失值(或缺少键),就会抛出此异常。
- 仅将字符串(字符)数据添加到表列中。源中的任何数字数据都将被忽略,从而导致值为数字的空列。
- 如果遇到完全数字数据,则列类型保留为字符,并且不会强制为数字。
我最终得到了以下结果,似乎可以解决这些问题:
aaListToDataTable = function ({aaList}, {Default Local},
unique=aaList[1];
for (i=1, i<=nitems(aaList), i++,
unique<<INSERT></INSERT>没有在特别大的数据集上测试它的性能。
哇, @杰夫·珀金森和@菲尔_布朗,我来寻找一些有关通过关联数组进行索引的一般内容,我得到了更多!
作为最近转换为关联数组(我过去有很多嵌套列表:\ )我正在尝试更频繁地使用它们,并且仅在这个线程中就必须至少有 4 个单独的想法,我可以将其合并到我的代码的各个集合中!
感谢您发布此内容:)
我还喜欢这样一个事实:您不需要知道有多少元素。 While( IsEmpty(...) ) 允许数组长度是动态的。
很有意思! 我可以提供另一种方法,类似于布雷迪的方法,但略有不同。
list = {
["key9" => "k9r1", "key2" => "k2r1"],
["key2" => "k2r2", "key1" => "k1r2", "key3" => "k3r2"],
["key1" => "k1r3", "key2" => "k2r3", "key4" => "k4r3"],
["key2" => "k2r4", "key1" => "k1r4", "key7" => "k7r4"]
};
newdt = New Table();
cols ={};
For( i = 1, i <= N Items( list ), i++,
newdt << addRows( 1 );
cKey = list[i] << first;
While( !Is Empty( cKey ),
If( !Contains(cols, cKey),
newdt << newColumn( cKey, Character );
InsertInto( cols, cKey);
);
Column( cKey )[i] = list[i][ckey];
cKey = list[i] << next( cKey );
);
);
newdt << deleteColumns( 1 );
newdt << reOrderByName;