deepsort

package module
v0.1.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 13, 2024 License: MIT Imports: 7 Imported by: 0

README

deepsort: Sort slice of slices in Go

Contents

  1. About
  2. Installation
  3. Usage
  4. Supported Data Types
  5. Error Handling

About

The deepsort package provides the DeepSort function, which allows sorting a slice of slices based on the values in multiple positions within the inner slices. DeepSort can handle sorting for any comparable type withing the inner slices (such as integers, floats, complex numbers, strings and more). It also supports sorting the inner slices based on struct types by specifying the struct field to be used for sorting.

Installation

go install github.com/gbatagian/deepsort@latest

Usage

package main

import (
	"fmt"

	"github.com/gbatagian/deepsort"
)

func main() {
	// Define the slice of slices to be sorted
	data := [][]any{
		{2, "a"},
		{1, "a"},
		{3, "a"},
		{2, "b"},
		{1, "b"},
		{3, "b"},
	}

	// Sort the data based on index positions 0 and 1 (ascending order)
	deepsort.DeepSort(&data, []int{0, 1})

	// Output the sorted data
	for _, row := range data {
		fmt.Println(row)
	}
}

Output:

[1 a]
[1 b]
[2 a]
[2 b]
[3 a]
[3 b]

By default, the sorting order is ascending. To specify descending order for an index position, use the negative equivalent of the index position, e.g.:

// Sort based on index position 0 in ascending order and index position 1 in descending order
data = deepsort.DeepSort(&data, []int{0, -1})

Based on the above example, this would output:

[1 b]
[1 a]
[2 b]
[2 a]
[3 b]
[3 a]

When sorting in descending order on the zero index position, use math.Copysign to force the negative sign. In those cases specify the sort positions as []float64 instead of []int, e.g.:

// Sort based on index position 0 in descending order and index position 1 in ascending order
data = deepsort.DeepSort(data, []float64{math.Copysign(0, -1), 1})

which would produce the following output based on the initial example:

[3 a]
[3 b]
[2 a]
[2 b]
[1 a]
[1 b]

When sorting the inner slices based on struct fields, you need to provide the combination of the struct's index position within the inner slices and the struct field to be used for sorting in the format index:FieldName (int:string). To specify descending order within this format, utilize the negative equivalent of the index, e.g. -2:SampleField. If only struct fields are provided, specify the sort positions as []string. If combinations of index positions with struct fields are provided, use []any, e.g.:

package main

import (
	"fmt"

	"github.com/gbatagian/deepsort"
)

func main() {
	// Define a sample struct
	type Person struct {
		Name string
		Age  int
	}

	// Define the slice of slices to be sorted
	values := [][]any{
		{Person{"Alice", 30}, 1},
		{Person{"Emma", 22}, 2},
		{Person{"Charlie", 18}, 3},
		{Person{"Alice", 42}, 1},
		{Person{"Emma", 37}, 2},
		{Person{"Charlie", 28}, 3},
	}

	// Sort the values based on index position 1 ascending, struct (in index position 0) field "Name" ascending and field "Age" descending
	deepsort.DeepSort(&values, []any{1, "0:Name", "-0:Age"})

	// Output the sorted data
	for _, e := range values {
		fmt.Println(e)
	}
}

which would output:

[{Alice 42} 1]
[{Alice 30} 1]
[{Emma 37} 2]
[{Emma 22} 2]
[{Charlie 28} 3]
[{Charlie 18} 3]

Supported Data Types

The slice of slices that is passed as input to the DeepSort function can be of any comparable data type, i.e. [][]int, [][]float32, [][]string, [][]customStruct, [][]any etc. As long as the elements at the same index position within the nested slices are of the same type, DeepSort can handle them for sorting. DeepSort is also designed to be able to handle boolean values when comparing the inner slices. When sorting based on booleans, false is considered to be less than true. So, when sorting in ascending order, slices with false will come before those with true, e.g.:

package main

import (
	"fmt"

	"github.com/gbatagian/deepsort"
)

func main() {
	// Define the slice of slices to be sorted
	data := [][]any{
		{3 + 3i, true},
		{3 + 3i, false},
		{1 + 1i, true},
		{1 + 1i, false},
		{2 + 2i, true},
		{2 + 2i, false},
	}

	// Sort the data based on index positions 1 and 0 (ascending order)
	deepsort.DeepSort(&data, []int{1, 0})

	// Output the sorted data
	for _, row := range data {
		fmt.Println(row)
	}
}

Which will output:

[(1+1i) false]
[(2+2i) false]
[(3+3i) false]
[(1+1i) true]
[(2+2i) true]
[(3+3i) true]

Error Handling

Mixed types on the same index position

If values at the same index position are of different types, DeepSort will panic, e.g.:

package main

import (
	"fmt"

	"github.com/gbatagian/deepsort"
)

func main() {
	// Define the slice of slices to be sorted
	data := [][]any{
		{2, true},
		{2, "false"},
		{1, true},
		{1, false},
	}

	// Sort the data based on index positions 0 and 1 (ascending order)
	deepsort.DeepSort(&data, []int{0, 1})

	// Output the sorted data
	for _, row := range data {
		fmt.Println(row)
	}
}

Output:

panic: sorting error at position 1. Value false (string) and true (bool) cannot be compared. Values at the same sort position must be of the same type
...

However, if an index position with values of different types exists in the inner slices but is not used by DeepSort, the algorithm will not panic. For instance, in the above example, if the DeepSort call is modified as follows:

// Sort the data based on index positions 0 in ascending order
data = deepsort.DeepSort(&data, []int{0})

The output would be:

[1 true]
[1 false]
[2 true]
[2 false] # This is the row with "false"

In this case, the sorting is based only on the first index position, so the rows with mixed types in the second index position do not cause a panic.

Invalid string position format

DeepSort will panic if a string sort position is not provided in the format int:string, e.g.

package main

import (
	"fmt"

	"github.com/gbatagian/deepsort"
)

func main() {
	// Define a sample struct
	type sample struct {
		Field string
	}

	// Define the slice of slices to be sorted
	values := [][]any{
		{sample{"a"}},
		{sample{"b"}},
		{sample{"c"}},
	}

	// Sort
	deepsort.DeepSort(&values, []any{"--0:Field"})

	// Output the sorted data
	for _, e := range values {
		fmt.Println(e)
	}
}

Output:

panic: invalid field specifier format: "--0:Field". Use int:string (e.g. "0:Name", "-1:Age" etc.)
...
Invalid sort position type

Furthermore, DeepSort will panic if the provided sort position is not of type int, float64, or string. For example, if the call in the above example is changed to:

// Sort
deepsort.DeepSort(&values, []any{[]any{0, "Field"}})

the function will output:

panic: unsupported type []interface {}([0 Field]) provided for sort position. Supported types: int | float64 | string
Incomparable sort data types

If the provided data for sorting are incomparable, DeepSort wil also panic, e.g.:

package main

import (
	"fmt"

	"github.com/gbatagian/deepsort"
)

func main() {
	// Define the slice of slices to be sorted
	values := [][]any{
		{map[string]int{"a": 1}},
		{map[string]int{"b": 2}},
		{map[string]int{"c": 3}},
	}

	// Sort
	deepsort.DeepSort(&values, []any{0})

	// Output the sorted data
	for _, e := range values {
		fmt.Println(e)
	}
}

output:

panic: runtime error: comparing uncomparable type map[string]int

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func DeepSort

func DeepSort[SortPosition, T comparable](sliceOfSlices *[][]T, positions []SortPosition)

DeepSort sorts a slice of slices based on multiple user-specified positions or fields. It supports various sorting criteria inputs:

  • Index-based sorting (int/float, converted to int): -- Used for direct element access within the inner slices for sorting. -- Sign determines ascending/descending order (positive/negative).
  • Field-based sorting (structs): -- Use strings in the format "index:fieldName" (e.g., "1:Name" or "-2:Age") -- The integer specifies the index of the struct within the inner slice. -- The field name identifies the struct field used for sorting. -- The sign on the integer controls ascending/descending order.

Index-based sorting and Field-based sorting can be used (if needed) in combination, e.g.:

type sample struct {
	Field string
}

values := [][]any{
	{2, sample{"a"}},
	{2, sample{"b"}},
	{1, sample{"a"}},
	{1, sample{"b"}},
}

deepsort.DeepSort(&values, []any{0, "-1:Field"})

which will result `values` to be:

[1 {b}]
[1 {a}]
[2 {b}]
[2 {a}]

Alternatively `positions []SortPositions` argument can be []int or []float64. Refer README.md for more example and use cases.

Types

type SortPosition added in v0.1.4

type SortPosition interface {
	int | float64 | string
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL