Увеличение размера среза, чтобы предотвратить ошибку «Границы среза вне диапазона»

#go

#Вперед

Вопрос:

Я написал следующее:

 func main() {
    //inside main        
        fileInputBytes, err := ioutil.ReadFile("/tmp/test")
        byteSize2 := len(fileInputBytes)

        var inputFileByteSlice = fileInputBytes[0:]
        var numberOfIndexes = math.Floor(float64(byteSize / indexingOffset))

        for i := 1; i <= int(numberOfIndexes); i   {
            // adding i to the indexer insures that we use lookahed to ignore previously inserted indexing values
            var v int = (i * indexingOffset)   i
            Insert(amp;inputFileByteSlice, v i, indexingByteValue)
            fmt.Println(i)
        }
    }
    //outside main
    //variation of https://blog.golang.org/slices with pointers and such
        func Insert(slice *[]byte, index int, value byte) {
            // Grow the slice by one element.
            (*slice) = (*slice)[0 : len(*slice) 1]
            // Use copy to move the upper part of the slice out of the way and open a hole.
            copy((*slice)[index 1:], (*slice)[index:])
            // Store the new value.
            (*slice)[index] = value
            // Return the result.
        }
  

slice bounds out of range Ошибка действует мне на нервы. Длина среза увеличивается за пределы размера и переполняется, причина, по которой я не понимаю, заключается в том, что я думал, что вызов «увеличить» срез на один элемент (перед копированием) будет динамически выделять больше места. Поскольку это не так, может ли кто-нибудь предложить мне лучшее предложение?

Комментарии:

1. Вы преобразуете an int в a float64 , затем используете math.Floor то, что уже является четным целочисленным значением, а затем преобразуете его обратно в an int . Ничего из этого не делает ничего полезного, просто используйте исходный результат целочисленного деления.

2. @JimB Хороший улов, это было довольно любительски с моей стороны

Ответ №1:

Прежде всего, фрагмент уже является ссылочным типом. Таким образом, вам не нужно передавать его указатель, если вы не собираетесь изменять его емкость. Таким образом, ваш main можно упростить как:

 func main() {
    fileInputBytes, err := ioutil.ReadFile("/tmp/test")
    byteSize2 := len(fileInputBytes)

    // No need to use pointer to slice. If you want a brand new slice
    // that does not affect the original slice values, use copy()
    inputFileByteArray := fileInputBytes
    var numberOfIndexes = math.Floor(float64(byteSize / indexingOffset))

    for i := 1; i <= int(numberOfIndexes); i   {
        var v int = (i * indexingOffset)   i

        // Insert needs to return the newly updated slice reference
        // which should be assigned in each iteration.
        inputFileByteArray = Insert(inputFileByteArray, v i, indexingByteValue)
        fmt.Println(i)
    }
}
  

Затем Insert функцию можно упростить, просто используя append along with copy и возвращая вновь созданный фрагмент:

 func Insert(slice []byte, index int, value byte) []byte {
    if index >= len(slice) {
        // add to the end of slice in case of index >= len(slice)
        return append(slice, value)
    }
    tmp := make([]byte, len(slice[:index   1]))
    copy(tmp, slice[:index])
    tmp[index] = value
    return append(tmp, slice[index:]...)
}
  

Возможно, это не лучшая реализация, но она достаточно проста. Пример использования в: https://play.golang.org/p/Nuq4RX9XQD

Комментарии:

1. Вам не нужно создавать временный фрагмент, документированный метод вставки фрагмента подходит (вы можете сохранить короткое замыкание, если хотите). Я также не стал бы принимать недопустимое значение индекса и возвращать неверный результат. Индексация вне диапазона должна вызывать панику или, по крайней мере, выдавать ошибку, а не автоматически создавать неправильный срез. play.golang.org/p/dLeNTmYKM7

2. @JimB можете ли вы объяснить, почему моя первоначальная попытка «увеличить» фрагмент не заключалась в создании экземпляра нового фрагмента с большим объемом памяти?

3. @abhink Причина, по которой я использовал указатели на исходный фрагмент, заключалась в том, что я хотел ограничить объем памяти. Как вы думаете, можно ли в любом случае расширять без копирования на новый срез каждый раз? Кроме того, JimB заявил, что вы можете обойтись без объявления tmp. Если это правда, я предполагаю, что было бы целесообразно просто объявить: copy(tmp, slice[:index 1]) ?

4. @Rice Поскольку вы выполняете случайные вставки в свой массив, это всегда будет линейное время. Поскольку массивы (или фрагменты здесь) представляют собой непрерывные блоки памяти, вы не можете вставить что-либо в середину, не переместив каждый последующий элемент массива на одно место. Поэтому потребуется обширное копирование. Если вам не нужен произвольный доступ (т.Е. Доступ к элементам фрагмента всегда линейный, от начала до конца), вы можете попробовать использовать структуры типа связанного списка. Кроме того, ответ Джимба похож на мой, но лучше реализован, поэтому используйте его, если нужно.

5. @abhink Большое вам спасибо за приведенный пример / объяснение. Очень полезно

Ответ №2:

Ваша функция работает только в том случае, если у фрагмента достаточно начальной емкости. Если вам нужна дополнительная емкость, вы можете «увеличить» фрагмент только с помощью append функции. Вы все равно можете использовать аргумент *[]byte указателя для изменения фрагмента на месте следующим образом:

 func Insert(slice *[]byte, index int, value byte) {
    *slice = append(*slice, 0)
    copy((*slice)[index 1:], (*slice)[index:])
    (*slice)[index] = value
}
  

Однако более привычно возвращать новое значение фрагмента и переназначать его каждый раз. Это дает вам сигнатуру функции, аналогичную встроенной append .

 func Insert(slice []byte, index int, value byte) []byte {
    slice = append(slice, 0)
    copy(slice[index 1:], slice[index:])
    slice[index] = value
    return slice
}
  

Комментарии:

1. По вашей логике (во второй функции, которую вы написали) объем памяти также увеличивается за счет использования append ?

2. @Rice: Я не уверен, что вы имеете в виду. Эти 2 функции в основном идентичны и отличаются только тем, как вы их вызываете.

3. Извините, мне нужно быть более конкретным. Моей простой потребностью (изначально) было увеличить фактический объем памяти фрагмента, а не только длину. Будет ли append выполнять эту работу?

4. @Rice: это то, что делает append . Вы не можете увеличить длину за пределы емкости, не увеличивая емкость.