Count occurrence of elements in map during creation - loops

This code works, but it's so ugly, so I was hesitant to put it on display. There must be a nicer/easier way to achieve this. The main issue is to count each unique occurrence during runtime when the object creation takes time.
Input:
1190 W X 1648 H X 14 T
S
1190 W X 1254 H X 14 T
S
1190 W X 1254 H X 14 T
S
1190 W X 1254 H X 14 T
S
1190 W X 1254 H X 14 T
S
1190 W X 1648 H X 14 T
I read each word by word, I need to extract from this data, occurrences, plus number of times each elements occurs.
func CreateGlassBoxNew(db *gorm.DB, boxID uint, internalName string, glassBoxData []byte) {
m := make(map[string]Measurement)
var outerList []string
var innerList []int
var index = 0
var count = 0
var appendStr string
scanner := bufio.NewScanner(strings.NewReader(string(glassBoxData)))
scanner.Split(bufio.ScanWords)
for scanner.Scan() {
if scanner.Text() == "S" || scanner.Text() == "W" || scanner.Text() == "H" || scanner.Text() == "T" || scanner.Text() == "X" {
continue
}
appendStr += scanner.Text() + "-"
innerList = append(innerList, addMeasurementInt(scanner.Text()))
index++
if index == 3 {
dump.P(innerList)
count++
fmt.Println(count)
m[appendStr] = Measurement{
Width: innerList[0],
Height: innerList[1],
Thicknes: innerList[2],
Quantity: count,
GlassBoxID: boxID,
}
innerList = nil
outerList = append(outerList, appendStr)
appendStr = ""
index = 0
}
}
if err := scanner.Err(); err != nil {
dump.P(err)
}
dump.P(m)
occurenceMap := CountOccurence(outerList)
var finalList []Measurement
for key, element := range m {
finalList = append(finalList, Measurement{
Width: element.Width,
Height: element.Height,
Thicknes: element.Thicknes,
Quantity: occurenceMap[key],
GlassBoxID: boxID,
})
}
dump.P(finalList)
}
the final result ready for db insert, looks like this, here is the quantity variable set.
[]models.Measurement [ #len=2
models.Measurement {
Width: int(1190),
Height: int(1648),
Thicknes: int(14),
Quantity: int(2),
GlassBoxID: uint(3436),
BluePrintName: *string<nil>,
},
models.Measurement {
Width: int(1190),
Height: int(1254),
Thicknes: int(14),
Quantity: int(4),
GlassBoxID: uint(3436),
BluePrintName: *string<nil>,
},
],

Related

How to remove duplicates strings or int from Slice in Go

Let's say I have a list of student cities and the size of it could be 100 or 1000, and I want to filter out all duplicates cities.
I want a generic solution that I can use to remove all duplicate strings from any slice.
I am new to Go Language, So I tried to do it by looping and checking if the element exists using another loop function.
Students' Cities List (Data):
studentsCities := []string{"Mumbai", "Delhi", "Ahmedabad", "Mumbai", "Bangalore", "Delhi", "Kolkata", "Pune"}
Functions that I created, and it's doing the job:
func contains(s []string, e string) bool {
for _, a := range s {
if a == e {
return true
}
}
return false
}
func removeDuplicates(strList []string) []string {
list := []string{}
for _, item := range strList {
fmt.Println(item)
if contains(list, item) == false {
list = append(list, item)
}
}
return list
}
My solution test
func main() {
studentsCities := []string{"Mumbai", "Delhi", "Ahmedabad", "Mumbai", "Bangalore", "Delhi", "Kolkata", "Pune"}
uniqueStudentsCities := removeDuplicates(studentsCities)
fmt.Println(uniqueStudentsCities) // Expected output [Mumbai Delhi Ahmedabad Bangalore Kolkata Pune]
}
I believe that the above solution that I tried is not an optimum solution. Therefore, I need help from you guys to suggest the fastest way to remove duplicates from the slice?
I checked StackOverflow, this question is not being asked yet, so I didn't get any solution.
I found Burak's and Fazlan's solution helpful. Based on that, I implemented the simple functions that help to remove or filter duplicate data from slices of strings, integers, or any other types with generic approach.
Here are my three functions, first is generic, second one for strings and last one for integers of slices. You have to pass your data and return all the unique values as a result.
Generic solution: => Go v1.18
func removeDuplicate[T string | int](sliceList []T) []T {
allKeys := make(map[T]bool)
list := []T{}
for _, item := range sliceList {
if _, value := allKeys[item]; !value {
allKeys[item] = true
list = append(list, item)
}
}
return list
}
To remove duplicate strings from slice:
func removeDuplicateStr(strSlice []string) []string {
allKeys := make(map[string]bool)
list := []string{}
for _, item := range strSlice {
if _, value := allKeys[item]; !value {
allKeys[item] = true
list = append(list, item)
}
}
return list
}
To remove duplicate integers from slice:
func removeDuplicateInt(intSlice []int) []int {
allKeys := make(map[int]bool)
list := []int{}
for _, item := range intSlice {
if _, value := allKeys[item]; !value {
allKeys[item] = true
list = append(list, item)
}
}
return list
}
You can update the slice type, and it will filter out all duplicates data for all types of slices.
Here is the GoPlayground link: https://go.dev/play/p/iyb97KcftMa
Adding this answer which worked for me, does require/include sorting, however.
func removeDuplicateStrings(s []string) []string {
if len(s) < 1 {
return s
}
sort.Strings(s)
prev := 1
for curr := 1; curr < len(s); curr++ {
if s[curr-1] != s[curr] {
s[prev] = s[curr]
prev++
}
}
return s[:prev]
}
For fun, I tried using generics! (Go 1.18+ only)
type SliceType interface {
~string | ~int | ~float64 // add more *comparable* types as needed
}
func removeDuplicates[T SliceType](s []T) []T {
if len(s) < 1 {
return s
}
// sort
sort.SliceStable(s, func(i, j int) bool {
return s[i] < s[j]
})
prev := 1
for curr := 1; curr < len(s); curr++ {
if s[curr-1] != s[curr] {
s[prev] = s[curr]
prev++
}
}
return s[:prev]
}
Go Playground Link with tests: https://go.dev/play/p/bw1PP1osJJQ
You can do in-place replacement guided with a map:
processed := map[string]struct{}{}
w := 0
for _, s := range cities {
if _, exists := processed[s]; !exists {
// If this city has not been seen yet, add it to the list
processed[s] = struct{}{}
cities[w] = s
w++
}
}
cities = cities[:w]
reduce memory usage:
package main
import (
"fmt"
"reflect"
)
type void struct{}
func main() {
digits := [6]string{"one", "two", "three", "four", "five", "five"}
set := make(map[string]void)
for _, element := range digits {
set[element] = void{}
}
fmt.Println(reflect.ValueOf(set).MapKeys())
}
p.s. playground
Simple to understand.
func RemoveDuplicate(array []string) []string {
m := make(map[string]string)
for _, x := range array {
m[x] = x
}
var ClearedArr []string
for x, _ := range m {
ClearedArr = append(ClearedArr, x)
}
return ClearedArr
}
If you want to don't waste memory allocating another array for copy the values, you can remove in place the value, as following:
package main
import "fmt"
var studentsCities = []string{"Mumbai", "Delhi", "Ahmedabad", "Mumbai", "Bangalore", "Delhi", "Kolkata", "Pune"}
func contains(s []string, e string) bool {
for _, a := range s {
if a == e {
return true
}
}
return false
}
func main() {
fmt.Printf("Cities before remove: %+v\n", studentsCities)
for i := 0; i < len(studentsCities); i++ {
if contains(studentsCities[i+1:], studentsCities[i]) {
studentsCities = remove(studentsCities, i)
i--
}
}
fmt.Printf("Cities after remove: %+v\n", studentsCities)
}
func remove(slice []string, s int) []string {
return append(slice[:s], slice[s+1:]...)
}
Result:
Cities before remove: [Mumbai Delhi Ahmedabad Mumbai Bangalore Delhi Kolkata Pune]
Cities after remove: [Ahmedabad Mumbai Bangalore Delhi Kolkata Pune]
It can also be done with a set-like map:
ddpStrings := []string{}
m := map[string]struct{}{}
for _, s := range strings {
if _, ok := m[scopeStr]; ok {
continue
}
ddpStrings = append(ddpStrings, s)
m[s] = struct{}{}
}
func UniqueNonEmptyElementsOf(s []string) []string {
unique := make(map[string]bool, len(s))
var us []string
for _, elem := range s {
if len(elem) != 0 {
if !unique[elem] {
us = append(us, elem)
unique[elem] = true
}
}
}
return us
}
send the duplicated splice to the above function, this will return the splice with unique elements.
func main() {
studentsCities := []string{"Mumbai", "Delhi", "Ahmedabad", "Mumbai", "Bangalore", "Delhi", "Kolkata", "Pune"}
uniqueStudentsCities := UniqueNonEmptyElementsOf(studentsCities)
fmt.Println(uniqueStudentsCities)
}
Here's a mapless index based slice's duplicate "remover"/trimmer. It use a sort method.
The n value is always 1 value lower than the total of non duplicate elements that's because this methods compare the current (consecutive/single) elements with the next (consecutive/single) elements and there is no matches after the lasts so you have to pad it to include the last.
Note that this snippet doesn't empty the duplicate elements into a nil value. However since the n+1 integer start at the duplicated item's indexes, you can loop from said integer and nil the rest of the elements.
sort.Strings(strs)
for n, i := 0, 0; ; {
if strs[n] != strs[i] {
if i-n > 1 {
strs[n+1] = strs[i]
}
n++
}
i++
if i == len(strs) {
if n != i {
strs = strs[:n+1]
}
break
}
}
fmt.Println(strs)
Based on Riyaz's solution, you can use generics since Go 1.18
func removeDuplicate[T string | int](tSlice []T) []T {
allKeys := make(map[T]bool)
list := []T{}
for _, item := range tSlice {
if _, value := allKeys[item]; !value {
allKeys[item] = true
list = append(list, item)
}
}
return list
}
Generics minimizes code duplication.
Go Playground link : https://go.dev/play/p/Y3fEtHJpP7Q
So far #snassr has given the best answer as it is the most optimized way in terms of memory (no extra memory) and runtime (nlogn). But one thing I want to emphasis here is if we want to delete any index/element of an array we should loop from end to start as it reduces complexity. If we loop from start to end then if we delete nth index then we will accidentally miss the nth element (which was n+1th before deleting nth element) as in the next iteration we will get the n+1th element.
Example Code
func Dedup(strs []string) {
sort.Strings(strs)
for i := len(strs) - 1; i > 0; i-- {
if strs[i] == strs[i-1] {
strs = append(strs[:i], strs[i+1:]...)
}
}
}
try: https://github.com/samber/lo#uniq
names := lo.Uniq[string]([]string{"Samuel", "John", "Samuel"})
// []string{"Samuel", "John"}

Calculate the data received inside the recursive function

The function outputs via print() all possible combinations of the characters "abc". (Depending on the specified length)
I need to calculate this amount. I only managed to output these combinations one by one through print(). I left a comment in the right place of the code.
func allLexicographicRecur (_ string: [String.Element], _ data: [String], _ last: Int, _ index: Int){
var length = string.count-1
var data = data
for i in 0...length {
data[index] = String(string[i])
if index == last {
print(data.joined()) // Displays a combination. It is necessary to somehow calculate.
}else{
allLexicographicRecur(string, data, last, index+1)
}
}
}
func allLexicographic(_ l: Int) {
var alphabet = "abc"
var data = Array(repeating: "", count: l)
var string = alphabet.sorted()
var counter = 0
allLexicographicRecur(string, data, l-1, 0)
}
allLexicographic(3)
The function must somehow return the number of these combinations.
I would be very grateful for the help!
I managed to count only this way (but most likely it is not the best way to do it):
var count = 0
func allLexicographicRecur (_ string: [String.Element], _ data: [String], _ last: Int, _ index: Int){
var length = string.count-1
var data = data
for i in 0...length {
data[index] = String(string[i])
if index == last {
print(data.joined()) // Displays a combination. It is necessary to somehow calculate.
count += 1
}else{
allLexicographicRecur(string, data, last, index+1)
}
}
}
func allLexicographic(_ l: Int) {
var alphabet = "abc"
var data = Array(repeating: "", count: l)
var string = alphabet.sorted()
var counter = 0
allLexicographicRecur(string, data, l-1, 0)
}
allLexicographic(3)
print(count)
You do not need a global variable. There are at least two other options. You can add an inout parameter to allLexicographicRecur to keep track of the count or you can have allLexicographicRecur return its count.
Here's your code using a return value:
func allLexicographicRecur(_ string: [String.Element], _ data: [String], _ last: Int, _ index: Int) -> Int {
let length = string.count - 1
var data = data
var count = 0
for i in 0...length {
data[index] = String(string[i])
if index == last {
print(data.joined()) // Displays a combination. It is necessary to somehow calculate.
count += 1
} else {
count += allLexicographicRecur(string, data, last, index + 1)
}
}
return count
}
func allLexicographic(_ l: Int) -> Int {
let alphabet = "abc"
let data = Array(repeating: "", count: l)
let string = alphabet.sorted()
return allLexicographicRecur(string, data, l - 1, 0)
}
print(allLexicographic(3))
Here's your code updated to use an inout parameter.
func allLexicographicRecur(_ string: [String.Element], _ data: [String], _ last: Int, _ index: Int, _ count: inout Int){
let length = string.count - 1
var data = data
for i in 0...length {
data[index] = String(string[i])
if index == last {
print(data.joined()) // Displays a combination. It is necessary to somehow calculate.
count += 1
} else {
allLexicographicRecur(string, data, last, index + 1, &count)
}
}
}
func allLexicographic(_ l: Int) -> Int {
let alphabet = "abc"
let data = Array(repeating: "", count: l)
let string = alphabet.sorted()
var counter = 0
allLexicographicRecur(string, data, l - 1, 0, &counter)
return counter
}
print(allLexicographic(3))
You can not mange the count without global variable because of recursive function. so the method you wrote in question is perfect as per the output you want to have.

Index out of range trying to add to slice

var bar string
var i int
var a []string
for foo, _ := reader.NextWord(); foo != bar; foo, _ = reader.NextWord() {
bar = foo
fmt.Print(foo)
a[i] = foo
i++
}
Shouldn't this be creating a nil slice and then adding the value to the appropriate place? I keep getting index out of range so I assume it's not adding to a[i]...
Checking length first with
if len(a) > 0 {
a[i] = foo
}
seems to help, but not getting the results I expected. I'll keep playing around.
Update: I did end up using append... I meant to update this thread but thank you both.
package main
import (
"fmt"
"log"
"os"
"strings"
"github.com/steven-ferrer/gonsole"
)
func main() {
file, err := os.Open("test.txt")
if err != nil {
log.Println(err)
}
defer file.Close()
reader := gonsole.NewReader(file)
// cycle through
var bar string
var i int
var quality []string = make([]string, 0)
var tempName []string = make([]string, 0)
var name []string = make([]string, 0)
for foo, _ := reader.NextWord(); foo != bar; foo, _ = reader.NextWord() {
bar = foo
if strings.Contains(foo, "(normal)") {
quality = append(quality, "normal")
for state := 0; state < 1; foo, _ = reader.NextWord() {
if foo == "|" {
state = 1
}
tempName = append(tempName, foo)
}
nameString := strings.Join(tempName, "")
name = append(name, nameString)
} else if strings.Contains(foo, "(unique)") {
quality = append(quality, "unique")
for state := 0; state < 1; foo, _ = reader.NextWord() {
if foo == "|" {
state = 1
}
tempName = append(tempName, foo)
}
nameString := strings.Join(tempName, "")
name = append(name, nameString)
} else if strings.Contains(foo, "(set)") {
quality = append(quality, "set")
for state := 0; state < 1; foo, _ = reader.NextWord() {
if foo == "|" {
state = 1
}
tempName = append(tempName, foo)
}
nameString := strings.Join(tempName, "")
name = append(name, nameString)
}
if tempName != nil {
tempName = nil // clear tempName
}
i++
}
Your slice a needs to be allocated utilizing make.
var a []string = make([]string, n)
where n is the size of the slice.
Removing some of the context-specific parts of your code, you should be using the append method against a dynamic-length slice.
package main
import (
"fmt"
"strings"
)
func main() {
book := "Lorem ipsum dolor sit amet"
var words []string
for _, word := range strings.Split(book, " ") {
words = append(words, word)
}
fmt.Printf("%+v\n", words)
}
https://play.golang.org/p/LMejsrmIGb9
If you know the number of values up front, the same can be achieved for a fixed length slice by using words := make([]string, 5), but I doubt this is what you want in this case.
The reason your code is causing you errors is because your slice isn't initialized at any given length, so your indexes don't yet exist. Generally when working with a slice, append is the method you want.
Opposite to this, when working with existing slices (ie, rangeing an slice), the reason you're able to set the values using indexes is because the index has already been allocated.

Find connected components in array

I'd like to apply some kind of special pattern find algorithm using Swift.
Some explanations:
I'm getting a simple 1-dimensional array that could look like this:
var array = [
"0000000000000000000",
"0000001110000000000",
"0000011111000000000",
"0000001110000000000",
"0000000000000000000",
"0001100000000000000",
"0001100000000011000",
"0011100000000011000",
"0000000000000000000"
]
And I'd like to extract the connected areas of "1"-characters (connected components).
Have a look at this:
111
11111
111
11
11 11
111 11
I'd like to get as result a multidimensional array that includes all x/y-positions of the single components.
var result = [
[ [6,1], [7,1], [8,1], [5,2], [6,2], [7,2], [8,2], [9,2], [6,3], [7,3], [8,2] ] // positions of the first area (the biggest one on top)
[ [3,5], [4,5], [3,6], [4,6], [2,7], [3,7], [4,7] ] // area bottom left
[ [14,6], [15,6], [14,7], [15,7] ] // area bottom right (smallest area)
]
I've coded the function for javascript. You can find the code right here:
var matrix = [
"0000000000000000000",
"0000001110000000000",
"0000011111000000000",
"0000001110000000000",
"0000000000000000000",
"0001100000000000000",
"0001100000000011000",
"0011100000000011000",
"0000000000000000000"
]
Array.prototype.extract_components_positions = function(offset) {
var array = this.map(item => item.split('')).map(str => Array.from(str, Number)),
default_value = 0,
result_object = {}
function test_connection(array, i, j) {
if (array[i] && array[i][j] === -1) {
if (!result_object[default_value]) result_object[default_value] = [];
result_object[default_value].push([j, i]);
array[i][j] = 1;
for (var k = offset; k > 0; k--) {
test_connection(array, i + k, j); // left - right
test_connection(array, i, j + k); // top - bottom
test_connection(array, i - k, j); // right - left
test_connection(array, i, j - k); // bottom - top
}
return true
}
}
array.forEach(function(a) {
a.forEach(function(b, i, bb) {
bb[i] = -b
})
});
array.forEach(function(a, i, aa) {
a.forEach(function(b, j, bb) {
test_connection(aa, i, j) && default_value++
})
})
return [result_object];
}
var result = matrix.extract_components_positions(1);
console.log(JSON.stringify(result))
but I have a big problems translating this Javascript code into Swift!
func extract_components_positions(matrix: [[String]],offset: Int) {
var array = [[]] // no idea how to use map to split the array from ["0011100"],... to ["0","0","1","1",...], ...
var default_value = 0,
result_object = [[Int]()]
func testconnection(matrix: [[String]], i: Int, j: Int) -> [[Int]] {
if (Int(array[i][j] as! Int) == -1) {
array[i][j] = 1
for var k in offset...0 {
testconnection(matrix: array, i: i+k, j: j) // error: "Cannot convert value of type '[[Any]]' to expected argument type '[[String]]'"
testconnection(matrix: array, i: i, j: j+k)
testconnection(matrix: array, i: i-k, j: j)
testconnection(matrix: array, i: i, j: j-k)
}
}
}
array.forEach { (a) in
a.forEach({ (b, i, bb) in // error: "Contextual closure type '(Any) -> Void' expects 1 argument, but 3 were used in closure body"
bb[i] = -b
})
}
array.forEach { (a, i, aa) in // error: "Contextual closure type '([Any]) -> Void' expects 1 argument, but 3 were used in closure body"
a.forEach({ (b, j, bb) in
testconnection(aa, i, j) && default_value++
})
}
return result_object
}
Any help how to fix my code would be very appreciated.
Look like you are playing Minesweeper! Here's my solution (in swift 4.0, Xcode 9.2). See inline comments for explanation.
let array = [
"0000000000000000000",
"0000001110000000000",
"0000011111000000000",
"0000001110000000000",
"0000000000000000000",
"0001100000000000000",
"0001100000000011000",
"0011100000000011000",
"0000000000000000000"
]
// A structure to hold the cell's coordinate as Int array
// can become confusing very quickly
struct Cell: Equatable {
var row: Int
var column: Int
var clusterIndex: Int?
static func == (lhs: Cell, rhs: Cell) -> Bool {
return lhs.row == rhs.row && lhs.column == rhs.column
}
}
// Get all the "1" cells
var cells = array.enumerated().flatMap { arg -> [Cell] in
let (rowIndex, str) = arg
// The flatMap below will become compactMap in Swift 4.1
return str.enumerated().flatMap { colIndex, char in
if char == "1" {
return Cell(row: rowIndex, column: colIndex, clusterIndex: nil)
} else {
return nil
}
}
}
// Assign each cell a clusterIndex
for (i, currentCell) in cells.enumerated() {
// A cell may not have all four neighbors, or not all its
// neighbors are "1" cells, hence the "potential"
let potentialNeighbors = [
Cell(row: currentCell.row - 1, column: currentCell.column, clusterIndex: nil), // above
Cell(row: currentCell.row + 1, column: currentCell.column, clusterIndex: nil), // below
Cell(row: currentCell.row, column: currentCell.column - 1, clusterIndex: nil), // left
Cell(row: currentCell.row, column: currentCell.column + 1, clusterIndex: nil) // right
]
// Get the actual neighboring cells and their indexes
let neighborsAndIndexes = cells.enumerated().filter { arg in
let (_, c) = arg
return potentialNeighbors.contains(c)
}
let neighborIndexes = neighborsAndIndexes.map { $0.0 }
let neighbors = neighborsAndIndexes.map { $0.1 }
// Determine what clusterIndex we should give the current cell and its neighbors
var clusterIndex = 0
if currentCell.clusterIndex != nil {
// If the current cell already has a clusteredIndex, reuse it
clusterIndex = currentCell.clusterIndex!
} else if let neighborClusterIndex = neighbors.first(where: { $0.clusterIndex != nil })?.clusterIndex {
// If the current cell has a neighbor whose clusterIndex is not nil, use that
clusterIndex = neighborClusterIndex
} else {
// Else increment from the max existing clusterIndex
clusterIndex = (cells.map({ $0.clusterIndex ?? 0 }).max() ?? 0) + 1
}
// Assign the same clusterIndex to the current cell and its neighbors
([i] + neighborIndexes).forEach {
cells[$0].clusterIndex = clusterIndex
}
}
// Group the cells by their clusterIndex
let clusters = Dictionary(grouping: cells, by: { $0.clusterIndex! })
.sorted(by: { $0.key < $1.key })
.map { $0.value }
// Print the result
// Visualize which cell belong to which cluster and how it appears on the board
for i in 0..<array.count {
for j in 0..<array[0].count {
if let clusterIndex = cells.first(where: { $0.row == i && $0.column == j })?.clusterIndex {
print(clusterIndex, terminator: "")
} else {
print("-", terminator: "")
}
}
print() // print a newline
}
Result:
-------------------
------111----------
-----11111---------
------111----------
-------------------
---22--------------
---22---------33---
--222---------33---
-------------------
Note that in Swift 4.1 (currently in beta), the flatMap we use here has been renamed to compactMap. This is not to say that flatMap is going away completely. flatMap has 3 versions, only 1 of them has been renamed to compactMap. For more info, see SE-0187.

alphanumeric sorting in Go

I am reading rows from the GAE Datastore and I want to sort them alphanumerically.
Suppose I have something like this:
key name description sequence
===========================================
ASD.. maths1 it is maths chap21.1
ASD.. maths2 it is maths chap21.10
ASD.. maths3 it is maths chap21.2
I want the result sorted alphanumerically on the sequence field, like this:
key name description sequence
===========================================
ASD.. maths1 it is maths chap21.1
ASD.. maths3 it is maths chap21.2
ASD.. maths2 it is maths chap21.10
Use ISO/IEC 14651:2011 to construct the sequence sort key. For example,
package main
import (
"fmt"
"sort"
)
const maxByte = 1<<8 - 1
func isDigit(d byte) bool {
return '0' <= d && d <= '9'
}
func SequenceKey(key string) string {
sKey := make([]byte, 0, len(key)+8)
j := -1
for i := 0; i < len(key); i++ {
b := key[i]
if !isDigit(b) {
sKey = append(sKey, b)
j = -1
continue
}
if j == -1 {
sKey = append(sKey, 0x00)
j = len(sKey) - 1
}
if sKey[j] == 1 && sKey[j+1] == '0' {
sKey[j+1] = b
continue
}
if sKey[j]+1 > maxByte {
panic("SequenceKey: invalid key")
}
sKey = append(sKey, b)
sKey[j]++
}
return string(sKey)
}
type Chapter struct {
Key string
Name string
Description string
Sequence string
SequenceKey string `datastore:"-"`
}
type Chapters []*Chapter
var chapters = Chapters{
{Key: "ASD..", Name: "maths1", Description: "it is maths", Sequence: "chap21.1"},
{Key: "ASD..", Name: "maths2", Description: "it is maths", Sequence: "chap21.10"},
{Key: "ASD..", Name: "maths3", Description: "it is maths", Sequence: "chap21.2"},
}
func (s Chapters) Len() int {
return len(s)
}
func (s Chapters) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
type BySequenceKey struct{ Chapters }
func (s BySequenceKey) Less(i, j int) bool {
return s.Chapters[i].SequenceKey < s.Chapters[j].SequenceKey
}
func main() {
for _, chapter := range chapters {
chapter.SequenceKey = SequenceKey(chapter.Sequence)
}
fmt.Println("Unsorted:")
for _, chapter := range chapters {
fmt.Printf(" sequence: %#v\n", chapter.Sequence)
fmt.Printf(" sort key: %#v\n", chapter.SequenceKey)
fmt.Printf(" name: %#v\n", chapter.Name)
}
fmt.Println("Sorted:")
sort.Sort(BySequenceKey{chapters})
for _, chapter := range chapters {
fmt.Printf(" sequence: %#v\n", chapter.Sequence)
fmt.Printf(" sort key: %#v\n", chapter.SequenceKey)
fmt.Printf(" name: %#v\n", chapter.Name)
}
}
Output:
Unsorted:
sequence: "chap21.1"
sort key: "chap\x0221.\x011"
name: "maths1"
sequence: "chap21.10"
sort key: "chap\x0221.\x0210"
name: "maths2"
sequence: "chap21.2"
sort key: "chap\x0221.\x012"
name: "maths3"
Sorted:
sequence: "chap21.1"
sort key: "chap\x0221.\x011"
name: "maths1"
sequence: "chap21.2"
sort key: "chap\x0221.\x012"
name: "maths3"
sequence: "chap21.10"
sort key: "chap\x0221.\x0210"
name: "maths2"
Peter's answer reminded me of the collate package of the go.text repository, a subrepo of the official Go repository that contains some packages that are currently under development. This package offers everything you need and is fully locale and unicode aware.
You could use the CompareString method to sort a slice of rows in-memory, but the better approach would be to store a sort key (a seqence of bytes that can be compared as usual) as an additional column and let GAE do the rest for you.
package main
import (
"code.google.com/p/go.text/collate"
"code.google.com/p/go.text/locale"
"fmt"
)
func main() {
locId := locale.Make("en-US")
col := collate.New(locId)
col.SetOptions(collate.Numeric | collate.IgnoreCase)
keys := []string{"chap21.1", "chap21.10", "chap21.2", "chap21.03.3",
"chap21.3.03", "chap21.03.03"}
buf := new(collate.Buffer)
for i := 0; i < len(keys); i++ {
fmt.Println(keys[i], col.KeyFromString(buf, keys[i]))
}
}
Edit: I have just taken a closer look at the implementation and most of the methods (including SetOptions and the handling of numeric sorting) are not implemented yet. So this answer was probably a bit too early, but at least you got the picture of how you might sort your rows in the future ;)
According to the reference, you can simply sort on the property you require:
From the doc:
// Order alphabetically by last name:
q := datastore.NewQuery("Person").Order("LastName")
So in your example, you could have something along the lines of:
func queryAll(r *http.Request) ([]string, error) {
c := appengine.NewContext(r)
res := make([]string, 0, 0)
t := datastore.NewQuery("YourStructure").Order("Sequence").Run(c)
for {
var s YourStructure
if _, err := t.Next(&s); err == datastore.Done {
// Done iterating
return res, nil
} else if err != nil {
// An error happened
return nil, err
}
res = append(res, s.Name)
}
panic("unreachable")
}
If you do not have too many numbers of rows, you can probably retrieve all rows and store them in a slice. Then you can sort those entries in RAM by implementing the sort.Interface and calling the sort.Sort function. Take a look at the source of sort.IntSlice if you need an example for that.
The tricky part is probably defining the alphanumeric sort order. I don't know the exact definition of it (and I wasn't able to look it up in this short amount of time), but I have tried to implement it anyway. Here is the code that you might use for the less method:
package main
import "log"
func less(a, b string) bool {
i, j := 0, 0
for i < len(a) && j < len(b) {
numeric, numA, numB := false, 0, 0
for i < len(a) && a[i] >= '0' && a[i] <= '9' {
numA = numA*10 + int(a[i]) - '0'
numeric = true
i++
}
for j < len(b) && b[j] >= '0' && b[j] <= '9' {
numB = numB*10 + int(b[j]) - '0'
numeric = true
j++
}
if numeric {
if numA != numB {
return numA < numB
}
continue
}
if a[i] != b[j] {
return a[i] < b[j]
}
i++
j++
}
return i == len(a) && j != len(b)
}
var tests = []struct {
a, b string
r1, r2 bool
}{
{"bar", "foo", true, false},
{"foo100", "foo10", false, true},
{"foo100a", "foo100b", true, false},
{"foo", "foo", false, false},
{"100", "100", false, false},
{"foo5", "foo12", true, false},
{"foo5", "fo3", true, false},
{"foo", "foo8", true, false},
}
func main() {
for i := range tests {
if less(tests[i].a, tests[i].b) != tests[i].r1 {
log.Fatalf("test %d failed", i)
}
if less(tests[i].b, tests[i].a) != tests[i].r2 {
log.Fatalf("reverse test %d failed", i)
}
}
}
I'm not sure if the code is sufficient for you or if you need to handle more complex cases, but it might provide at least a good starting point for your own modifications.

Resources