mirror of
https://github.com/emirpasic/gods.git
synced 2026-06-27 18:06:24 +03:00
454720c27e
* Improved ArrayList performance by removing `size` field.
Removed the `size` field from the `List` struct and replaced it with the built-in Go slice length implementation.
+ Achieved an average reduction of nearly 40% in execution time for `ArrayListGet`.
+ Achieved an average reduction of nearly 23% in memory usage for `ArrayListAdd`.
+ However, this change slightly increased the execution time for `ArrayListAdd` by 2.7%.
```
goos: linux
goarch: amd64
pkg: github.com/emirpasic/gods/v2/lists/arraylist
cpu: Intel(R) Core(TM) i7-4790 CPU @ 3.60GHz
│ old.txt │ new.txt │
│ sec/op │ sec/op vs base │
ArrayListGet100-8 51.20n ± 0% 32.49n ± 0% -36.53% (n=50)
ArrayListGet1000-8 447.5n ± 0% 270.3n ± 1% -39.60% (n=50)
ArrayListGet10000-8 4.418µ ± 1% 2.540µ ± 0% -42.52% (n=50)
ArrayListGet100000-8 44.06µ ± 0% 25.15µ ± 0% -42.91% (n=50)
ArrayListAdd100-8 726.5n ± 1% 760.5n ± 0% +4.69% (p=0.000 n=50)
ArrayListAdd1000-8 7.437µ ± 2% 7.389µ ± 1% ~ (p=0.746 n=50)
ArrayListAdd10000-8 70.06µ ± 1% 74.34µ ± 1% +6.11% (p=0.000 n=50)
ArrayListAdd100000-8 740.2µ ± 1% 728.9µ ± 2% ~ (p=0.147 n=50)
ArrayListRemove100-8 233.8n ± 0% 233.9n ± 0% ~ (p=0.162 n=50)
ArrayListRemove1000-8 2.275µ ± 0% 2.276µ ± 0% ~ (p=0.452 n=50)
ArrayListRemove10000-8 22.75µ ± 0% 22.75µ ± 0% ~ (p=0.956 n=50)
ArrayListRemove100000-8 1.323m ± 1% 1.331m ± 1% ~ (p=0.120 n=50)
geomean 7.218µ 6.119µ -15.22%
│ old.txt │ new.txt │
│ B/op │ B/op vs base │
ArrayListGet100-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=50) ¹
ArrayListGet1000-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=50) ¹
ArrayListGet10000-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=50) ¹
ArrayListGet100000-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=50) ¹
ArrayListAdd100-8 2.987Ki ± 0% 2.087Ki ± 1% -30.14% (n=50)
ArrayListAdd1000-8 27.50Ki ± 1% 23.31Ki ± 2% -15.24% (p=0.000 n=50)
ArrayListAdd10000-8 293.9Ki ± 1% 204.7Ki ± 1% -30.36% (n=50)
ArrayListAdd100000-8 2.667Mi ± 1% 2.244Mi ± 12% -15.86% (p=0.000 n=50)
ArrayListRemove100-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=50) ¹
ArrayListRemove1000-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=50) ¹
ArrayListRemove10000-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=50) ¹
ArrayListRemove100000-8 453.5 ± 1% 457.5 ± 1% ~ (p=0.059 n=50)
geomean ² -8.38% ²
¹ all samples are equal
² summaries must be >0 to compute geomean
│ old.txt │ new.txt │
│ allocs/op │ allocs/op vs base │
ArrayListGet100-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=50) ¹
ArrayListGet1000-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=50) ¹
ArrayListGet10000-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=50) ¹
ArrayListGet100000-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=50) ¹
ArrayListAdd100-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=50) ¹
ArrayListAdd1000-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=50) ¹
ArrayListAdd10000-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=50) ¹
ArrayListAdd100000-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=50) ¹
ArrayListRemove100-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=50) ¹
ArrayListRemove1000-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=50) ¹
ArrayListRemove10000-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=50) ¹
ArrayListRemove100000-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=50) ¹
geomean ² +0.00% ²
¹ all samples are equal
² summaries must be >0 to compute geomean
```
* Improve ArrayList Remove() by `slices.Delete`
The performance of `slices.Delete()` is better
```
goos: linux
goarch: amd64
pkg: github.com/emirpasic/gods/v2/lists/arraylist
cpu: Intel(R) Core(TM) i7-4790 CPU @ 3.60GHz
│ old-remove.txt │ new-remove.txt │
│ sec/op │ sec/op vs base │
ArrayListRemove100-8 234.2n ± 1% 211.2n ± 2% -9.82% (p=0.000 n=10)
ArrayListRemove1000-8 2.293µ ± 1% 2.063µ ± 4% -10.05% (p=0.000 n=10)
ArrayListRemove10000-8 22.78µ ± 1% 20.53µ ± 2% -9.86% (p=0.000 n=10)
ArrayListRemove100000-8 1.318m ± 3% 1.279m ± 2% -2.96% (p=0.019 n=10)
geomean 11.27µ 10.34µ -8.22%
│ old-remove.txt │ new-remove.txt │
│ B/op │ B/op vs base │
ArrayListRemove100-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
ArrayListRemove1000-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
ArrayListRemove10000-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
ArrayListRemove100000-8 452.0 ± 4% 444.5 ± 1% ~ (p=0.224 n=10)
geomean ² -0.42% ²
¹ all samples are equal
² summaries must be >0 to compute geomean
│ old-remove.txt │ new-remove.txt │
│ allocs/op │ allocs/op vs base │
ArrayListRemove100-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
ArrayListRemove1000-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
ArrayListRemove10000-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
ArrayListRemove100000-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹
geomean ² +0.00% ²
¹ all samples are equal
² summaries must be >0 to compute geomean
```
* Improve ArrayList Clear() by using built-in clear() instead of creating a new one
* Refactor code by `slices` package.
* Refactor ArrayList Insert() by `slices.Insert()`
208 lines
5.5 KiB
Go
208 lines
5.5 KiB
Go
// Copyright (c) 2015, Emir Pasic. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
// Package arraylist implements the array list.
|
|
//
|
|
// Structure is not thread safe.
|
|
//
|
|
// Reference: https://en.wikipedia.org/wiki/List_%28abstract_data_type%29
|
|
package arraylist
|
|
|
|
import (
|
|
"fmt"
|
|
"slices"
|
|
"strings"
|
|
|
|
"github.com/emirpasic/gods/v2/lists"
|
|
"github.com/emirpasic/gods/v2/utils"
|
|
)
|
|
|
|
// Assert List implementation
|
|
var _ lists.List[int] = (*List[int])(nil)
|
|
|
|
// List holds the elements in a slice
|
|
type List[T comparable] struct {
|
|
elements []T
|
|
}
|
|
|
|
const (
|
|
growthFactor = float32(2.0) // growth by 100%
|
|
shrinkFactor = float32(0.25) // shrink when size is 25% of capacity (0 means never shrink)
|
|
)
|
|
|
|
// New instantiates a new list and adds the passed values, if any, to the list
|
|
func New[T comparable](values ...T) *List[T] {
|
|
list := &List[T]{}
|
|
if len(values) > 0 {
|
|
list.Add(values...)
|
|
}
|
|
return list
|
|
}
|
|
|
|
// Add appends a value at the end of the list
|
|
func (list *List[T]) Add(values ...T) {
|
|
l := len(list.elements)
|
|
list.growBy(len(values))
|
|
for i := range values {
|
|
list.elements[l+i] = values[i]
|
|
}
|
|
}
|
|
|
|
// Get returns the element at index.
|
|
// Second return parameter is true if index is within bounds of the array and array is not empty, otherwise false.
|
|
func (list *List[T]) Get(index int) (T, bool) {
|
|
|
|
if !list.withinRange(index) {
|
|
var t T
|
|
return t, false
|
|
}
|
|
|
|
return list.elements[index], true
|
|
}
|
|
|
|
// Remove removes the element at the given index from the list.
|
|
func (list *List[T]) Remove(index int) {
|
|
|
|
if !list.withinRange(index) {
|
|
return
|
|
}
|
|
|
|
list.elements = slices.Delete(list.elements, index, index+1)
|
|
list.shrink()
|
|
}
|
|
|
|
// Contains checks if elements (one or more) are present in the set.
|
|
// All elements have to be present in the set for the method to return true.
|
|
// Performance time complexity of n^2.
|
|
// Returns true if no arguments are passed at all, i.e. set is always super-set of empty set.
|
|
func (list *List[T]) Contains(values ...T) bool {
|
|
for _, searchValue := range values {
|
|
if !slices.Contains(list.elements, searchValue) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// Values returns all elements in the list.
|
|
func (list *List[T]) Values() []T {
|
|
return slices.Clone(list.elements)
|
|
}
|
|
|
|
// IndexOf returns index of provided element
|
|
func (list *List[T]) IndexOf(value T) int {
|
|
return slices.Index(list.elements, value)
|
|
}
|
|
|
|
// Empty returns true if list does not contain any elements.
|
|
func (list *List[T]) Empty() bool {
|
|
return len(list.elements) == 0
|
|
}
|
|
|
|
// Size returns number of elements within the list.
|
|
func (list *List[T]) Size() int {
|
|
return len(list.elements)
|
|
}
|
|
|
|
// Clear removes all elements from the list.
|
|
func (list *List[T]) Clear() {
|
|
clear(list.elements[:cap(list.elements)])
|
|
list.elements = list.elements[:0]
|
|
}
|
|
|
|
// Sort sorts values (in-place) using.
|
|
func (list *List[T]) Sort(comparator utils.Comparator[T]) {
|
|
if len(list.elements) < 2 {
|
|
return
|
|
}
|
|
slices.SortFunc(list.elements, comparator)
|
|
}
|
|
|
|
// Swap swaps the two values at the specified positions.
|
|
func (list *List[T]) Swap(i, j int) {
|
|
if list.withinRange(i) && list.withinRange(j) {
|
|
list.elements[i], list.elements[j] = list.elements[j], list.elements[i]
|
|
}
|
|
}
|
|
|
|
// Insert inserts values at specified index position shifting the value at that position (if any) and any subsequent elements to the right.
|
|
// Does not do anything if position is negative or bigger than list's size
|
|
// Note: position equal to list's size is valid, i.e. append.
|
|
func (list *List[T]) Insert(index int, values ...T) {
|
|
if !list.withinRange(index) {
|
|
// Append
|
|
if index == len(list.elements) {
|
|
list.Add(values...)
|
|
}
|
|
return
|
|
}
|
|
|
|
l := len(list.elements)
|
|
list.growBy(len(values))
|
|
list.elements = slices.Insert(list.elements[:l], index, values...)
|
|
}
|
|
|
|
// Set the value at specified index
|
|
// Does not do anything if position is negative or bigger than list's size
|
|
// Note: position equal to list's size is valid, i.e. append.
|
|
func (list *List[T]) Set(index int, value T) {
|
|
|
|
if !list.withinRange(index) {
|
|
// Append
|
|
if index == len(list.elements) {
|
|
list.Add(value)
|
|
}
|
|
return
|
|
}
|
|
|
|
list.elements[index] = value
|
|
}
|
|
|
|
// String returns a string representation of container
|
|
func (list *List[T]) String() string {
|
|
str := "ArrayList\n"
|
|
values := make([]string, 0, len(list.elements))
|
|
for _, value := range list.elements {
|
|
values = append(values, fmt.Sprintf("%v", value))
|
|
}
|
|
str += strings.Join(values, ", ")
|
|
return str
|
|
}
|
|
|
|
// Check that the index is within bounds of the list
|
|
func (list *List[T]) withinRange(index int) bool {
|
|
return index >= 0 && index < len(list.elements)
|
|
}
|
|
|
|
func (list *List[T]) resize(len, cap int) {
|
|
newElements := make([]T, len, cap)
|
|
copy(newElements, list.elements)
|
|
list.elements = newElements
|
|
}
|
|
|
|
// Expand the array if necessary, i.e. capacity will be reached if we add n elements
|
|
func (list *List[T]) growBy(n int) {
|
|
// When capacity is reached, grow by a factor of growthFactor and add number of elements
|
|
currentCapacity := cap(list.elements)
|
|
|
|
if newLength := len(list.elements) + n; newLength >= currentCapacity {
|
|
newCapacity := int(growthFactor * float32(currentCapacity+n))
|
|
list.resize(newLength, newCapacity)
|
|
} else {
|
|
list.elements = list.elements[:newLength]
|
|
}
|
|
}
|
|
|
|
// Shrink the array if necessary, i.e. when size is shrinkFactor percent of current capacity
|
|
func (list *List[T]) shrink() {
|
|
if shrinkFactor == 0.0 {
|
|
return
|
|
}
|
|
// Shrink when size is at shrinkFactor * capacity
|
|
currentCapacity := cap(list.elements)
|
|
if len(list.elements) <= int(float32(currentCapacity)*shrinkFactor) {
|
|
list.resize(len(list.elements), len(list.elements))
|
|
}
|
|
}
|