可靠的标量与数组判断方法
9028 评价本文:
暂无评分
可靠的标量与数组判断方法
匿名作者 2016年3月31日 星期四
过去我曾写过关于识别未定义变量与!NULL以及重写IDL_Object方法的隐患的博客。今天我想基于相关主题,讨论如何准确判断一个变量是标量还是数组。
有人可能会疑惑为什么要关注这个问题,但在某些情况下,标量值和单元素数组的表现相同,而在另一些情况下则不同。一个非常简单的例子出现在比较运算符和WHERE子句中:
IDL> a = IndGen(10)
IDL> Where(a GT 5)
6 7 8 9
IDL> Where(a GT [5])
-1
当我在GT比较中使用标量阈值时,会得到预期结果。如果将该阈值转换为单元素数组,则会得到-1,表示没有符合条件的值。如果我们查看WHERE子句内比较表达式返回的结果,就能明白原因:
IDL> a GT 5
0 0 0 0 0 0 1 1 1 1
IDL> a GT [5]
0
标量比较返回与变量"a"维度相同的布尔值数组,而数组比较则返回标量。这是因为GT比较运算符基于较小维度的操作数子集进行操作:
IDL> a GT [0]
0
IDL> a GT [-1]
1
IDL> a GT [-1, 0, 1]
1 1 1
IDL> a GT [-1, 0, 2]
1 1 0
GT运算符是向量化的,它会在每个对应索引的值上进行逐对比较,直到达到较小的最大索引为止。当我们使用数值字面量时,这种行为很容易识别,但如果使用变量,就必须检查该变量以获得预期行为。在我的工作中,单元素数组的一个常见来源就是WHERE子句本身——失败返回值-1是标量,但成功返回值总是数组(无论是单元素还是多元素)。一个简单的解决方案是始终使用索引0来访问变量以使其标量化,但如果你确实需要数组,就必须注意在何处使用该索引来"标量化"变量。
IDL> a = 5
IDL> b = [a]
IDL> help, a, b, a[0], b[0]
A INT = 5
B INT = Array[1]
列表(List)和哈希(Hash)的困扰
另一个常见的标量与单元素数组差异问题出现在处理List和Hash对象时。当使用方括号索引运算符与标量值时,它会返回对应索引的元素;但如果使用数组,则会返回包含相应元素副本的新List或Hash:
IDL> l = List(1,2,3,4)
IDL> help, l[2]
IDL> help, l[[2]]
IDL> print, l[[2]]
3
这会产生很大差异,如果你使用WHERE子句或List/Hash的Where()方法,很容易出现这种情况,因为它们总是返回数组(即使是单元素情况)。这种行为在List和Hash帮助页面的底部有文档说明,介绍了如何设置或获取单个元素与多个元素。
识别标量
既然我们已经讨论了为什么了解变量是标量还是数组很重要,接下来问题就是如何可靠地进行判断。经典答案是测试N_ELEMENTS()的返回值或调用ISA(/SCALAR),但有时这些方法会产生错误结果。随着IDL 8.0引入IDL_Object,类可以继承和重写_overloadSize()方法,List和Hash对象正是这样做的。当你对List或Hash调用N_ELEMENTS()甚至SIZE(/DIMENSIONS)时,返回的是对象中的元素数量,而不是变量中的对象数量。
IDL> l = List(1,2,3,4)
IDL> N_Elements(l)
4
IDL> Size(l, /DIMENSIONS)
4
IDL> Size(l, /N_DIMENSIONS)
1
IDL> ISA(l, /SCALAR)
0
对于空的List或Hash,这更加令人困惑:
IDL> l = List()
IDL> N_Elements(l)
0
IDL> Size(l, /DIMENSIONS)
0
IDL> Size(l, /N_DIMENSIONS)
0
IDL> ISA(l, /SCALAR)
1
正如我在关于IDL_Object注意事项的博客中展示的,你可以通过限定基类范围来调用函数以获得预期结果,但这看起来有些笨拙:
IDL> l.IDL_Object::_overloadSize()
1
我不得不反复处理这个问题,我找到的最佳解决方案依赖于这样一个事实:Obj_Valid()函数将返回与输入参数维度相同的布尔值。因此,如果我传入标量对象,则返回标量布尔值;传入单元素对象数组则返回单元素布尔数组;传入N维对象数组则返回N维布尔数组。然后我可以将Obj_Valid()的返回值传递给ISA(/SCALAR)。借助三元运算符,这可以编写成单行函数:
function isScalar, value
compile_opt idl2
return, ISA((Size(value, /TYPE) eq 11) $
? Obj_Valid(value) : value, $
/SCALAR)
end
我已经用标量、单元素和多元素数组(包含数字、字符串、指针(空和有效)、List和Hash对象以及普通对象(空和有效))测试了这个函数。唯一可能从该函数得到意外结果的情况是处理"标量结构体"变量,这是IDL不支持的构造。即使你创建只有一个标签的单个匿名结构体,它也会被视为单元素数组:
IDL> s = { foo : 1 }
IDL> N_Elements(s)
1
IDL> Size(s, /DIMENSIONS)
1
IDL> Size(s, /N_DIMENSIONS)
1
IDL> ISA(s, /SCALAR)
0
因此,如果你需要区分单个结构体与多个结构体数组,那么测试N_ELEMENTS()的返回值是正确方法,并且可以添加到isScalar()函数中。
注意:我也可以使用Obj_ISA(),但计时测试显示Obj_Valid()速度更快。