在Go语言中寻找合理的堆栈实现吗?

huangapple go评论87阅读模式
英文:

Looking for reasonable stack implementation in golang?

问题

到目前为止,我的天真方法是

type stack []int

func (s *stack) Push(v int) {
    *s = append(*s, v)
}

func (s *stack) Pop() int {
    res := (*s)[len(*s)-1]
    *s = (*s)[:len(*s)-1]
    return res
}

它可以工作- playground,但看起来很丑并且有太多的解引用。我能做得更好吗?

英文:

So far my naive approach is

type stack []int

func (s *stack) Push(v int) {
	*s = append(*s, v)
}

func (s *stack) Pop() int {
	res:=(*s)[len(*s)-1]
	*s=(*s)[:len(*s)-1]
	return res
}

it works - playground, but looks ugly and has too much dereferencing. Can I do better?

答案1

得分: 91

这是一种风格和个人口味的问题,你的代码很好(除了不是线程安全的,并且在从空栈中弹出时会出现恐慌)。为了简化代码,你可以使用值方法并返回栈本身,这在某种程度上更加优雅(对某些人来说)。例如:

type stack []int

func (s stack) Push(v int) stack {
    return append(s, v)
}

func (s stack) Pop() (stack, int) {
    // FIXME: 但是如果栈为空怎么办?

    l := len(s)
    return  s[:l-1], s[l-1]
}


func main(){
    s := make(stack,0)
    s = s.Push(1)
    s = s.Push(2)
    s = s.Push(3)
    
    s, p := s.Pop()
    fmt.Println(p)
 
}

另一种方法是将其封装在一个结构体中,这样你还可以轻松地添加互斥锁以避免竞态条件等。代码如下:

type stack struct {
     lock sync.Mutex // 如果不需要线程安全,可以不加锁
     s []int
}

func NewStack() *stack {
    return &stack {sync.Mutex{}, make([]int,0), }
}

func (s *stack) Push(v int) {
    s.lock.Lock()
    defer s.lock.Unlock()
    
    s.s = append(s.s, v)
}

func (s *stack) Pop() (int, error) {
    s.lock.Lock()
    defer s.lock.Unlock()
    
    
    l := len(s.s)
    if l == 0 {
        return 0, errors.New("Empty Stack")
    }
    
    res := s.s[l-1]
    s.s = s.s[:l-1]
    return res, nil
}


func main(){
    s := NewStack()
    s.Push(1)
    s.Push(2)
    s.Push(3)
	fmt.Println(s.Pop())
    fmt.Println(s.Pop())
    fmt.Println(s.Pop())
}
英文:

It's a matter of style and personal taste, your code is fine (apart from not being thread safe and panicking if you pop from an empty stack). To simplify it a bit you can work with value methods and return the stack itself, it's slightly more elegant to some tastes. i.e.

type stack []int

func (s stack) Push(v int) stack {
    return append(s, v)
}

func (s stack) Pop() (stack, int) {
    // FIXME: What do we do if the stack is empty, though?

    l := len(s)
    return  s[:l-1], s[l-1]
}


func main(){
    s := make(stack,0)
    s = s.Push(1)
    s = s.Push(2)
    s = s.Push(3)
    
    s, p := s.Pop()
    fmt.Println(p)
 
}

Another approach is to wrap it in a struct, so you can also easily add a mutex to avoid race conditions, etc. something like:

type stack struct {
     lock sync.Mutex // you don't have to do this if you don't want thread safety
     s []int
}

func NewStack() *stack {
    return &stack {sync.Mutex{}, make([]int,0), }
}

func (s *stack) Push(v int) {
    s.lock.Lock()
    defer s.lock.Unlock()
    
    s.s = append(s.s, v)
}

func (s *stack) Pop() (int, error) {
    s.lock.Lock()
    defer s.lock.Unlock()
    
    
    l := len(s.s)
    if l == 0 {
        return 0, errors.New("Empty Stack")
    }
    
    res := s.s[l-1]
    s.s = s.s[:l-1]
    return res, nil
}


func main(){
    s := NewStack()
    s.Push(1)
    s.Push(2)
    s.Push(3)
	fmt.Println(s.Pop())
    fmt.Println(s.Pop())
    fmt.Println(s.Pop())
}

答案2

得分: 21

这是一个使用链式数据结构实现的后进先出(LIFO)的代码实现。

package stack

import "sync"

type element struct {
	data interface{}
	next *element
}

type stack struct {
	lock *sync.Mutex
	head *element
	Size int
}

func (stk *stack) Push(data interface{}) {
	stk.lock.Lock()

	element := new(element)
	element.data = data
	temp := stk.head
	element.next = temp
	stk.head = element
	stk.Size++

	stk.lock.Unlock()
}

func (stk *stack) Pop() interface{} {
	if stk.head == nil {
		return nil
	}
	stk.lock.Lock()
	r := stk.head.data
	stk.head = stk.head.next
	stk.Size--

	stk.lock.Unlock()

	return r
}

func New() *stack {
	stk := new(stack)
	stk.lock = &sync.Mutex{}

	return stk
}

这段代码实现了一个栈(stack)数据结构,使用链式数据结构实现了后进先出(LIFO)的特性。其中,Push函数用于将元素压入栈中,Pop函数用于从栈中弹出元素。New函数用于创建一个新的栈对象。

英文:

Here is a LIFO implementation using linked data structure

package stack

import "sync"

type element struct {
	data interface{}
	next *element
}

type stack struct {
	lock *sync.Mutex
	head *element
	Size int
}

func (stk *stack) Push(data interface{}) {
	stk.lock.Lock()

	element := new(element)
	element.data = data
	temp := stk.head
	element.next = temp
	stk.head = element
	stk.Size++

	stk.lock.Unlock()
}

func (stk *stack) Pop() interface{} {
	if stk.head == nil {
		return nil
	}
   	stk.lock.Lock()
	r := stk.head.data
	stk.head = stk.head.next
	stk.Size--

	stk.lock.Unlock()

	return r
}

func New() *stack {
	stk := new(stack)
	stk.lock = &sync.Mutex{}

	return stk
}

答案3

得分: 6

我相信如果我们能够利用Go的标准库而不是像@guy_fawkes那样创建自定义数据结构,会更好。尽管他做得很好,但使用单链表不是一种标准的方式。

为了在O(1)的时间复杂度内获取链表的尾部,我会使用双向链表。我用空间换取时间。

我还采纳了@Not_a_Golfer的建议,在栈中添加了一个锁。

import (
	"container/list"
	"sync"
)

type Stack struct {
	dll   *list.List
	mutex sync.Mutex
}

func NewStack() *Stack {
	return &Stack{dll: list.New()}
}

func (s *Stack) Push(x interface{}) {
	s.mutex.Lock()
	defer s.mutex.Unlock()

	s.dll.PushBack(x)
}

func (s *Stack) Pop() interface{} {
	s.mutex.Lock()
	defer s.mutex.Unlock()

	if s.dll.Len() == 0 {
		return nil
	}
	tail := s.dll.Back()
	val := tail.Value
	s.dll.Remove(tail)
	return val
}

点击这个playground预览结果。

英文:

I believe it would be nice if we could take advantage of Go's standard libs instead of creating self-defined data structure like @guy_fawkes did. Although he did a great job, it's not a standard way to use singly linked list.

To get list's tail in O(1) time complexity, I would use doubly linked list. I trade space for time.

I also take @Not_a_Golfer's advice to add a lock to the stack.

import (
	"container/list"
	"sync"
)

type Stack struct {
	dll   *list.List
	mutex sync.Mutex
}

func NewStack() *Stack {
	return &Stack{dll: list.New()}
}

func (s *Stack) Push(x interface{}) {
	s.mutex.Lock()
	defer s.mutex.Unlock()

	s.dll.PushBack(x)
}

func (s *Stack) Pop() interface{} {
	s.mutex.Lock()
	defer s.mutex.Unlock()

	if s.dll.Len() == 0 {
		return nil
	}
	tail := s.dll.Back()
	val := tail.Value
	s.dll.Remove(tail)
	return val
}

Click this playground to preview the result.

答案4

得分: 4

引入泛型后,您现在可以创建一些非常优雅的实现,这里是一个使用更函数式风格的示例:

package main

import (
    "fmt"
)

type stack[T any] struct {
    Push   func(T)
    Pop    func() T
    Length func() int
}

func Stack[T any]() stack[T] {
    slice := make([]T, 0)
    return stack[T]{
        Push: func(i T) {
            slice = append(slice, i)
        },
        Pop: func() T {
            res := slice[len(slice)-1]
            slice = slice[:len(slice)-1]
            return res
        },
        Length: func() int {
            return len(slice)
        },
    }
}

func main() {
    stack := Stack[string]()
    stack.Push("this")
    fmt.Println(stack.Length())
    fmt.Println(stack.Pop())
}

这段代码使用泛型创建了一个名为stack的结构体,其中包含了PushPopLength三个方法。通过调用Stack函数,可以创建一个指定类型的栈。在main函数中,我们创建了一个字符串类型的栈,并进行了一些操作。

英文:

With the introduction of generics you can now create some pretty elegant implementations, here's one using a more functional style:

package main

import (
    "fmt"
)

type stack[T any] struct {
    Push func(T)
    Pop func() T
    Length func() int
}

func Stack[T any]() stack[T] {
    slice := make([]T, 0)
    return stack[T]{
        Push: func(i T) {
            slice = append(slice, i)
        },
        Pop: func() T {
            res := slice[len(slice)-1]
            slice = slice[:len(slice)-1]
            return res
        },
        Length: func() int {
            return len(slice)
        },
    }
}


func main() {
    stack := Stack[string]()
    stack.Push("this")
    fmt.Println(stack.Length())
    fmt.Println(stack.Pop())
}

答案5

得分: 2

使用 https://github.com/emirpasic/gods

package main

import lls "github.com/emirpasic/gods/stacks/linkedliststack"

func main() {
    stack := lls.New()  // 空栈
    stack.Push(1)       // 1
    stack.Push(2)       // 1, 2
    stack.Values()      // 2, 1 (后进先出顺序)
    _, _ = stack.Peek() // 2,true
    _, _ = stack.Pop()  // 2, true
    _, _ = stack.Pop()  // 1, true
    _, _ = stack.Pop()  // nil, false (没有可弹出的元素)
    stack.Push(1)       // 1
    stack.Clear()       // 空栈
    stack.Empty()       // true
    stack.Size()        // 0
}
英文:

Use https://github.com/emirpasic/gods

package main

import lls "github.com/emirpasic/gods/stacks/linkedliststack"

func main() {
	stack := lls.New()  // empty
	stack.Push(1)       // 1
	stack.Push(2)       // 1, 2
	stack.Values()      // 2, 1 (LIFO order)
	_, _ = stack.Peek() // 2,true
	_, _ = stack.Pop()  // 2, true
	_, _ = stack.Pop()  // 1, true
	_, _ = stack.Pop()  // nil, false (nothing to pop)
	stack.Push(1)       // 1
	stack.Clear()       // empty
	stack.Empty()       // true
	stack.Size()        // 0
}

答案6

得分: 0

我可以做得更好吗?

有了泛型的发布,我们可以像这样泛化您的实现:

package main

import (
	"fmt"
	"log"
)

// 泛型栈
// 应该实现更好的约束条件
type stack[V any] []V

func (s *stack[V]) Push(v V) int {
	*s = append(*s, v)
	return len(*s)
}
func (s *stack[V]) Last() V {
	l := len(*s)

	// 由开发者处理空栈
	if l == 0 {
		log.Fatal("空栈")
	}

	last := (*s)[l-1]
	return last
}

func (s *stack[V]) Pop() V {
	removed := (*s).Last()
	*s = (*s)[:len(*s)-1]

	return removed
}

// 不需要指针,因为是只读操作
func (s stack[V]) Values() {
	for i := len(s) - 1; i >= 0; i-- {
		fmt.Printf("%v ", s[i])
	}
}

Playground

英文:

> Can I do better?

With generics released, we can generalize your implementation like this:

package main

import (
	"fmt"
	"log"
)

// Generic stack
// One should implement better constraints
type stack[V any] []V

func (s *stack[V]) Push(v V) int {
	*s = append(*s, v)
	return len(*s)
}
func (s *stack[V]) Last() V {
	l := len(*s)

    // Upto the developer to handle an empty stack
	if l == 0 {
		log.Fatal("Empty Stack")
	}

	last := (*s)[l-1]
	return last
}

func (s *stack[V]) Pop() V {
	removed := (*s).Last()
	*s = (*s)[:len(*s)-1]

	return removed
}

// Pointer not needed because read-only operation
func (s stack[V]) Values() {
	for i := len(s) - 1; i >= 0; i-- {
		fmt.Printf("%v ", s[i])
	}
}

Playground

答案7

得分: 0

我的堆栈实现:

优点:

  1. 使用“切片”不需要手动调整内存,自动调整!
  2. 对于“多线程”是安全的。
  3. 不容易破坏信息。
  4. 使用泛型,因此可以灵活地与不同类型的数据一起使用。

缺点:

  1. 无法删除或使用随机项。
stack := utility.NewStack[string]()
stack.Push("1")
stack2 := utility.NewStack[[]int]()
stack2.Push([]int{1, 2, 3, 4, 5})
element, _ := stack2.Peek()
// 使用泛型来定义堆栈中的类型,也可以使用结构体。
type Stack[T any] struct {
	lock sync.Mutex // 用于线程安全的互斥锁
	S    []T        // 切片
}

func NewStack[T any]() *Stack[T] {
	return &Stack[T]{lock: sync.Mutex{}, S: []T{}}
}

func (stack *Stack[T]) Push(element T) {
	stack.lock.Lock()
	defer stack.lock.Unlock()
	stack.S = append(stack.S, element)
}

func (stack *Stack[T]) Pop() (T, error) {
	stack.lock.Lock()
	defer stack.lock.Unlock()
	l := len(stack.S)
	if l == 0 {
		var empty T
		return empty, errors.New("empty Stack")
	}
	element := stack.S[l-1]
	stack.S = stack.S[:l-1]
	return element, nil
}

func (stack *Stack[T]) Peek() (T, error) {
	stack.lock.Lock()
	defer stack.lock.Unlock()
	l := len(stack.S)
	if l == 0 {
		var empty T
		return empty, errors.New("empty Stack")
	}
	return stack.S[l-1], nil
}
英文:

My Stack implimentation:
The advantages are:

  1. Use "slice" no need to retune memory is automatically tuned!
  2. It is safe for "multithreading"
  3. Not easy to corrupt information
  4. Generics are used so it is flexible in use with different types of data.

The disadvantages are:

  1. You can't remove, use an random item
stack := utility.NewStack[string]()
stack.Push("1")
stack2 := utility.NewStack[[]int]()
stack2.Push([]int{1, 2, 3, 4, 5})
element, _ := stack2.Peek()
// Using Generics to define Type in Stake to Use Structs, too.
type Stack[T any] struct {
	lock sync.Mutex // Mutex for Thread safety
	S    []T // Slice
}

func NewStack[T any]() *Stack[T] {
	return &Stack[T]{lock: sync.Mutex{}, S: []T{}}
}

func (stack *Stack[T]) Push(element T) {
	stack.lock.Lock()
	defer stack.lock.Unlock()
	stack.S = append(stack.S, element)
}

func (stack *Stack[T]) Pop() (T, error) {
	stack.lock.Lock()
	defer stack.lock.Unlock()
	l := len(stack.S)
	if l == 0 {
		var empty T
		return empty, errors.New("empty Stack")
	}
	element := stack.S[l-1]
	stack.S = stack.S[:l-1]
	return element, nil
}

func (stack *Stack[T]) Peek() (T, error) {
	stack.lock.Lock()
	defer stack.lock.Unlock()
	l := len(stack.S)
	if l == 0 {
		var empty T
		return empty, errors.New("empty Stack")
	}
	return stack.S[l-1], nil
}

答案8

得分: 0

在Golang中并发安全栈实现中的另一个添加。

支持以下操作:

  1. Push(value interface{})
  2. Peek() interface{}
  3. Pop()
  4. Size() int
  5. Reverse()
  6. Display()
package main

import (
	"fmt"
	"sync"
)

type StackMethods interface {
	Push(value interface{})
	Peek() interface{}
	Pop()
	Size() int
	Reverse()
	Display()
}

type Stack struct {
	node *Node
	size int
	lock sync.Mutex
}

type Node struct {
	value interface{}
	next  *Node
}

func (s *Stack) Size() int {
	return s.size
}
func (s *Stack) Push(value interface{}) {
	s.lock.Lock()
	defer s.lock.Unlock()
	s.node = &Node{
		value: value,
		next:  s.node,
	}
	s.size++
}

func (s *Stack) Peek() interface{} {
	if s.size > 0 {
		fmt.Println("Element: ", s.node.value)
		return s.node.value
	} else {
		fmt.Println("No elements in stack to display.")
	}
	return nil
}

func (s *Stack) Pop() {
	s.lock.Lock()
	defer s.lock.Unlock()
	if s.size > 0 {
		fmt.Printf("Removing element %v from stack.\n", s.node.value)
		s.node = s.node.next
		s.size--
	} else {
		fmt.Println("No elements in stack to remove.")
	}
}

func (s *Stack) Reverse() {
	s.lock.Lock()
	defer s.lock.Unlock()
	if s.node != nil {
		var next, prev *Node
		curr := s.node

		for curr != nil {
			next = curr.next
			curr.next = prev
			prev = curr
			curr = next
		}
		s.node = prev
		return
	}
}

func (s *Stack) Display() {
	if s.node != nil {
		ptr := s.node
		fmt.Println("Displaying elements")
		for ptr != nil {
			fmt.Print(ptr.value, "\n")
			fmt.Println("____")
			ptr = ptr.next
		}
		return
	}
	fmt.Println("Stack is empty.")
}

func main() {
	//Creating interface object.
	var stackObject StackMethods
	//Assigning Stack struct object to interface object.
	stackObject = new(Stack)

	stackObject.Push("A")
	fmt.Println("Size:", stackObject.Size())
	stackObject.Peek()
	stackObject.Peek()
	fmt.Println("Size:", stackObject.Size())
	stackObject.Push("B")
	stackObject.Push("C")
	fmt.Println("Size:", stackObject.Size())
	stackObject.Peek()
	fmt.Println("Size:", stackObject.Size())
	fmt.Println("Before Reversing:")
	stackObject.Display()
	fmt.Println("After Reversing:")
	stackObject.Reverse()
	stackObject.Display()
}

英文:

One more addition to concurrency safe stack implementation in Golang.

Supporting operations like:

  1. Push(value interface{})
  2. Peek() interface{}
  3. Pop()
  4. Size() int
  5. Reverse()
  6. Display()
package main
import (
"fmt"
"sync"
)
type StackMethods interface {
Push(value interface{})
Peek() interface{}
Pop()
Size() int
Reverse()
Display()
}
type Stack struct {
node *Node
size int
lock sync.Mutex
}
type Node struct {
value interface{}
next  *Node
}
func (s *Stack) Size() int {
return s.size
}
func (s *Stack) Push(value interface{}) {
s.lock.Lock()
defer s.lock.Unlock()
s.node = &Node{
value: value,
next:  s.node,
}
s.size++
}
func (s *Stack) Peek() interface{} {
if s.size > 0 {
fmt.Println("Element: ", s.node.value)
return s.node.value
} else {
fmt.Println("No elements in stack to display.")
}
return nil
}
func (s *Stack) Pop() {
s.lock.Lock()
defer s.lock.Unlock()
if s.size > 0 {
fmt.Printf("Removing element %v from stack.\n", s.node.value)
s.node = s.node.next
s.size--
} else {
fmt.Println("No elements in stack to remove.")
}
}
func (s *Stack) Reverse() {
s.lock.Lock()
defer s.lock.Unlock()
if s.node != nil {
var next, prev *Node
curr := s.node
for curr != nil {
next = curr.next
curr.next = prev
prev = curr
curr = next
}
s.node = prev
return
}
}
func (s *Stack) Display() {
if s.node != nil {
ptr := s.node
fmt.Println("Displaying elements")
for ptr != nil {
fmt.Print(ptr.value, "\n")
fmt.Println("____")
ptr = ptr.next
}
return
}
fmt.Println("Stack is empty.")
}
func main() {
//Creating interface object.
var stackObject StackMethods
//Assigning Stack struct object to interface object.
stackObject = new(Stack)
stackObject.Push("A")
fmt.Println("Size:", stackObject.Size())
stackObject.Peek()
stackObject.Peek()
fmt.Println("Size:", stackObject.Size())
stackObject.Push("B")
stackObject.Push("C")
fmt.Println("Size:", stackObject.Size())
stackObject.Peek()
fmt.Println("Size:", stackObject.Size())
fmt.Println("Before Reversing:")
stackObject.Display()
fmt.Println("After Reversing:")
stackObject.Reverse()
stackObject.Display()
}

huangapple
  • 本文由 发表于 2015年2月16日 20:37:57
  • 转载请务必保留本文链接:https://go.coder-hub.com/28541609.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定