#1355 [Newbie] How can I write this function in Fantom?

perp Tue 7 Dec 2010

I'm new to Fantom, and I've only played around with it for a day or so. I'm having a bit of trouble figuring out how to write a certain function.

The idea is to have a function consuming a list of A, and returning a map from B => lists of A, where the B keys are given by calling a supplied function, F, with A as an argument. So it's sort of like "grouping" the elements in a list depending on some property on each object.

Perhaps code samples are clearer... :-)

The equivalent Ruby code could look something like:

def group(list, func)
  m = {}
  list.each do |e| 
	k = func.call(e)
	m[k] ||= [] 
	m[k] << e
  end
  m
end

p group([1,2,3,4,5,6,7,8,9], lambda {|a| a % 2 == 0 ? :even : :odd})

Output:

{:odd=>[1, 3, 5, 7, 9], :even=>[2, 4, 6, 8]}

(There are probably prettier ways to do that; I'm new to Ruby too.)

And in Java you could write it as :

import java.util.*;

public class Group {

    static interface Unary<T, U> {
         public U call(T arg);
    }

    static <K, V> Map<K, List<V>> group(Collection<V> coll, Unary<V, K> fun) {
        Map<K, List<V>> map = new HashMap<K, List<V>>();
        for (V v : coll) {
            K key = fun.call(v);
            List<V> list = map.get(key);
            if (list == null) {
                list = new ArrayList<V>();
                map.put(key, list);
            }
            list.add(v);
        }
        return map;
    }

    public static void main(String[] args) {
        System.out.println(group(
            Arrays.asList(1,2,3,4,5,6,7,8,9), 
            new Unary<Integer, String>() {
                public String call(Integer arg) {
                    return arg % 2 == 0 ? "even" : "odd";
                }
            }));
    }
}

Output:

{even=[2, 4, 6, 8], odd=[1, 3, 5, 7, 9]}

How would I write an equivalent general group() function in Fantom?

DanielFath Tue 7 Dec 2010

First off Fantom doesn't support generic programming so I'm gonna reduce the domain to Int[] and map of Str:Int[]. Something like this:

class Group
{
  static Str:Int[] group(Int[] list, |Int->Str| func)
  {
    retVal := Str:Int[][:]

    list.each |Int i| 
    { 

      s := retVal[func.call(i)]
      if(s != null && !s.isEmpty)
      {
        s.add(i)
      }
      else
      {
        retVal.getOrAdd(func(i)) {  [i] }
      }

    }

    return retVal
  }

  static Void main(Str[] args)
  {
    echo(group([1,2,3,4,5,6,7,8,9], |Int i->Str|{return i%2 == 0 ? "even" : "odd" } )) 
  }
}

Note: func.call(i) and func(i) are the same. func(i) is just a shortcut for call method.

ivan Tue 7 Dec 2010

Writing in DanielFath's domain, I'd rather write it like this:

list := (0..<20).toList
func := |Int i->Str| { (i % 5).toStr }
result := list.reduce([:]) |Str:Int[] r, Int val->Str:Int[]| { 
            r { getOrAdd(func(val)) |->Int[]| { Int[,] }.add(val) } 
          }
echo(result)

prints:

[3:[3, 8, 13, 18], 2:[2, 7, 12, 17], 1:[1, 6, 11, 16], 0:[0, 5, 10, 15], 4:[4, 9, 14, 19]]

However, the complete translation of Ruby code to Fantom could look like this:

Obj:Obj?[] group(Obj?[] list, |Obj?->Obj| func)
{
  m := Obj:Obj?[][:] //m := [:]
  list.each |e|
  {
    k := func(e) 
    m[k] = m[k] ?: [,] 
    m[k].add(e) //m[k]->add(e)
  }
  return m
}

Replacing lines with comments would correspond to solution with dynamic invoke

DanielFath Tue 7 Dec 2010

I'm a Java programmer, can't you tell :P

ahhatem Tue 7 Dec 2010

class Main
{
  static Void main()
  {        
    echo(group([1,2,3,4,5,6,7,8,9], |Int e->Str|{ return e % 2==0?"even" : "Odd"}))
  }


  static Obj:Obj[] group (Obj[] list, |Obj e->Obj| func)
  {
    Obj:Obj[] map := [:]

    list.each |Obj e|{
      k := func(e)
      map[k] = map[k] ?: [,] 
     // if (!map.containsKey(k)) map[k] = Obj[,]
      map[k].add(e)
    }

    return map
  }

}

perp Tue 7 Dec 2010

Great answers, everyone, thanks! I'm starting to like this language. :-)

vkuzkokov Tue 7 Dec 2010

Actually m.getOrAdd(k) { Int[,] } is closer than m[k] = m[k] ?: [,] because there's no unnecessary set in the former. The flipside is an extra closure.

BTW, Ivan's example optimizes away second get. Ruby equivalent would be (AFAIK it's valid Ruby) (m[k] ||= []) << e.

Login or Signup to reply.