Удалить из среза на месте в Голанге - PullRequest
1 голос
/ 31 мая 2019

У меня есть следующий тест, который печатает оригинальный входной срез (после фильтрации) без удаленного элемента, но с дополнительным элементом в конце, делающим входной срез такой же длины, даже если после фильтрации оно должно быть короче.

Я прошел этот документ https://github.com/golang/go/wiki/SliceTricks#delete Однако я думаю, что мне не хватает некоторых ошибок в Go, потому что кажется, что я использую срезы с неправильным подходом.

  • как мне избежать "выходного среза"? (который напечатан правильно, содержит правильные элементы, имеющие ожидаемую длину и емкость)
  • почему моя попытка "удаления на месте" приводит к тому, что "входной срез" имеет ту же длину, что и до процесса фильтрации?
  • почему «входной срез» имеет ту же длину, что и до применения процесса фильтрации? Как сделать операцию удаления, чтобы изменить длину «входного среза»?

Это код:

package foo

import (
    "fmt"
    "log"
    "math/rand"
    "testing"
)

type FooItem struct {
    Id       int
    Category string
    Value    float64
}

const minRand = 0
const maxRand = 10

const maxSliceLen = 3

var inFooSlice []FooItem

func init() {
    for i := 1; i <= maxSliceLen; i++ {
        inFooSlice = append(inFooSlice, FooItem{
            Id:       i,
            Category: "FooCat",
            Value:    minRand + rand.Float64()*(maxRand-minRand),
        })
    }
}

// this is the function I am testing
func FindAndRemoveFromFooSlice(iFilter int, inSl []FooItem) (*FooItem, []FooItem) {

    inLen := len(inSl)
    outSl := make([]FooItem, inLen)

    for idx, elem := range inSl {
        if elem.Id == iFilter {
            log.Printf("Loop ID %v", idx)

            // check these docs: https://github.com/golang/go/wiki/SliceTricks#delete
            outSl = inSl[:idx+copy(inSl[idx:], inSl[idx+1:inLen])]
            outSl = outSl[:inLen-1]

            return &elem, outSl
        }
    }
    return nil, nil
}

func TestFoo(t *testing.T) {
    fmt.Printf("\nOriginal (PRE) slice\n")
    fmt.Println(inFooSlice)
    fmt.Println(len(inFooSlice))
    fmt.Println(cap(inFooSlice))

    idFilter := 1

    fePtr, outFooSlice := FindAndRemoveFromFooSlice(idFilter, inFooSlice)

    fmt.Printf("\nOriginal (POST) slice\n")
    fmt.Println(inFooSlice)
    fmt.Println(len(inFooSlice))
    fmt.Println(cap(inFooSlice))

    fmt.Printf("\nFiltered element\n")
    fmt.Println(*fePtr)

    fmt.Printf("\nOutput slice\n")
    fmt.Println(outFooSlice)
    fmt.Println(len(outFooSlice))
    fmt.Println(cap(outFooSlice))
}

Это результат выполнения теста:

$ go test -v -run TestFoo
=== RUN   TestFoo

Original (PRE) slice
[{1 FooCat 6.046602879796196} {2 FooCat 9.405090880450125} {3 FooCat 6.645600532184904}]
3
4
2019/05/31 12:53:30 Loop ID 0

Original (POST) slice
[{2 FooCat 9.405090880450125} {3 FooCat 6.645600532184904} {3 FooCat 6.645600532184904}]
3
4

Filtered element
{1 FooCat 6.046602879796196}

Output slice
[{2 FooCat 9.405090880450125} {3 FooCat 6.645600532184904}]
2
4
--- PASS: TestFoo (0.00s)
PASS
ok      git.openenergi.net/scm/flex/service/common  0.008s

Обновление «входной слайс как указатель»

ОК, при условии, что я хотел бы иметь дело с исходным входным срезом, т.е. без копирования или выходного среза.

  • Почему следующий код вызывает панику во время выполнения в закомментированной строке кода? (pointedInSl[inLen-1] = FooItem{})
  • Почему напечатанный фрагмент (после применения функции) содержит 2 одинаковых символа в конце? Как мне удалить последний лишний элемент?
  • Почему длина среза после применения функции остается такой же, как у среза до применения функции?
  • Как можно уменьшить исходный фрагмент на 1 (т. Е. Иметь выходную длину = исходную длину - 1)?

Это код:

func FindAndRemoveFromFooSliceInPlace(iFilter int, inSl *[]FooItem) *FooItem {
    pointedInSl := *inSl
    inLen := len(pointedInSl)
    for idx, elem := range pointedInSl {
        if elem.Id == iFilter {
            log.Printf("Loop ID %v", idx)

            // check these docs: https://github.com/golang/go/wiki/SliceTricks#delete
            pointedInSl = append(pointedInSl[:idx], pointedInSl[idx+1:inLen]...)
            // pointedInSl[inLen-1] = FooItem{} // why this throws a runtime "panic: runtime error: index out of range" ???
            pointedInSl = pointedInSl[:inLen-1]

            return &elem
        }
    }
    return nil
}

func TestFooInPlace(t *testing.T) {
    fmt.Printf("\nOriginal (PRE) slice\n")
    fmt.Println(inFooSlice)
    fmt.Println(len(inFooSlice))
    fmt.Println(cap(inFooSlice))

    idFilter := 1

    fePtr := FindAndRemoveFromFooSliceInPlace(idFilter, &inFooSlice)

    fmt.Printf("\nOriginal (POST) slice\n")
    fmt.Println(inFooSlice)
    fmt.Println(len(inFooSlice))
    fmt.Println(cap(inFooSlice))

    fmt.Printf("\nFiltered element\n")
    fmt.Println(*fePtr)
}

Это странный вывод:

$ go test -v -run TestFooInPlace
=== RUN   TestFooInPlace

Original (PRE) slice
[{1 FooCat 6.046602879796196} {2 FooCat 9.405090880450125} {3 FooCat 6.645600532184904}]
3
4
2019/05/31 16:32:38 Loop ID 0

Original (POST) slice
[{2 FooCat 9.405090880450125} {3 FooCat 6.645600532184904} {3 FooCat 6.645600532184904}]
3
4

Filtered element
{1 FooCat 6.046602879796196}
--- PASS: TestFooInPlace (0.00s)
PASS
ok      git.openenergi.net/scm/flex/service/common  0.007s

Ответы [ 2 ]

4 голосов
/ 31 мая 2019

Если у вас есть переменная типа int, и вы хотите написать функцию, которая увеличивает ее значение, как вы это делаете?Вы либо передаете указатель на переменную, либо возвращаете увеличенное значение, которое необходимо присвоить исходной переменной.

Например (попробуйте на Go Playground ):

func inc(i int) int { i++; return i }

var i int = 2
inc(i)
fmt.Println(i) // This will be 2

В приведенном выше коде вы передаете i в inc(), который увеличивает его и возвращает его значение.Оригинал i, конечно, не изменится, i внутри inc() - это просто копия, независимая от оригинала i.Чтобы оригинал изменился, вам нужно указать возвращаемое значение:

i = inc(i)

Или использовать указатели в первую очередь (попробуйте на Go Playground ):

func inc(i *int) { *i++ }

var i int = 2
inc(&i)
fmt.Println(i) // This will be 3

То же самое относится и к ломтикам.Если вы хотите / должны изменить заголовок слайса (который является указателем данных, длиной и емкостью, см. reflect.SliceHeader), вы должны либо передать указатель на этот слайс (не очень часто), либоВы должны вернуть измененный новый заголовок среза, который вы должны назначить вызывающей стороне.Это решение используется чаще, таков подход, которому следует встроенный append().

Когда вы срезаете срез (например, someslice[min:max]), новый срез будет разделенрезервный массив с оригинальным.Это означает, что если вы измените элементы нового среза, оригинал также будет наблюдать эти изменения.Таким образом, если вы удаляете элемент из нового слайса и копируете элементы на место удаленного элемента, последний элемент исходного слайса все равно будет там, который «покрыт» исходным слайсом.Обычной практикой является обнуление последнего элемента, чтобы сборщик мусора мог восстановить свою память, если это будет указатель типа (или «подобный», как фрагмент, карта или канал).Подробнее см. Утечка памяти в срезе golang и Идет ли сбор мусора на части срезов?

Чтобы ответить на ваши вопросы напрямую:

  • как мне избежать "выходного среза"?(который напечатан правильным образом, содержит правильные элементы, с ожидаемой длиной и емкостью)

Как указано в этом ответе: вам нужно будет передать указатель на ваш срез,и измените указанное значение в FindAndRemoveFromFooSlice(), так что вам не нужно возвращать новый фрагмент.

  • , почему моя попытка «удалить на месте» приводит к «входному слайсу»"с той же самой длиной, которая была до процесса фильтрации?

Вы никогда не изменяли исходный фрагмент, вы передавали его, чтобы сделать копию, и внутри FindAndRemoveFromFooSlice() вы можете изменять толькокопия (но вы даже не изменили копию).Вы возвращаете новый фрагмент, но не назначаете его, поэтому исходный фрагмент (заголовок) не поврежден.

  • , почему «входной фрагмент» имеет ту же длину, что и раньшеприменять процесс фильтрации?Как сделать операцию удаления, чтобы изменить длину «входного среза»?

Ответ на предыдущие 2 вопроса.

См. Связанныйвопросы:

Проходят ли срезы Голанга по значению?

срез по сравнению с картой, которая будет использоваться в параметре

0 голосов
/ 04 июня 2019

Я предложил отредактировать ответ icza, чтобы предоставить в качестве примера минимальный рабочий код для полезной информации, которую он дал. Он был отклонен, заявив, что он не имеет смысла как редактирование и что он должен был быть написан как комментарий или ответ, так что вот оно (в основном, речь идет о icza):

Пример минимального рабочего кода (с комментариями, чтобы дать некоторый контекст):

// use a pointer for the input slice so then it is changed in-place
func FindAndRemoveFromFooSliceInPlace(iFilter int, inSl *[]FooItem) *FooItem {
    pointedInSl := *inSl // dereference the pointer so then we can use `append`
    inLen := len(pointedInSl)
    for idx, elem := range pointedInSl {
        if elem.Id == iFilter {
            log.Printf("Loop ID %v", idx)

            // check these docs: https://github.com/golang/go/wiki/SliceTricks#delete
            pointedInSl = append(pointedInSl[:idx], pointedInSl[idx+1:inLen]...)
            pointedInSl = pointedInSl[:inLen-1]
            *inSl = pointedInSl // assigning the new slice to the pointed value before returning

            return &elem
        }
    }
    return nil
}
...