Working with arrays: sort, group by, reflection..

181 views
Skip to first unread message

Michal Hantl

unread,
Jan 21, 2012, 1:52:10 PM1/21/12
to google-ap...@googlegroups.com
Hello,
 I am trying to implement something like this:

array := AnyType[]{...}

groups = group(array, "SomeField")

// groups should be map of arrays of AnyType grouped by SomeField values

I threw a few hours at this and it seems I it seems I'll need to spend a few days to fully understand how to solve this.

So far I've come up with:


import (
  "test"
  "fw"
  "reflect"
  "fmt"
)

func group_by (array []interface{}, key string) {
  grouped := map[interface{}][]interface{}{}
  for _, item := range array {
    v := reflect.ValueOf(&item).Elem().FieldByName(key)
    grouped[v] = append(grouped[v], item)
  }
  fmt.Println(grouped)
}


func tests(c *fw.MyContext) {

  It := test.It
  //Assert := test.Assert
  Describe := test.Describe
  
  type Venue struct {
    City string
  }

  p1 := Venue{"Ostrava"}
  p2 := Venue{"Oslo"}


  //venues := []Venue{p1, p2} // I wish I could just use []Venue, but i didn't know how to do it..
  venues := []interface{}{p1, p2}

  group_by(venues, "City")

  runner := test.NewRunner()
  runner.AddSuite(suite)

  test.ReportHtml(runner, c)
}



This compiles and I get an error:

2012/01/21 18:47:27 http: panic serving @: reflect: call of reflect.Value.FieldByName on interface Value
/tmp/appengine/google_appengine/goroot/src/pkg/http/server.go:588 (0x8083af1)
/tmp/appengine/google_appengine/goroot/src/pkg/runtime/proc.c:1235 (0x80556da)
/tmp/appengine/google_appengine/goroot/src/pkg/reflect/value.go:354 (0x80d77af)
/tmp/appengine/google_appengine/goroot/src/pkg/reflect/value.go:746 (0x80d9ac7)
app/utils.go:14 (0x805ea24)
group_by: v := reflect.ValueOf(&item).Elem().FieldByName(key)
app/utils.go:40 (0x8061a2c)
_func_002: group_by(venues, "City")
test/spec.go:62 (0x80d2ab5)
(*Spec).Run: spec.Body()
test/testsuite.go:25 (0x80d2fd6)
(*Suite).Run: suiteResult.AddSpecResult(spec.Run())
test/runner.go:21 (0x80d2c50)
(*Runner).Run: result.AddSuiteResult(suite.Run())
test/suite.go:19 (0x80d2d39)
ReportHtml: result := runner.Run()
app/utils.go:47 (0x805ed29)
tests: test.ReportHtml(runner, c)
fw/context.go:321 (0x80b79fd)
Route: page(context)
fw/context.go:258 (0x80b8327)
_func_007: Route(context, router, e404)
app/router.go:28 (0x805f9aa)
route: }, e404)
app/router.go:16 (0x805f852)
handler: route(fw.NewMyContext(w, r))


If you could help with some of the thing I would very much appreciate it.

Also if there is any library I could use of this, it would let me go on programming until I grasp the concepts.

Thanks!






Michal Hantl

unread,
Jan 21, 2012, 3:50:58 PM1/21/12
to google-ap...@googlegroups.com
So I've dug out some code from moustache and hacked it together.


package hello

import (
  "fw"
  "reflect"
  "fmt"
)


func lookup(contextChain []interface{}, name string) reflect.Value {
    defer func() {
        if r := recover(); r != nil {
            fmt.Printf("Panic while looking up %q: %s\n", name, r)
        }
    }()

  Outer:
    for i := len(contextChain) - 1; i >= 0; i-- {
        v := contextChain[i].(reflect.Value)
        for v.IsValid() {
            fmt.Println("valid")
            typ := v.Type()
            if n := v.Type().NumMethod(); n > 0 {
                for i := 0; i < n; i++ {
                    m := typ.Method(i)
                    mtyp := m.Type
                    if m.Name == name && mtyp.NumIn() == 1 {
                        return v.Method(i).Call(nil)[0]
                    }
                }
            }
            switch av := v; av.Kind() {
            case reflect.Ptr:
                fmt.Println("pointer")
                v = av.Elem()
            case reflect.Interface:
                fmt.Println("interface")
                v = av.Elem()
            case reflect.Struct:
                fmt.Println("struct")
                ret := av.FieldByName(name)
                if ret.IsValid() {
                    return ret
                } else {
                    continue Outer
                }
            case reflect.Map:
                fmt.Println("map")
                ret := av.MapIndex(reflect.ValueOf(name))
                if ret.IsValid() {
                    return ret
                } else {
                    continue Outer
                }
            default:
                continue Outer
            }
        }
    }
    return reflect.Value{}
}



func group_by (array []interface{}, key string) map[interface{}][]interface{} {
  grouped := map[interface{}][]interface{}{}
  for _, item := range array {
    v := lookup([]interface{}{reflect.ValueOf(item)}, key).Interface()
    grouped[v] = append(grouped[v], item)
  }
  return grouped
}


func tests(c *fw.MyContext) {
 
  type Venue struct {
    City string
  }


  p1 := Venue{City: "Ostrava"}
  p2 := Venue{"Oslo"}
  p3 := Venue{"Prague"}
  p4 := Venue{"Prague"}
  p5 := Venue{"Prague"}

  venues := []interface{}{p1, p2, p3, p4, p5}

  groups := group_by(venues, "City")

  fmt.Println(groups) // map[Oslo:[{Oslo}] Prague:[{Prague} {Prague} {Prague}] Ostrava:[{Ostrava}]]

}









Michal Hantl

unread,
Jan 22, 2012, 8:15:14 AM1/22/12
to google-ap...@googlegroups.com
Next iteration, this time my grouping function can taky any slice and can be further improved to take more parameter types.

I come from Ruby and JS and I must say it is very nice to be able to do this in Go!

func group (items interface{}, key string) map[interface{}][]interface{} {
  grouped := map[interface{}][]interface{}{}

  value := reflect.ValueOf(items)

  fmt.Println(value)
  fmt.Println(value.Kind())

  if value.Kind() == reflect.Slice {
    fmt.Println("its a slice!")
    for i := 0; i < value.Len(); i++ {
      item := value.Index(i)
      fmt.Println("item: ", item)
      v := lookup([]interface{}{item}, key).Interface()
      grouped[v] = append(grouped[v], item.Interface())
    }
  }
  return grouped
}

Rodrigo Moraes

unread,
Jan 22, 2012, 3:20:34 PM1/22/12
to google-appengine-go
What are you trying to do? Try to not abuse reflection.

-- rodrigo
Reply all
Reply to author
Forward
0 new messages