简体   繁体   English

如何在仅用于数字输入的Smalltalk集合中编写其他方法?

[英]How to write additional methods in Smalltalk Collections which work only for Numeric Inputs?

I want to add a method "average" to array class. 我想为数组类添加一个“average”方法。 But average doesn't make any sense if input array contains characters/strings/objects. 但是,如果输入数组包含字符/字符串/对象,则平均值没有任何意义。 So I need to check if array contains only integers/floats. 所以我需要检查数组是否只包含整数/浮点数。

Smalltalk says datatype check [checking if variable belongs to a particular datatype like int string array etc... or not] is a bad way of programming. Smalltalk说数据类型检查[检查变量是否属于特定数据类型,如int string array等...或者不是]是一种糟糕的编程方式。

So what is best way to implement this? 那么实现这个的最佳方法是什么?

The specification is somewhat incomplete. 规范有点不完整。 You'd need to specify what behavior the collection should show when you use it with non-numeric input. 您需要指定集合在使用非数字输入时应显示的行为。 There are a huge number of possibly desirable behaviors. 存在大量可能期望的行为。 Smalltalk supports most of them, except for the static typing solution (throw a compile-time error when you add a non-numeric thing to a numeric collection). Smalltalk支持其中的大多数,除了静态类型解决方案(在向数字集合添加非数字事物时抛出编译时错误)。

  • If you want to catch non-numeric objects as late as possible, you might just do nothing - objects without arithmetic methods will signal their own exceptions when you try arithmetic on them. 如果你想尽可能晚地捕获非数字对象,你可能什么都不做 - 没有算术方法的对象会在你对它们进行算术运算时发出自己的异常信号。
  • If you want to catch non-numeric elements early, implement a collection class which ensures that only numeric objects can be added (probably by signaling an exception when you add a non-numeric object is added). 如果要提前捕获非数字元素,请实现一个集合类,以确保只能添加​​数字对象(可能通过添加非数字对象时发出异常信号)。
  • You might also want to implement "forgiving" methods for sum or average that treat non-numeric objects as either zero-valued or non-existing (does not make a difference for #sum, but for #average you would only count the numeric objects). 您可能还希望为求和或平均值实现“宽容”方法,将非数字对象视为零值或不存在(对于#sum没有区别,但对于#average,您只计算数字对象)。

In at least there is 至少在中有

Collection >> average
^ self sum / self size

In Collections-arithmetic category. Collections-arithmetic类别中。 When you work with with a statically typed languages you are being hit by the language when you add non-number values to the collection. 使用静态类型语言时,在向集合中添加非数字值时,您将被该语言命中。 In dynamically typed languages you the same happens when you try to calculate average of inappropriate elements ei you try to send + , - or / to an object that does not understand it. 在动态类型语言中,当您尝试计算不合适元素的平均值时,您会尝试发送+-/到不理解它的对象。

Don't think where you put data, think what are you doing with it. 不要在想你放数据的地方,想一想你用它做什么。

It's reasonable to check type if you want to do different things, eg: 如果你想做不同的事情,检查类型是合理的,例如:

(obj isKindOf: Number) ifTrue: [:num| num doItForNum].
(obj isKindOf: Array ) ifTrue: [:arr| arr doItForArr].

But in this case you want to move the logic of type checking into the object-side. 但在这种情况下,您希望将类型检查的逻辑移动到对象端。

So in the end it will be just: 所以最后它将是:

obj doIt.

and then you'll have also something like: 然后你还会有类似的东西:

Number >> doIt
    "do something for number"

Array >> doIt
    "do something for array"

(brite example of this is printOn: method) (brite的例子是printOn:方法)

I would have thought the Smalltalk answer would be to implement it for numbers, then be mindful not to send a collection of pets #sum or #average. 我原以为Smalltalk的答案是为数字实现它,然后注意不要发送宠物的集合#sum或#average。 Of course, if there later becomes a useful implementation for a pet to add itself to another pet or even an answer to #average, then that would be up to the implementer of Pet or PetCollection. 当然,如果后来成为宠物将其自身添加到另一只宠物或甚至是#average的答案的有用实现,那么这将取决于Pet或PetCollection的实现者。

I did a similar thing when I implemented trivial algebra into my image. 当我将琐碎的代数实现到我的图像中时,我做了类似的事情。 It allowed me to mix numbers, strings, and symbols in simple math equations. 它允许我在简单的数学方程中混合数字,字符串和符号。 2 * #x result in 2x. 2 * #x导致2x。 x + y resulted in x + y. x + y导致x + y。 It's a fun way to experiment with currencies by imagining algebra happening in your wallet. 通过想象钱包中发生的代数来试验货币是一种有趣的方式。 Into my walled I deposit (5 x #USD) + (15 * #CAN) for 5USD + 15CAN. 进入我的围墙我存款(5 x #USD)+(15 * #CAN)5USD + 15CAN。 Given an object that converts between currencies I can then answer what the total is in either CAN or USD. 给定一个在货币之间转换的对象,然后我可以回答CAN或USD中的总数。

We actually used it for supply-chain software for solving simple weights and measures. 我们实际上将它用于供应链软件以解决简单的权重和度量。 If a purchase order says it will pay XUSD/1TON of something, but the supplier sends foot-lbs of that same thing, then to verify the shipment value we need a conversion between ton and foot-lbs. 如果采购订单说它将向XUSD / 1TON支付某些东西,但供应商发送同样数量的英尺,那么为了验证货运价值,我们需要在吨和英尺 - 磅之间进行转换。 Letting the library reduce the equation we're able to produce a result without molesting the input data, or without having to come up with new objects representing tons and foot-pounds or anything else. 让库减少等式,我们能够产生结果而不会骚扰输入数据,或者不必提出代表吨和英尺磅或其他任何东西的新对象。

I had high ambitions for the library (it was pretty simple) but alas, 2008 erased the whole thing... 我对图书馆抱有很大的抱负(这很简单)但是,唉,2008年整个事情已经消失了......

"I want to add a method "average" to array class. But average doesn't make any sense if input array contains characters/strings/objects. So I need to check if array contains only integers/floats." “我想在数组类中添加一个”average“方法。但是如果输入数组包含字符/字符串/对象,则平均值没有任何意义。所以我需要检查数组是否只包含整数/浮点数。”

There are many ways to accomplish the averaging of the summation of numbers in an Array while filtering out non-numeric objects. 在过滤掉非数字对象时,有许多方法可以实现数组总和的平均值。

First I'd make it a more generic method by lifting it up to the Collection class so it can find more cases of reuse. 首先,我将它提升到Collection类,使其成为一种更通用的方法,以便它可以找到更多的重用案例。 Second I'd have it be generic for numbers rather than just floats and integers, oh it'll work for those but also for fractions. 其次我认为它是数字的通用而不仅仅是浮点数和整数,哦它对那些人也适用于分数。 The result will be a float average if there are numbers in the collection array list. 如果集合数组列表中有数字,则结果将是浮点平均值。

(1) When adding objects to the array test them to ensure they are numbers and only add them if they are numbers. (1)当向对象添加对象时,测试它们以确保它们是数字,并且只有在它们是数字时才添加它们。 This is my preferred solution. 这是我的首选解决方案。

(2) Use the Collection #select: instance method to filter out the non-numbers leaving only the numbers in a separate collection. (2)使用Collection #select:instance方法过滤掉非数字,只留下单独集合中的数字。 This makes life easy at the cost of a new collection (which is fine unless you're concerned with large lists and memory issues). 这样可以以新集合为代价轻松生活(除非您关注大型列表和内存问题,否则这样很好)。 This is highly effective, easy to do and a common solution for filtering collections before performing some operation on them. 这是非常有效,易于操作的,也是在对集合执行某些操作之前过滤集合的常用解决方案。 Open up a Smalltalk and find all the senders of #select: to see other examples. 打开一个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.

Executing the above code with "print it" will produce the following for the example array list: 使用“print it”执行上面的代码将为示例数组列表生成以下代码:

 36.523809523809526

However if the list of numbers is of size zero, empty in other words then you'll get a divide by zero exception with the above code. 但是,如果数字列表的大小为零,则换句话说为空,那么您将使用上述代码获得除以零的异常。 Also this version isn't on the Collection class as an instance method. 此版本也不作为实例方法在Collection类上。

(3) Write an instance method for the Collection class to do your work of averaging for you. (3)为Collection类编写一个实例方法,为你做平均工作。 This solution doesn't use the select since that creates intermediate collections and if your list is very large that's a lot of extra garbage to collect. 此解决方案不使用select,因为它创建了中间集合,如果列表非常大,则需要收集大量额外垃圾。 This version merely loops over the existing collection tallying the results. 此版本仅循环覆盖结果的现有集合。 Simple, effective. 简单,有效。 It also addresses the case where there are no numbers to tally in which case it returns the nil object rather than a numeric average. 它还解决了没有数字来计算的情况,在这种情况下它返回nil对象而不是数字平均值。

Collection method: #computeAverage 收集方法:#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
]

Note the variable "average" is just used to show the math, it's not actually needed. 请注意,变量“average”仅用于显示数学,实际上并不需要。

You then use the above method as follows: 然后使用上面的方法如下:

| 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"

Or you can use it like so: 或者您可以像这样使用它:

{ 
    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' ].

Of course if you know for sure that there are numbers in the list then you won't ever get the exceptional case of the nil object and you won't need to use an if message to branch accordingly. 当然,如果您确定列表中有数字,那么您将无法获得nil对象的特殊情况,并且您不需要使用if消息进行相应的分支。


Data Type/Class Checking At Runtime 运行时数据类型/类检查

As for the issue you raise, "Smalltalk says datatype check [checking if variable belongs to a particular datatype like int string array etc... or not] is a bad way of programming", there are ways to do things that are better than others. 至于你提出的问题,“Smalltalk说数据类型检查[检查变量是否属于特定的数据类型,如int string array等...或者不是]是一种糟糕的编程方式”,有办法做更好的事情。其他。

For example, while one can use #isKindOf: Number to ask each element if it's not the best way to determine the "type" or "class" at runtime since it locks it in via predetermined type or class as a parameter to the #isKindOf: message. 例如,虽然可以使用#isKindOf:Number来询问每个元素是否不是在运行时确定“类型”或“类”的最佳方法,因为它通过预定类型或类将其作为参数锁定到#isKindOf : 信息。

It's way better to use an "is" "class" method such as #isNumber so that any class that is a number replies true and all other objects that are not numeric returns false. 使用诸如#isNumber之类的“is”“class”方法会更好,这样任何一个数字的类都会返回true,而所有其他非数字的对象都会返回false。

A main point of style in Smalltalk when it comes to ascertaining the types or classes of things is that it's best to use message sending with a message that the various types/classes comprehend but behave differently rather than using explicit type/class checking if at all possible. Smalltalk在确定事物的类型或类别时的一个主要观点是,最好使用消息发送,消息是各种类型/类理解但行为不同而不是使用显式类型/类检查(如果有的话)可能。

The method #isNumber is an instance method on the Number class in Pharo Smalltalk and it returns true while on the Object instance version it returns false. #isNumber方法是Pharo Smalltalk中Number类的实例方法,它在Object实例版本上返回true,返回false。

Using polymorphic message sends in this away enables more flexibility and eliminates code that is often too procedural or too specific. 使用多态消息发送可以实现更大的灵活性,并消除通常过于程序化或过于具体的代码。 Of course it's best to avoid doing this but reality sets in in various applications and you have to do the best that you can. 当然最好避免这样做,但现实在各种应用程序中都会出现,你必须尽力做到最好。

This is not the kind of thing you do in Smalltalk. 这不是你在Smalltalk中所做的事情。 You could take suggestions from the above comments and "make it work" but the idea is misguided (from a Smalltalk point of view). 您可以从上述评论中获取建议并“使其工作”,但这个想法是错误的(从Smalltalk的角度来看)。

The "Smalltalk" thing to do would be to make a class that could perform all such operations for you --computing the average, mean, mode, etc. The class could then do the proper checking for numerical inputs, and you could write how it would respond to bad input. “Smalltalk”要做的就是创建一个可以为你执行所有这些操作的类 - 计算平均值,平均值,模式等。然后类可以对数字输入进行正确的检查,你可以编写如何它会回应糟糕的输入。 The class would use a plain old array, or list or something. 该类将使用普通的旧数组,或列表或其他东西。 The name of the class would make it clear what it's usage would be for. 该类的名称将清楚它的用途是什么。 The class could then be part of your deployment and could be exported/imported to different images as needed. 然后,该类可以成为部署的一部分,并可根据需要导出/导入到不同的映像。

Make a new collection class; 创建一个新的集合类; perhaps a subclass of Array , or perhaps of OrderedCollection , depending on what collection related behaviour you want. 可能是Array的子类,也可能是OrderedCollection的子类,具体取决于您想要的集合相关行为。

In the new class' at:put: and/or add: methods test the new item for #isNumber and return an error if it fails. 在新类' at:put:和/或add:方法中测试#isNumber的新项,如果失败则返回错误。

Now you have a collection you can guarantee will have just numeric objects and nils. 现在你有一个集合,你可以保证只有数字对象和nils。 Implement your required functions in the knowledge that you won't need to deal with trying to add a Sealion to a Kumquat. 在您不需要处理尝试将Sealion添加到金橘的知识中实现您所需的功能。 Take care with details though; 但要注意细节; for example if you create a WonderNumericArray of size 10 and insert two values into it, when you average the array do you want to sum the two items and divide by two or by ten? 例如,如果您创建一个大小为10的WonderNumericArray并在其中插入两个值,那么当您对数组求平均值时,您想要WonderNumericArray两个项相加并除以2或10吗?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM