我想向数组类添加一个“平均”方法。但是如果输入数组包含字符/字符串/对象,则平均值没有任何意义。所以我需要检查数组是否只包含整数/浮点数。
Smalltalk 说数据类型检查 [检查变量是否属于特定数据类型,如 int 字符串数组等......或不] 是一种糟糕的编程方式。
那么实现这一点的最佳方法是什么?
我想向数组类添加一个“平均”方法。但是如果输入数组包含字符/字符串/对象,则平均值没有任何意义。所以我需要检查数组是否只包含整数/浮点数。
Smalltalk 说数据类型检查 [检查变量是否属于特定数据类型,如 int 字符串数组等......或不] 是一种糟糕的编程方式。
那么实现这一点的最佳方法是什么?
规范有些不完整。您需要指定将集合与非数字输入一起使用时应显示的行为。有大量可能理想的行为。Smalltalk 支持其中的大多数,除了静态类型解决方案(当您将非数字事物添加到数字集合时抛出编译时错误)。
在pharo至少有
Collection >> average
^ self sum / self size
在Collections-arithmetic
类别中。当您使用静态类型语言时,当您将非数字值添加到集合中时,您会受到该语言的影响。在动态类型语言中,当您尝试计算您尝试发送的不适当元素的平均值或发送到不理解它的对象时,也会发生同样+
的-
情况/
。
不要想你把数据放在哪里,想想你在用它做什么。
如果您想做不同的事情,检查类型是合理的,例如:
(obj isKindOf: Number) ifTrue: [:num| num doItForNum].
(obj isKindOf: Array ) ifTrue: [:arr| arr doItForArr].
但在这种情况下,您希望将类型检查的逻辑移至对象端。
所以最终它会是:
obj doIt.
然后你还会有类似的东西:
Number >> doIt
"do something for number"
Array >> doIt
"do something for array"
(这个例子是printOn:
方法)
我原以为 Smalltalk 的答案是为数字实现它,然后注意不要发送宠物集合#sum 或 #average。当然,如果以后有一个有用的实现让宠物将自己添加到另一个宠物,甚至是#average 的答案,那么这将取决于 Pet 或 PetCollection 的实现者。
当我在我的图像中实现微不足道的代数时,我做了类似的事情。它让我可以在简单的数学方程式中混合数字、字符串和符号。2 * #x 结果为 2x。x + y 导致 x + y。这是一种通过想象钱包中发生的代数来试验货币的有趣方式。我将 (5 x #USD) + (15 * #CAN) 存入我的围墙,获得 5USD + 15CAN。给定一个可以在货币之间转换的对象,我可以回答以 CAN 或 USD 表示的总数。
我们实际上将它用于供应链软件,以解决简单的度量衡问题。如果采购订单说它将支付 XUSD/1 吨某物,但供应商发送了同样的东西的英尺磅,那么为了验证装运价值,我们需要在吨和英尺磅之间进行转换。让库简化方程,我们能够在不干扰输入数据的情况下产生结果,或者不必提出表示吨和英尺磅或其他任何东西的新对象。
我对图书馆抱有很高的抱负(这很简单),但可惜的是,2008 年抹去了整件事……
这不是您在 Smalltalk 中所做的那种事情。您可以从上述评论中获取建议并“让它发挥作用”,但这个想法是错误的(从 Smalltalk 的角度来看)。
“Smalltalk”要做的事情是创建一个可以为您执行所有此类操作的类——计算平均值、平均值、模式等。然后该类可以对数字输入进行适当的检查,您可以编写如何它会响应错误的输入。该类将使用普通的旧数组或列表或其他东西。类的名称可以清楚地说明它的用途。然后该类可以成为您的部署的一部分,并且可以根据需要导出/导入到不同的图像。
创建一个新的集合类;可能是 的子类Array
,也可能是 的子类OrderedCollection
,具体取决于您想要的与集合相关的行为。
在新类at:put:
和/或add:
方法中测试新项目,#isNumber
如果失败则返回错误。
现在你有了一个可以保证只有数字对象和 nil 的集合。在您无需处理尝试将海狮添加到金橘的情况下实现您所需的功能。不过要注意细节;例如,如果您创建WonderNumericArray
大小为 10 的 a 并在其中插入两个值,当您对数组求平均时,您想要将这两项相加并除以 2 还是除以 10?
“我想在数组类中添加一个方法“平均”。但是如果输入数组包含字符/字符串/对象,平均没有任何意义。所以我需要检查数组是否只包含整数/浮点数。
有许多方法可以在过滤掉非数字对象的同时完成数组中数字总和的平均。
首先,我将通过将其提升到 Collection 类使其成为更通用的方法,以便它可以找到更多的重用案例。其次,我希望它对于数字是通用的,而不仅仅是浮点数和整数,哦,它适用于那些,也适用于分数。如果集合数组列表中有数字,则结果将是浮点平均值。
(1) 将对象添加到数组时,测试它们以确保它们是数字,并且只有当它们是数字时才添加它们。这是我的首选解决方案。
(2) 使用 Collection #select: 实例方法过滤掉非数字,只将数字留在单独的集合中。这以新集合为代价让生活变得轻松(这很好,除非您担心大型列表和内存问题)。这是在对集合执行某些操作之前过滤集合的高效、简单和常用的解决方案。打开 Smalltalk 并找到 #select: 的所有发件人以查看其他示例。
| list numberList sum average |
list := { 100. 50. 'string'. Object new. 1. 90. 2/3. 88. -74. 'yup' }.
numberList := list select: [ :each | each isNumber ].
sum := numberList sum.
average := sum / (numberList size) asFloat.
使用“print it”执行上述代码将为示例数组列表生成以下内容:
36.523809523809526
但是,如果数字列表的大小为零,换句话说是空的,那么您将在上面的代码中得到除以零的异常。此外,此版本不在 Collection 类中作为实例方法。
(3) 为 Collection 类编写一个实例方法来为你做平均工作。此解决方案不使用 select,因为它会创建中间集合,如果您的列表非常大,那么需要收集很多额外的垃圾。这个版本只是循环现有的集合来计算结果。简单,有效。它还解决了没有数字可以统计的情况,在这种情况下,它返回 nil 对象而不是数字平均值。
收集方法:#computeAverage
"Compute the average of all the numbers in the collection. If no numbers are present return the nil object to indicate so, otherwise return the average as a floating point number."
| sum count average |
sum := 0.
count := 0.
self do: [ :each |
each isNumber ifTrue: [
count := count +1.
sum := sum + each.
]
].
count > 0 ifTrue: [
^average := sum / count asFloat
] ifFalse: [
^nil
]
请注意,变量“平均值”仅用于显示数学,实际上并不需要。
然后,您可以按如下方式使用上述方法:
| list averageOrNil |
list := { 100. 50. 'string'. Object new. 1. 90. 2/3. 88. -74. 'yup' }.
averageOrNil := list computeAverage.
averageOrNil ifNotNil: [ "got the average" ] ifNil: [ "there were no numbers in the list"
或者你可以像这样使用它:
{
100. 50. 'string'. Object new. 1. 90. 2/3. 88. -74. 'yup'
} computeAverage
ifNotNil: [:average |
Transcript show: 'Average of list is: ', average printString
]
ifNil: [Transcript show: 'No numbers to average' ].
当然,如果您确定列表中有数字,那么您将永远不会遇到 nil 对象的例外情况,并且您不需要使用 if 消息进行相应的分支。
运行时的数据类型/类检查
至于您提出的问题,“Smalltalk 说数据类型检查 [检查变量是否属于特定数据类型,如 int 字符串数组等......或不是] 是一种不好的编程方式”,有一些方法可以做的事情比其他。
例如,虽然可以使用 #isKindOf: Number 来询问每个元素是否不是在运行时确定“类型”或“类”的最佳方法,因为它通过预定类型或类将其锁定为 #isKindOf 的参数: 信息。
最好使用诸如#isNumber 之类的“is”“类”方法,这样任何作为数字的类都返回true,而所有其他非数字对象返回false。
在确定事物的类型或类别时,Smalltalk 中的一个主要风格点是,最好将消息发送与各种类型/类理解但行为不同的消息一起使用,而不是使用显式类型/类检查。可能的。
#isNumber 方法是 Pharo Smalltalk 中 Number 类的一个实例方法,它返回 true,而在 Object 实例版本上它返回 false。
在这种情况下使用多态消息发送可以实现更大的灵活性并消除通常过于程序化或过于具体的代码。当然最好避免这样做,但现实会出现在各种应用程序中,你必须尽你所能。