atc0005 / learn

Various topics that I want to learn more about, either for professional development or for my own use
0 stars 0 forks source link

Golang | Slices | Indexing / Subsets #19

Open atc0005 opened 5 years ago

atc0005 commented 5 years ago

Working through the Slice Subsets portion of Cory LaNou's Getting Started with Go O'Reilly course and having some trouble wrapping my head around the slicing syntax.

The guide for the syntax was given as:

// slice[starting_index : (starting_index + length)]

The code example:

package main

import "fmt"

// slice[starting_index : (starting_index + length)]

func main() {

    names := []string{"John", "Paul", "George", "Ringo"}

    fmt.Println(names) // [John Paul George Ringo]

    // Get 2 elements starting with the second element (index 1)
    fmt.Println(names[1:3]) // [Paul George] - names[1:1+2]

    // functionally equivalent
    fmt.Println(names[2:len(names)]) // [George Ringo]
    fmt.Println(names[2:])           // [George Ringo]

    // functionally equivalent
    fmt.Println(names[0:2])         // [John Paul]
    fmt.Println(names[:2])          // [John Paul]
}

Go Playground: https://play.golang.org/p/KqfpsxZzQIO

atc0005 commented 5 years ago

The most known form of slice expression is:

input[low:high]

Indices low and high must be integers. They specify which elements of the operand (input) are placed inside the resulting slice or string. The result contains operand’s elements starting at low (inclusively) up to high (exclusively). Operand is either string, array, pointer to array or slice

atc0005 commented 5 years ago

https://www.packtpub.com/free-ebooks/learning-go-programming

The slicing expression uses the [ : ] operator to specify the low and high bound indices, separated by a colon, for the slice segment.

  • The low value is the zero-based index where the slice segment starts
  • The high value is the n th element offset where the segment stops
atc0005 commented 5 years ago

https://blog.golang.org/go-slices-usage-and-internals

The start and end indices of a slice expression are optional; they default to zero and the slice's length respectively

the expression b[1:4] creates a slice including elements 1 through 3 of b (the indices of the resulting slice will be 0 through 2).

atc0005 commented 5 years ago

http://shop.oreilly.com/product/0636920046516.do

Another way to create slices is to use the [low : high] expression:

arr := [5]float64{1, 2, 3, 4, 5} 
x := arr[0:5] 

low is the index of where to start the slice and high is the index of where to end it (but not including the index itself).

For example, while arr[0:5] returns [1,2,3,4,5] , arr[1:4] returns [2,3,4] .

atc0005 commented 5 years ago

https://www.manning.com/books/get-programming-with-go

Slicing is expressed with a half-open range. For example, in the following listing, planets[0:4] begins with the planet at index 0 and continues up to, but not including, the planet at index 4.

atc0005 commented 5 years ago

https://tour.golang.org/moretypes/7

A slice is formed by specifying two indices, a low and high bound, separated by a colon:

a[low : high]

This selects a half-open range which includes the first element, but excludes the last one.

The following expression creates a slice which includes elements 1 through 3 of a:

a[1:4]

atc0005 commented 4 years ago

From the Go Fundamentals course by Nigel Poulton:

package main

import (
    "fmt"
)

func main() {
    mySlice := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

    // this was a "gotcha" shown in the course
    fmt.Println(mySlice[4])

    mySlice[1] = 0
    fmt.Println(mySlice)

    // Golang slices
    // Left side: inclusive
    // Right side: exclusive
    sliceOfSlice := mySlice[2:5]
    fmt.Println(sliceOfSlice)

}
atc0005 commented 4 years ago

From the https://www.udemy.com/course/go-the-complete-developers-guide course (paraphrased with Go code):

// https://play.golang.org/p/FIsOa4bRRXR

fruits := []string{"apple", "banana", "grape", "orange"}

// fruits[startIndexIncluding : upToNotIncluding]
fmt.Println(fruits[0:2])

Output:

[apple banana]
atc0005 commented 3 years ago

This scratch code is based off of https://github.com/mactsouk/mastering-Go-3rd/blob/9329a87dd5afc3878831e3a98739fa1c8c361cd5/ch03/phoneBook.go and attempts to reason why the starting or lower bound can be equal to or greater than the length of the slice:

package main

import (
    "fmt"
)

func main() {

    i := 2

    data := []string{
        "first",  // index 0
        "second", // index 1
        "last",   // index 2
    }

    fmt.Println("cap:", cap(data))
    fmt.Println("len:", len(data))

    fmt.Println("i:", i)
    fmt.Println("data[:i]:", data[:i])
    fmt.Println("data[i+1:]:", data[i+1:])

    // Everything up to this makes sense, but not the following. Why are you
    // able to specify a starting index one past the length of the slice
    // without running into an index error?
    fmt.Println("Start with index 3, one past slice length:")

    fmt.Println("data[3:]:", data[3:]) // this works
    // fmt.Println(data[3])               // this panics

    // remove the slice element at specified index
    // data = append(data[:i], data[i+1:]...)
    data = append(
        data[:2],    // [first second]
        data[3:]..., // []
    )

    // show results
    fmt.Println(data)

}

and is explained by https://stackoverflow.com/questions/45299185/slice-start-position-greater-than-length-of-the-string:

Since you are slicing a string, indices are in range if:

0 <= low <= high <= len(a)

This expression:

"hi"[2:]

Since the upper bound is missing, it defaults to length, which is 2, so it is equvivalent to:

"hi"[2:2]

This is perfectly valid by the spec, and it will result in an empty string. If you change it to "hi"[3:], then it will be out of range and result in a compile-time error (as slicing a constant string can be checked at compile-time).

Reasoning is that the upper bound is exclusive, e.g. a[0:0] is valid and will be 0-length, a[0:1] will have a length of 1, a[0:len(a)] valid and will have the same length as a.

In case of slices, the lower bound can even be greater than the slice length (but must not exceed the slice capacity). For more details, see Slicing: Out of bounds error in Go.

To add extra emphasis:

In case of slices, the lower bound can even be greater than the slice length (but must not exceed the slice capacity). For more details, see Slicing: Out of bounds error in Go.

and from that link:

Foreword: The question is good, the source of downvotes is because at first glance the question does seem like the asker simply doesn't know what the valid bounds are when slicing. But the case is uncommon! It is very rare that someone slices a slice in a way that even the lower bound is beyond the length of the slice!


In short: The problem is not with the lower bound which can be equal to or greater than len() (the upper limit is dictated by cap() in case of slices). The problem is with the higher bound: it must be greater than or equal to the lower bound. And since you didn't specify the higher bound, it defaults to len() (and not to cap()!) which is 0. And 1 is not less than or equal to 0.

Spec: Slice expressions:

For arrays or strings, the indices are in range if 0 <= low <= high <= len(a), otherwise they are out of range. For slices, the upper index bound is the slice capacity cap(a) rather than the length.

Since you are slicing a slice, indices are in range if:

0 <= low <= high <= cap(a)

So this line:

c := b[1:]

Is invalid, because:

A missing low index defaults to zero; a missing high index defaults to the length of the sliced operand.

So in your case low = 1 and high = 0 (implicit), which does not satisfy:

0 <= low <= high <= cap(a)

So for example the following expressions are valid:

c := b[1:1]        // c len=0 cap=4 []
c := b[1:2]        // c len=1 cap=4 [0]
c := b[1:cap(b)]   // c len=4 cap=4 [0 0 0 0]
atc0005 commented 3 years ago

This scratch code is based off of https://github.com/mactsouk/mastering-Go-3rd/blob/9329a87dd5afc3878831e3a98739fa1c8c361cd5/ch03/phoneBook.go and attempts to reason why the starting or lower bound can be equal to or greater than the length of the slice

Ultimately, I was attempting to understand why this works:

https://github.com/mactsouk/mastering-Go-3rd/blob/9329a87dd5afc3878831e3a98739fa1c8c361cd5/ch03/phoneBook.go#L112-L126

func deleteEntry(key string) error {
    i, ok := index[key]
    if !ok {
        return fmt.Errorf("%s cannot be found!", key)
    }
    data = append(data[:i], data[i+1:]...)
    // Update the index - key does not exist any more
    delete(index, key)

    err := saveCSVFile(CSVFILE)
    if err != nil {
        return err
    }
    return nil
}

more specifically:

    data = append(data[:i], data[i+1:]...)

The goal is to retain the slice elements before/after the specific index value i. This confused me if we're working with a slice like this one:

    data := []string{
        "first",  // index 0
        "second", // index 1
        "last",   // index 2
    }

where the length of the slice is 3 (index positions 0, 1 & 2), but using a slice of expression of data[3:] uses an index value one past the max index value of 2.

As noted in the last post to this issue, the lower bound index value can be equal to or greater than the value of len(data), provided it is lower than cap(data). This was (and still is) surprising, but I can see how the property is useful for slicing operations like this in order to exclude elements from a new slice.

atc0005 commented 3 years ago

As noted in the last post to this issue, the lower bound index value can be equal to or greater than the value of len(data), provided it is lower than cap(data). This was (and still is) surprising, but I can see how the property is useful for slicing operations like this in order to exclude elements from a new slice.

As noted by others, you still have to guard against empty (and presumably nil) slices.

This attempt to use range syntax on an empty slice:

package main

import (
    "fmt"
)

func main() {
    myarr := []int{}

    for _, v := range myarr[1:] {
        fmt.Println(v)
    }
}

panics with this output:

panic: runtime error: slice bounds out of range [1:0]

goroutine 1 [running]:
main.main()
    /tmp/sandbox3034326152/prog.go:10 +0x1a

Program exited: status 2.

One workaround mentioned (to safely skip first element in a slice):

for i := 1; i < len(Arr); i++ {
    val = Arr[i]
    // Do something
}

refs: