Sorting objects in Rego, based on multiple attributes - sorting

I'm trying to write some code in OPA Rego to sort a collection of objects based on the value of multiple attributes. I start with the objects in a dictionary as below and I'd like to sort the objects inside and return their relative ids.
So having the dictionary
dict = {
"1": {"name": "ccc", "foo": "4"},
"2": {"name": "aaa", "foo": "1"},
"3": {"name": "bbb", "foo": "6"},
"4": {"name": "eee", "foo": "5"},
"5": {"name": "eee", "foo": "2"},
"6": {"name": "ddd", "foo": "3"}
}
sorting first by name and then by foo I would expect to return [ 2 3 1 6 5 4]
Notice that for ids 4 and 5 the objects have the same name, so the order should be decided by foo
My first attempt is
_sorted = res{
orderBy = ["name", "foo"]
sorted1 = sort([ x | x := dict[_][orderBy[0]] ])
res = [id | sorted1[_] == dict[id][orderBy[0]] ]
}
but this approach has problems when there are objects with the same name, therefore the following result "_sorted": ["2","3","1","6",**"4","5","4","5"**]
The second attempt was using sets instead, which solves the duplication issue
_sorted = res{
orderBy = ["name", "foo"]
sorted1 = { x | x := dict[_][orderBy[0]] }
res = [id | sorted1[_] == dict[id][orderBy[0]] ]
}
but I don't know how to make it work with sorting on 2 attributes - last attempt
_sorted = res{
orderBy = ["name", "foo"]
sorted1 = { x | x := dict[_][orderBy[0]] }
sorted2 = { x | x := dict[_][orderBy[1]] }
res = [id | sorted1[_] == dict[id][orderBy[0]]; sorted2[_] == dict[sid][orderBy[1]] ]
}
Any suggestions are much appreciated :-) Thanks!

I think the key to sorting declaratively is to calculate the rank of each element, and then build an array accordingly.
I've given your example a go here: playground
rank(x, k, coll) := count(smaller) {
smaller := { y | some y in coll; y[k] <= x[k] }
}
sorted(k, coll) := { r: sorted_("foo", es) |
some r in numbers.range(1, count(coll))
es := { e | some e in coll; r == rank(e, k, coll) }
}
# same as sorted, without the inner sorted_ call
sorted_(k, coll) := { r: es |
some r in numbers.range(1, count(coll))
es := { e | some e in coll; r == rank(e, k, coll) }
}
result := [ x | x := sorted("name", dict)[_][_][_] ]
It is not generic in the orderBy array -- name and foo are hardcoded there.
I think it might be cool to add a new built-in function: sort_by(any<array<object>, set<object>) -> array<object>

Related

Idiomatic Splice in Go

I checked an existing answer but it's not similar to my case.
I need to pluck an element at the index and break out of the for loop at runtime based on Compare function.
Issues:
If element to pluck is found at 0 index, index-1 will throw slice bounds of range error and similarly if index+1 is greater than len(elements).
Question: What's the best concise way to achieve the above?
for index, element := range elements {
if element.Compare() == true {
elements = append(elements[:index-1], elements[index+1:]...)
break
}
}
Attempt
for index, element := range elements {
if element.Compare() == true {
if len(elements) > 1 {
elements = append(elements[:index-1], elements[index+1:]...)
} else if len(elements) == 1 {
delete(elements, 0)
}
break
}
}
Attempt 2 Playground any improvements/suggestions?
The idea is to copy the remaining elements from beginning to index and then any elements after.
var elements = []string {"a", "b", "c", "d"}
fmt.Println(elements)
for index, element := range elements {
if element == "c" {
var temp = elements[:index]
for i := index + 1; i<len(elements); i++ {
temp = append(temp, elements[i])
}
elements = temp
break
}
}
fmt.Println(elements)
The high index in a slice expression is exclusive.
This means your example is flawed, and also that no special treatment is required.
The correct slicing expression is:
elements = append(elements[:index], elements[index+1:]...)
If index is the first element (0), then elements[:0] will be an empty slice.
If index is the last element (len-1), then elements[index+1:] will also be an empty slice, as index+1 will be equal to the lenght of the slice. So the solution is simply:
for index, element := range elements {
if element.Compare() {
elements = append(elements[:index], elements[index+1:]...)
break
}
}
To demonstrate it on the Go Playground, let's substitute the Compare() method with a simple index check:
for _, idxToRemove := range []int{0, 2, 4} {
s := []int{0, 1, 2, 3, 4}
for i := range s {
if i == idxToRemove {
s = append(s[:i], s[i+1:]...)
break
}
}
fmt.Println(idxToRemove, ":", s)
}
Output (try it on the Go Playground):
0 : [1 2 3 4]
2 : [0 1 3 4]
4 : [0 1 2 3]
If the slice s is sorted and len(s) is large, find x using a binary search. For example,
package main
import (
"fmt"
"sort"
)
func pluck(s []string, x string) []string {
i := sort.SearchStrings(s, x)
if i >= 0 && i < len(s) && s[i] == x {
s = append(s[:i], s[i+1:]...)
}
return s
}
func main() {
s := []string{"a", "b", "c", "d"}
fmt.Println(s)
s = pluck(s, "b")
fmt.Println(s)
}
Output:
[a b c d]
[a c d]
If the order of slice s does not need to be preserved, switch elements. For example,
package main
import "fmt"
func pluck(s []string, x string) []string {
for i, v := range s {
if v == x {
s[i] = s[len(s)-1]
s = s[:len(s)-1]
break
}
}
return s
}
func main() {
s := []string{"a", "b", "c", "d"}
fmt.Println(s)
s = pluck(s, "b")
fmt.Println(s)
}
Output:
[a b c d]
[a d c]
Otherwise, splice slice s elements. For example,
package main
import "fmt"
func pluck(s []string, x string) []string {
for i, v := range s {
if v == x {
s = append(s[:i], s[i+1:]...)
break
}
}
return s
}
func main() {
s := []string{"a", "b", "c", "d"}
fmt.Println(s)
s = pluck(s, "b")
fmt.Println(s)
}
Output:
[a b c d]
[a c d]
I'm not sure if this is idiomatic, but this works quite well:
package main
import "fmt"
func splice(start, count int, items []string) (ret []string) {
ret = make([]string, len(items)-count)
copy(ret, items[:start])
copy(ret[start:], items[start+count:])
return
}
func main() {
s := []string{"a", "b", "c", "d"}
fmt.Println(s)
s = splice(1, 2, s)
fmt.Println(s)
}
Go Playground: https://play.golang.org/p/UNtdtw77sEQ

How to convert slice of structs to slice of strings in go?

New go user here.
I have a slice of this struct objects:
type TagRow struct {
Tag1 string
Tag2 string
Tag3 string
}
Which yeilds slices like:
[{a b c} {d e f} {g h}]
I'm wondering how can I convert the resulting slice to a slice of strings like:
["a" "b" "c" "d" "e" "f" "g" "h"]
I tried to iterate over like:
for _, row := range tagRows {
for _, t := range row {
fmt.Println("tag is" , t)
}
}
But I get:
cannot range over row (type TagRow)
So appreciate your help.
For your specific case I would just do it "manually":
rows := []TagRow{
{"a", "b", "c"},
{"d", "e", "f"},
{"g", "h", "i"},
}
var s []string
for _, v := range rows {
s = append(s, v.Tag1, v.Tag2, v.Tag3)
}
fmt.Printf("%q\n", s)
Output:
["a" "b" "c" "d" "e" "f" "g" "h" "i"]
If you want it to dynamically walk through all fields, you may use the reflect package. A helper function which does that:
func GetFields(i interface{}) (res []string) {
v := reflect.ValueOf(i)
for j := 0; j < v.NumField(); j++ {
res = append(res, v.Field(j).String())
}
return
}
Using it:
var s2 []string
for _, v := range rows {
s2 = append(s2, GetFields(v)...)
}
fmt.Printf("%q\n", s2)
Output is the same:
["a" "b" "c" "d" "e" "f" "g" "h" "i"]
Try the examples on the Go Playground.
See similar questions with more complex examples:
Golang, sort struct fields in alphabetical order
How to print struct with String() of fields?

Golang: How to sort slices by days of the week?

I have a slice of strings which contains the days of the week like this:
days := []string{"M", "Th", "W", "F", "T", "S", "Su"}
How do I sort them so it will output like this:
[M T W Th F S Su]
starting with Monday.
You can create a map keying days to their relative order. Then you can have a type implement sort.Interface interface:
type Days []string
// map to store weekdays' relative order
var days = map[string]int{
"M": 1,
"T": 2,
"W": 3,
"Th": 4,
"F": 5,
"S": 6,
"Su": 7,
}
func (d Days) Len() int { return len(d) }
func (d Days) Swap(i, j int) { d[i], d[j] = d[j], d[i] }
func (d Days) Less(i, j int) bool {
return days[d[i]] < days[d[j]]
}
func main() {
ds := Days([]string{"M", "Th", "W", "F", "T", "S", "Su"})
sort.Sort(ds)
fmt.Println(ds)
}
Example: https://play.golang.org/p/lzdfI-1BJK
If you want you can move days in Less method to avoid globals. But that would be severely inefficient since the map will be initialized for each element of Days.
Another implementation would be to have a pre-declared slice with the days in correct order and then use strings.Index in Less. This would perform slower but since your slice is of fixed length and very small (only 7 elements), it shouldn't be much of an issue.
You want the sort.Interface, which lets you use sort.Sort. The tricky part of this is to define the Less method, which needs to return true if one day should be considered to come before another day. I chose to do this by testing the relationship of the days to each other semantically, but you could also do this by assigning each day an int that represents its position in the week.
package main
import (
"fmt"
"sort"
)
type Days []string
func (d Days) Len() int {
return len(d)
}
func (d Days) Swap(i, j int) {
d[i], d[j] = d[j], d[i]
}
func (d Days) Less(i, j int) bool {
switch d[i] {
case "M":
return true
case "T":
return d[j] != "M"
case "W":
return d[j] != "M" && d[j] != "T"
case "Th":
return d[j] != "M" && d[j] != "T" && d[j] != "W"
case "F":
return d[j] == "S" || d[j] == "Su"
case "S":
return d[j] == "Su"
default:
return false
}
}
func main() {
days := []string{"M", "Th", "W", "F", "T", "S", "Su"}
sort.Sort(Days(days))
fmt.Println(days)
}
With Go 1.8 (Q1 2017) and the new sort.go implementing sort.Slice(), Paddy Foran's answer would become:
days := []string{"M", "Th", "W", "F", "T", "S", "Su"}
sort.Slice(days, func(i, j int) bool { return less(days[i], days[j] })
less() being a func taking two strings (first letter of two days) and comparing them.
No need for Swap() or Len() anymore (done by reflection in the new sort implementation)
public static void main(String[] args) {
String[] arr = new String[] { "M", "Th", "W", "F", "T", "S", "Su" };
// [M T W Th F S Su]
String[] arr1 = new String[] { "M", "T", "W", "Th", "F", "S", "Su" };
LinkedHashSet<String> h = new LinkedHashSet<String>();
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr1.length; j++) {
if (arr[i].equals(arr1[j])) {
h.add(arr[i]);
} else {
arr[i] = arr1[j];
h.add(arr[i]);
}
}
}
System.out.println("arr = " + h);
}

How to compare if two structs, slices or maps are equal?

I want to check if two structs, slices and maps are equal.
But I'm running into problems with the following code. See my comments at the relevant lines.
package main
import (
"fmt"
"reflect"
)
type T struct {
X int
Y string
Z []int
M map[string]int
}
func main() {
t1 := T{
X: 1,
Y: "lei",
Z: []int{1, 2, 3},
M: map[string]int{
"a": 1,
"b": 2,
},
}
t2 := T{
X: 1,
Y: "lei",
Z: []int{1, 2, 3},
M: map[string]int{
"a": 1,
"b": 2,
},
}
fmt.Println(t2 == t1)
//error - invalid operation: t2 == t1 (struct containing []int cannot be compared)
fmt.Println(reflect.ValueOf(t2) == reflect.ValueOf(t1))
//false
fmt.Println(reflect.TypeOf(t2) == reflect.TypeOf(t1))
//true
//Update: slice or map
a1 := []int{1, 2, 3, 4}
a2 := []int{1, 2, 3, 4}
fmt.Println(a1 == a2)
//invalid operation: a1 == a2 (slice can only be compared to nil)
m1 := map[string]int{
"a": 1,
"b": 2,
}
m2 := map[string]int{
"a": 1,
"b": 2,
}
fmt.Println(m1 == m2)
// m1 == m2 (map can only be compared to nil)
}
http://play.golang.org/p/AZIzW2WunI
You can use reflect.DeepEqual, or you can implement your own function (which performance wise would be better than using reflection):
http://play.golang.org/p/CPdfsYGNy_
m1 := map[string]int{
"a":1,
"b":2,
}
m2 := map[string]int{
"a":1,
"b":2,
}
fmt.Println(reflect.DeepEqual(m1, m2))
reflect.DeepEqual is often incorrectly used to compare two like structs, as in your question.
cmp.Equal is a better tool for comparing structs.
To see why reflection is ill-advised, let's look at the documentation:
Struct values are deeply equal if their corresponding fields, both exported and unexported, are deeply equal.
....
numbers, bools, strings, and channels - are deeply equal if they are equal using Go's == operator.
If we compare two time.Time values of the same UTC time, t1 == t2 will be false if their metadata timezone is different.
go-cmp looks for the Equal() method and uses that to correctly compare times.
Example:
m1 := map[string]int{
"a": 1,
"b": 2,
}
m2 := map[string]int{
"a": 1,
"b": 2,
}
fmt.Println(cmp.Equal(m1, m2)) // will result in true
Important Note:
Be careful when using cmp.Equal as it may lead to a panic condition
It is intended to only be used in tests, as performance is not a goal
and it may panic if it cannot compare the values. Its propensity
towards panicking means that its unsuitable for production
environments where a spurious panic may be fatal.
Here's how you'd roll your own function http://play.golang.org/p/Qgw7XuLNhb
func compare(a, b *T) bool {
if a == b {
return true
}
if a.X != b.X || a.Y != b.Y {
return false
}
if len(a.Z) != len(b.Z) || len(a.M) != len(b.M) {
return false
}
for i, v := range a.Z {
if b.Z[i] != v {
return false
}
}
for k, v := range a.M {
if b.M[k] != v {
return false
}
}
return true
}
Update: Go 1.18
import (
"golang.org/x/exp/maps"
"golang.org/x/exp/slices"
)
func compare(a, b *T) bool {
if a == b {
return true
}
if a.X != b.X {
return false
}
if a.Y != b.Y {
return false
}
if !slices.Equal(a.Z, b.Z) {
return false
}
return maps.Equal(a.M, b.M)
}
If you intend to use it in tests, since July 2017 you can use cmp.Equal with cmpopts.IgnoreFields option.
func TestPerson(t *testing.T) {
type person struct {
ID int
Name string
}
p1 := person{ID: 1, Name: "john doe"}
p2 := person{ID: 2, Name: "john doe"}
println(cmp.Equal(p1, p2))
println(cmp.Equal(p1, p2, cmpopts.IgnoreFields(person{}, "ID")))
// Prints:
// false
// true
}
If you're comparing them in unit test, a handy alternative is EqualValues function in testify.
If you want to compare simple one-level structs, the best and simple method is the if statement.
Like this if s1 == s2
Here is a simple example:
type User struct {
name string
email string
}
func main() {
u1 := User{
name: "Iron Man",
email: "ironman#avengers.com",
}
u2 := User{
name: "Iron Man",
email: "ironman#avengers.com",
}
// Comparing 2 structs
if u1 == u2 {
fmt.Println("u1 is equal to u2")
} else {
fmt.Println("u1 is not equal to u2")
}
}
Result: u1 is equal to u2
You can play with this here.
New way to compare maps
This proposal (https://github.com/golang/go/issues/47649) that is part of the future implementation of Go generics introduces a new function to compare two maps, maps.Equal:
// Equal reports whether two maps contain the same key/value pairs.
// Values are compared using ==.
func Equal[M1, M2 constraints.Map[K, V], K, V comparable](m1 M1, m2 M2) bool
Example use
strMapX := map[string]int{
"one": 1,
"two": 2,
}
strMapY := map[string]int{
"one": 1,
"two": 2,
}
equal := maps.Equal(strMapX, strMapY)
// equal is true
maps package is found in golang.org/x/exp/maps. This is experimental and outside of Go compatibility guarantee. They aim to move it into the std lib in Go 1.19
You can see it working in gotip playground
https://gotipplay.golang.org/p/M0T6bCm1_3m

Sorting slices of strings by a map of their frequencies

I have a slice of slices of strings, and want to sort them by their frequency, I tried to follow the byAge example from the docs here http://golang.org/pkg/sort/ but was unable how to pass a list of frequencies to it.
Meaning, the outcome of the example would be:
[[a,b] [a,b,c,d] [a,c,d,e]]
Would the approach be to have "a" be represented by a custom struct with frequency as it's own attribute? That seems to be more in line with the byAge example.
func main() {
transactions := [][]string{{"a", "b"}, {"b", "c", "d", "a"}, {"c", "d", "e", "a"}}
frequencies := map[string]int{
"a": 3,
"b": 2,
"c": 2,
"d": 2,
"e": 1,
}
fmt.Println(transactions, frequencies)
}
In case you need more than the data you want to sort in the sorting process, a common way is
to implement your own struct, yes. In your case this would be something like this (on play):
type SortableTransaction struct {
data []string
frequencies map[string]int
}
data would be the slice with strings and frequencies your specific frequency table.
The following implementation could be used for the Sort interface:
func (s SortableTransaction) Len() int { return len(s.data) }
func (s SortableTransaction) Less(i, j int) bool {
return s.frequencies[s.data[i]] > s.frequencies[s.data[j]]
}
func (s SortableTransaction) Swap(i, j int) {
s.data[j], s.data[i] = s.data[i], s.data[j]
}
If your frequency table is constant, you can declare it at package level of course.
In case you want to sort the outer slice as well, you'd have to sort the inner slices
first and then the outer slices.
For example,
package main
import (
"fmt"
"sort"
)
type NameFrequency struct {
Name string
Frequency int
}
func (nf NameFrequency) String() string {
return fmt.Sprintf("%s: %d", nf.Name, nf.Frequency)
}
type ByFrequency []NameFrequency
func (nf ByFrequency) Len() int { return len(nf) }
func (nf ByFrequency) Swap(i, j int) { nf[i], nf[j] = nf[j], nf[i] }
func (nf ByFrequency) Less(i, j int) bool {
less := nf[i].Frequency > nf[j].Frequency
if nf[i].Frequency == nf[j].Frequency {
less = nf[i].Name < nf[j].Name
}
return less
}
func SortByFrequency(names []string, frequencies map[string]int) []string {
nf := make(ByFrequency, len(names))
for i, name := range names {
nf[i] = NameFrequency{name, frequencies[name]}
}
sort.Sort(ByFrequency(nf))
sortedNames := make([]string, len(names))
for i, nf := range nf {
sortedNames[i] = nf.Name
}
return sortedNames
}
func main() {
transactions := [][]string{{"a", "b"}, {"b", "c", "d", "a"}, {"c", "d", "e", "a"}}
fmt.Println(transactions)
frequencies := map[string]int{
"a": 3,
"b": 2,
"c": 2,
"d": 2,
"e": 1,
}
fmt.Println(frequencies)
sortedTransactions := make([][]string, len(transactions))
for i, transaction := range transactions {
sortedTransactions[i] = SortByFrequency(transaction, frequencies)
}
fmt.Println(sortedTransactions)
}
Output:
[[a b] [b c d a] [c d e a]]
map[a:3 b:2 c:2 d:2 e:1]
[[a b] [a b c d] [a c d e]]

Resources