Hi Joseph!

Here is my take on your problem:

categoriesByPayees
 := Dictionary new.
FileStream readOnlyFileNamed: 'payees-by-categories.txt' do: [ :file |
    
" With #readOnlyFileNamed:do: you don't have to worry about closing the file,
    it ensures that #close is sent to the file, even if you leave the block through an Exception. "

    
[ file atEnd ] whileFalse: [
        
| parts |
        
parts := (file nextLine findTokens: $| escapedBy: Character tab)
            
collect: #withBlanksCondensed.
        
" We collect the parts #withBlanksCondensed, so we don't have to call it repeatedly in the loop.
        You can use unary selectors in place of blocks with one argument. You may look at the implementation
        of Collection >> #collect: and Symbol >> #value: to find out why. I'm not sure what you really want is
        #findTokens:escapedBy:, it cuts the string along | characters if they are not preceded by a tab."
        
parts size > 1 ifTrue: [ 
            
" We skip blank lines or categories without at least one payee. "
            
| category |
            
category := parts first.
            
parts allButFirstDo: [ :payee |
                
(categoriesByPayees
                    
at: payee
                    
ifAbsentPut: [ OrderedCollection new ]) add: category
                    
" Your implementation only remembers the last category encountered for a payee.
                    We can collect all the categories. "
 ] ] ] ]

Cheers, Balázs