Tuesday, November 23, 2010

In search of a method I can't name...

Every couple weeks/months, I want a collection transform method that's liked groupedBy:, but isn't quite the same. And it's kinda like collect:. But again, not quite the same.

The idea is to return a "map" of the original series from original values to some derived value. For example, maybe we have a series of Strings, and I want to create a map from those Strings to their md5sum values.

I can code this as
Dictionary withAll: (setOfStrings collect: [:each | each -> (Security.MD5 new hash: 'Hello World')])

but I really would rather capture this as a common method. What would you call it?

16 comments:

  1. setOfStrings asMapWithTransformedValues: [:each | ....]

    ?

    ReplyDelete
  2. setOfStrings asDictionaryValuedBy: [:each | Security.MD5 new hash: 'Hello World']

    setOfStrings associationsCollect: [:each | each -> (Security.MD5 new hash: 'Hello World')]

    ReplyDelete
  3. should be similiar to collect:/reject:/select:
    e.g.

    setOfStrings associate: [: each | Security.MD5 new
    hash: each]

    setOfStrings map: [: each | Security.MD5 new hash: each]

    ReplyDelete
  4. I think the method name needs to reveal the dictionary nature of the answer, and the fact that this is a conversion method from one kind of collection to another. By analogy with asSortedCollection:, we maybe don't need to specify what the block does.

    setOfStrings asDictionary: [:key | Security.MD5 new hash: key]

    ReplyDelete
  5. I implemented this in Ruby once upon a time, and at the time called it "catalog". FWIW.

    It'd be fun if you could make it rhyme w/ select/collect/etc out of respect for Dan Ingalls and Arlo Guthrie. Candidates might include

    #connect: (establishing connection between original and transformed object)
    #transect: (representing a linear "slice" through the objects)
    #inflect: (metaphor from language, where words are transformed according to their role)
    #confect: (cookin' up something tasty)

    ReplyDelete
  6. I agree with camperov, associate: is a good name, if you want to key by the elements.
    Reads to me like

    aCollection associate each with *some value*

    catalog: in my mind goes the other way around (The elements are the values), i.e my internal reading would be

    aCollection catalog each by *some value*

    ReplyDelete
  7. I like Steven's asDictionary: best.

    Thought about suggesting cache: but that's not obvious enough (though it describes a likely common use case).

    ReplyDelete
  8. How about
    setOfStrings mapTo: [:each | Security.MD5 new hash: 'Hello World'].
    or
    setOfStrings mapEachTo: [:each | Security.MD5 new hash: 'Hello World'].

    OTOH, I like map: and associate:, which read well and fit nicely with select: et al.

    I don't like asDictionary:. This doesn't fit my thinking about conversion (I agree with Kent Beck on this one).

    ReplyDelete
  9. #project: and #project:into: are implemented for this feature in Slate.

    ReplyDelete
  10. Another tactic is to go APL on it. I don't know the class library for your Smalltalk dialect, but many of them already have a general-purpose zipWith: method. This method takes two sequences and produces a sequence of pairs.

    Using such a method, you can write your example like this, without needing a new method:

    Dictionary withAll:
    (setOfStrings zipWith:
    (setOfStrings collect: [ :each |
    Security.MD new hash: 'Hello World' ]))

    ReplyDelete
  11. Lex, I don't see what that buys me over the original snippet. Dictionary withAll: takes a collection of associations. But there's also methods like withKeysAndValues: for creating dictioanries which takes pairs. But that's awfully roundabout, to me. Essentially, in this context, the -> method is an element based "zip" which zips two things together.

    ReplyDelete
  12. Implementation(body) of project: for reference, to clarify that it doesn't create Associations if the resulting dictionary doesn't use them:

    keys@(Collection traits) project: valueBlock into: map@(Mapping traits)
    [
      keys do: [| :key | map at: key put: (valueBlock applyWith: key)].
      map
    ].

    ReplyDelete
  13. arose := thyStrings perplect:
    [:key|
    MD5 hash: key
    ].



    "
    An' I said: I can understand you takin' my <img>, so's I won't have any cached.exe
    to 'pend in my cell.jpg there, but what'dya want my <dl> for?

    An' the serif said:

    We don't want no hangin'ins.

    'N I said: Did ja think I'd wanna go hangin'ins myself -- for letterin'?

    Well, the serif said he was makin' sure,
    an' friends, he was, 'cuz he took out my <title> so's I couldn' <html> myself over the <head> and drown, he took my <tt><pre>s,
    so I couldn't bend the columns, &lt>br>eak the <cr>roll the comment out the <br>eak the window and have an 16r1B.
    "

    Collection>>perplect: a1block

    ^self
    inject: Dictionary new
    into: [:map :each|
    map += (each perplect: a1block)
    ]

    Object>>perplect: a1block

    ^self isCollection
    ifTrue: [self subclassResponsibility]
    ifFalse: [self -> (a1block value: self)]

    >>+= another

    ^self addAnsweringReceiver: another

    ReplyDelete
  14. Cross-posted from the VisualWorks forum on forum.world.st

    It seems the behavior Travis wants is "From a Collection, create a mapping where the key is the original object and the value is transformed from the key". Most replies seem to assume that the original input is a Set or able to become one without loss, though if not you could make it work producing an Array of Associations. I like the previously mentioned "project:" method name. Thus:

    'abcabc' project: [ :e | e isVowel ]

    which would produce an Array (preserving order and duplicates) of Associations.

    You could write one implementation on Collection with the above behavior, and a refinement on Set to produce a Dictionary:

    'abcabc' asSet project: [ :e | e isVowel ]

    What do you think?

    ReplyDelete
  15. Just came up with a one method implementation:

    connect: aOneArgBlock
    ^self
    inject: Dictionary new
    into: [ :accumulator :e | accumulator at: e put: (oneArgBlock value: e); yourself]



    ( I posted this on the mailing list vwnc@cs.uiuc.edu, as seen at http://forum.world.st/Looking-for-a-good-method-name-td3056676.html#a3066226. Just thought I would add it to the comments here in case someone did not see the mailing list )

    ReplyDelete