diff --git a/examples/linkedhashset/linkedhashset.go b/examples/linkedhashset/linkedhashset.go new file mode 100644 index 0000000..689d212 --- /dev/null +++ b/examples/linkedhashset/linkedhashset.go @@ -0,0 +1,23 @@ +// 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 main + +import "github.com/emirpasic/gods/sets/linkedhashset" + +// LinkedHashSetExample to demonstrate basic usage of LinkedHashSet +func main() { + set := linkedhashset.New() // empty + set.Add(5) // 5 + set.Add(4, 4, 3, 2, 1) // 5, 4, 3, 2, 1 (in insertion-order, duplicates ignored) + set.Remove(4) // 5, 3, 2, 1 (in insertion-order) + set.Remove(2, 3) // 5, 1 (in insertion-order) + set.Contains(1) // true + set.Contains(1, 5) // true + set.Contains(1, 6) // false + _ = set.Values() // []int{5, 1} (in insertion-order) + set.Clear() // empty + set.Empty() // true + set.Size() // 0 +} diff --git a/sets/linkedhashset/enumerable.go b/sets/linkedhashset/enumerable.go new file mode 100644 index 0000000..ad6ac96 --- /dev/null +++ b/sets/linkedhashset/enumerable.go @@ -0,0 +1,79 @@ +// 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 linkedhashset + +import "github.com/emirpasic/gods/containers" + +func assertEnumerableImplementation() { + var _ containers.EnumerableWithIndex = (*Set)(nil) +} + +// Each calls the given function once for each element, passing that element's index and value. +func (set *Set) Each(f func(index int, value interface{})) { + iterator := set.Iterator() + for iterator.Next() { + f(iterator.Index(), iterator.Value()) + } +} + +// Map invokes the given function once for each element and returns a +// container containing the values returned by the given function. +func (set *Set) Map(f func(index int, value interface{}) interface{}) *Set { + newSet := New() + iterator := set.Iterator() + for iterator.Next() { + newSet.Add(f(iterator.Index(), iterator.Value())) + } + return newSet +} + +// Select returns a new container containing all elements for which the given function returns a true value. +func (set *Set) Select(f func(index int, value interface{}) bool) *Set { + newSet := New() + iterator := set.Iterator() + for iterator.Next() { + if f(iterator.Index(), iterator.Value()) { + newSet.Add(iterator.Value()) + } + } + return newSet +} + +// Any passes each element of the container to the given function and +// returns true if the function ever returns true for any element. +func (set *Set) Any(f func(index int, value interface{}) bool) bool { + iterator := set.Iterator() + for iterator.Next() { + if f(iterator.Index(), iterator.Value()) { + return true + } + } + return false +} + +// All passes each element of the container to the given function and +// returns true if the function returns true for all elements. +func (set *Set) All(f func(index int, value interface{}) bool) bool { + iterator := set.Iterator() + for iterator.Next() { + if !f(iterator.Index(), iterator.Value()) { + return false + } + } + return true +} + +// Find passes each element of the container to the given function and returns +// the first (index,value) for which the function is true or -1,nil otherwise +// if no element matches the criteria. +func (set *Set) Find(f func(index int, value interface{}) bool) (int, interface{}) { + iterator := set.Iterator() + for iterator.Next() { + if f(iterator.Index(), iterator.Value()) { + return iterator.Index(), iterator.Value() + } + } + return -1, nil +} diff --git a/sets/linkedhashset/iterator.go b/sets/linkedhashset/iterator.go new file mode 100644 index 0000000..dfdbfc5 --- /dev/null +++ b/sets/linkedhashset/iterator.go @@ -0,0 +1,77 @@ +// 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 linkedhashset + +import ( + "github.com/emirpasic/gods/containers" + "github.com/emirpasic/gods/lists/doublylinkedlist" +) + +func assertIteratorImplementation() { + var _ containers.ReverseIteratorWithIndex = (*Iterator)(nil) +} + +// Iterator holding the iterator's state +type Iterator struct { + iterator doublylinkedlist.Iterator +} + +// Iterator returns a stateful iterator whose values can be fetched by an index. +func (set *Set) Iterator() Iterator { + return Iterator{iterator: set.list.Iterator()} +} + +// Next moves the iterator to the next element and returns true if there was a next element in the container. +// If Next() returns true, then next element's index and value can be retrieved by Index() and Value(). +// If Next() was called for the first time, then it will point the iterator to the first element if it exists. +// Modifies the state of the iterator. +func (iterator *Iterator) Next() bool { + return iterator.iterator.Next() +} + +// Prev moves the iterator to the previous element and returns true if there was a previous element in the container. +// If Prev() returns true, then previous element's index and value can be retrieved by Index() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator) Prev() bool { + return iterator.iterator.Prev() +} + +// Value returns the current element's value. +// Does not modify the state of the iterator. +func (iterator *Iterator) Value() interface{} { + return iterator.iterator.Value() +} + +// Index returns the current element's index. +// Does not modify the state of the iterator. +func (iterator *Iterator) Index() int { + return iterator.iterator.Index() +} + +// Begin resets the iterator to its initial state (one-before-first) +// Call Next() to fetch the first element if any. +func (iterator *Iterator) Begin() { + iterator.iterator.Begin() +} + +// End moves the iterator past the last element (one-past-the-end). +// Call Prev() to fetch the last element if any. +func (iterator *Iterator) End() { + iterator.iterator.End() +} + +// First moves the iterator to the first element and returns true if there was a first element in the container. +// If First() returns true, then first element's index and value can be retrieved by Index() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator) First() bool { + return iterator.iterator.First() +} + +// Last moves the iterator to the last element and returns true if there was a last element in the container. +// If Last() returns true, then last element's index and value can be retrieved by Index() and Value(). +// Modifies the state of the iterator. +func (iterator *Iterator) Last() bool { + return iterator.iterator.Last() +} diff --git a/sets/linkedhashset/linkedhashset.go b/sets/linkedhashset/linkedhashset.go new file mode 100644 index 0000000..a9fb2ae --- /dev/null +++ b/sets/linkedhashset/linkedhashset.go @@ -0,0 +1,118 @@ +// 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 linkedhashset implements a set that preserves insertion-order and is backed a hash tables to store values and doubly-linked list to store ordering. +// +// Note that insertion-order is not affected if an element is re-inserted into the set. +// +// Structure is not thread safe. +// +// References: http://en.wikipedia.org/wiki/Set_%28abstract_data_type%29 +package linkedhashset + +import ( + "fmt" + "github.com/emirpasic/gods/lists/doublylinkedlist" + "github.com/emirpasic/gods/sets" + "strings" +) + +func assertSetImplementation() { + var _ sets.Set = (*Set)(nil) +} + +// Set holds elements in go's native map +type Set struct { + items map[interface{}]struct{} + list *doublylinkedlist.List +} + +var itemExists = struct{}{} + +// New instantiates a new empty set +func New() *Set { + return &Set{ + items: make(map[interface{}]struct{}), + list: doublylinkedlist.New(), + } +} + +// Add adds the items (one or more) to the set. +// Note that insertion-order is not affected if an element is re-inserted into the set. +func (set *Set) Add(items ...interface{}) { + for _, item := range items { + + if _, contains := set.items[item]; contains { + continue + } + + set.items[item] = itemExists + set.list.Append(item) + } +} + +// Remove removes the items (one or more) from the set. +// Slow operation, worst-case O(n^2). +func (set *Set) Remove(items ...interface{}) { + for _, item := range items { + + if _, contains := set.items[item]; !contains { + continue + } + + delete(set.items, item) + index := set.list.IndexOf(item) + set.list.Remove(index) + } +} + +// Contains check if items (one or more) are present in the set. +// All items have to be present in the set for the method to return true. +// Returns true if no arguments are passed at all, i.e. set is always superset of empty set. +func (set *Set) Contains(items ...interface{}) bool { + for _, item := range items { + if _, contains := set.items[item]; !contains { + return false + } + } + return true +} + +// Empty returns true if set does not contain any elements. +func (set *Set) Empty() bool { + return set.Size() == 0 +} + +// Size returns number of elements within the set. +func (set *Set) Size() int { + return set.list.Size() +} + +// Clear clears all values in the set. +func (set *Set) Clear() { + set.items = make(map[interface{}]struct{}) + set.list.Clear() +} + +// Values returns all items in the set. +func (set *Set) Values() []interface{} { + values := make([]interface{}, set.Size()) + it := set.Iterator() + for it.Next() { + values[it.Index()] = it.Value() + } + return values +} + +// String returns a string representation of container +func (set *Set) String() string { + str := "LinkedHashSet\n" + items := []string{} + it := set.Iterator() + for it.Next() { + items = append(items, fmt.Sprintf("%v", it.Value())) + } + str += strings.Join(items, ", ") + return str +} diff --git a/sets/linkedhashset/linkedhashset_test.go b/sets/linkedhashset/linkedhashset_test.go new file mode 100644 index 0000000..cf3746e --- /dev/null +++ b/sets/linkedhashset/linkedhashset_test.go @@ -0,0 +1,498 @@ +// 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 linkedhashset + +import ( + "fmt" + "testing" +) + +func TestSetAdd(t *testing.T) { + set := New() + set.Add() + set.Add(1) + set.Add(2) + set.Add(2, 3) + set.Add() + if actualValue := set.Empty(); actualValue != false { + t.Errorf("Got %v expected %v", actualValue, false) + } + if actualValue := set.Size(); actualValue != 3 { + t.Errorf("Got %v expected %v", actualValue, 3) + } +} + +func TestSetContains(t *testing.T) { + set := New() + set.Add(3, 1, 2) + set.Add(2, 3) + set.Add() + if actualValue := set.Contains(); actualValue != true { + t.Errorf("Got %v expected %v", actualValue, true) + } + if actualValue := set.Contains(1); actualValue != true { + t.Errorf("Got %v expected %v", actualValue, true) + } + if actualValue := set.Contains(1, 2, 3); actualValue != true { + t.Errorf("Got %v expected %v", actualValue, true) + } + if actualValue := set.Contains(1, 2, 3, 4); actualValue != false { + t.Errorf("Got %v expected %v", actualValue, false) + } +} + +func TestSetRemove(t *testing.T) { + set := New() + set.Add(3, 1, 2) + set.Remove() + if actualValue := set.Size(); actualValue != 3 { + t.Errorf("Got %v expected %v", actualValue, 3) + } + set.Remove(1) + if actualValue := set.Size(); actualValue != 2 { + t.Errorf("Got %v expected %v", actualValue, 2) + } + set.Remove(3) + set.Remove(3) + set.Remove() + set.Remove(2) + if actualValue := set.Size(); actualValue != 0 { + t.Errorf("Got %v expected %v", actualValue, 0) + } +} + +func TestSetEach(t *testing.T) { + set := New() + set.Add("c", "a", "b") + set.Each(func(index int, value interface{}) { + switch index { + case 0: + if actualValue, expectedValue := value, "c"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 1: + if actualValue, expectedValue := value, "a"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 2: + if actualValue, expectedValue := value, "b"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + default: + t.Errorf("Too many") + } + }) +} + +func TestSetMap(t *testing.T) { + set := New() + set.Add("c", "a", "b") + mappedSet := set.Map(func(index int, value interface{}) interface{} { + return "mapped: " + value.(string) + }) + if actualValue, expectedValue := mappedSet.Contains("mapped: c", "mapped: b", "mapped: a"), true; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if actualValue, expectedValue := mappedSet.Contains("mapped: c", "mapped: b", "mapped: x"), false; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if mappedSet.Size() != 3 { + t.Errorf("Got %v expected %v", mappedSet.Size(), 3) + } +} + +func TestSetSelect(t *testing.T) { + set := New() + set.Add("c", "a", "b") + selectedSet := set.Select(func(index int, value interface{}) bool { + return value.(string) >= "a" && value.(string) <= "b" + }) + if actualValue, expectedValue := selectedSet.Contains("a", "b"), true; actualValue != expectedValue { + fmt.Println("A: ", selectedSet.Contains("b")) + t.Errorf("Got %v (%v) expected %v (%v)", actualValue, selectedSet.Values(), expectedValue, "[a b]") + } + if actualValue, expectedValue := selectedSet.Contains("a", "b", "c"), false; actualValue != expectedValue { + t.Errorf("Got %v (%v) expected %v (%v)", actualValue, selectedSet.Values(), expectedValue, "[a b]") + } + if selectedSet.Size() != 2 { + t.Errorf("Got %v expected %v", selectedSet.Size(), 3) + } +} + +func TestSetAny(t *testing.T) { + set := New() + set.Add("c", "a", "b") + any := set.Any(func(index int, value interface{}) bool { + return value.(string) == "c" + }) + if any != true { + t.Errorf("Got %v expected %v", any, true) + } + any = set.Any(func(index int, value interface{}) bool { + return value.(string) == "x" + }) + if any != false { + t.Errorf("Got %v expected %v", any, false) + } +} + +func TestSetAll(t *testing.T) { + set := New() + set.Add("c", "a", "b") + all := set.All(func(index int, value interface{}) bool { + return value.(string) >= "a" && value.(string) <= "c" + }) + if all != true { + t.Errorf("Got %v expected %v", all, true) + } + all = set.All(func(index int, value interface{}) bool { + return value.(string) >= "a" && value.(string) <= "b" + }) + if all != false { + t.Errorf("Got %v expected %v", all, false) + } +} + +func TestSetFind(t *testing.T) { + set := New() + set.Add("c", "a", "b") + foundIndex, foundValue := set.Find(func(index int, value interface{}) bool { + return value.(string) == "c" + }) + if foundValue != "c" || foundIndex != 0 { + t.Errorf("Got %v at %v expected %v at %v", foundValue, foundIndex, "c", 0) + } + foundIndex, foundValue = set.Find(func(index int, value interface{}) bool { + return value.(string) == "x" + }) + if foundValue != nil || foundIndex != -1 { + t.Errorf("Got %v at %v expected %v at %v", foundValue, foundIndex, nil, nil) + } +} + +func TestSetChaining(t *testing.T) { + set := New() + set.Add("c", "a", "b") +} + +func TestSetIteratorPrevOnEmpty(t *testing.T) { + set := New() + it := set.Iterator() + for it.Prev() { + t.Errorf("Shouldn't iterate on empty set") + } +} + +func TestSetIteratorNext(t *testing.T) { + set := New() + set.Add("c", "a", "b") + it := set.Iterator() + count := 0 + for it.Next() { + count++ + index := it.Index() + value := it.Value() + switch index { + case 0: + if actualValue, expectedValue := value, "c"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 1: + if actualValue, expectedValue := value, "a"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 2: + if actualValue, expectedValue := value, "b"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + default: + t.Errorf("Too many") + } + if actualValue, expectedValue := index, count-1; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + } + if actualValue, expectedValue := count, 3; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } +} + +func TestSetIteratorPrev(t *testing.T) { + set := New() + set.Add("c", "a", "b") + it := set.Iterator() + for it.Prev() { + } + count := 0 + for it.Next() { + count++ + index := it.Index() + value := it.Value() + switch index { + case 0: + if actualValue, expectedValue := value, "c"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 1: + if actualValue, expectedValue := value, "a"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + case 2: + if actualValue, expectedValue := value, "b"; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + default: + t.Errorf("Too many") + } + if actualValue, expectedValue := index, count-1; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + } + if actualValue, expectedValue := count, 3; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } +} + +func TestSetIteratorBegin(t *testing.T) { + set := New() + it := set.Iterator() + it.Begin() + set.Add("a", "b", "c") + for it.Next() { + } + it.Begin() + it.Next() + if index, value := it.Index(), it.Value(); index != 0 || value != "a" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 0, "a") + } +} + +func TestSetIteratorEnd(t *testing.T) { + set := New() + it := set.Iterator() + + if index := it.Index(); index != -1 { + t.Errorf("Got %v expected %v", index, -1) + } + + it.End() + if index := it.Index(); index != 0 { + t.Errorf("Got %v expected %v", index, 0) + } + + set.Add("a", "b", "c") + it.End() + if index := it.Index(); index != set.Size() { + t.Errorf("Got %v expected %v", index, set.Size()) + } + + it.Prev() + if index, value := it.Index(), it.Value(); index != set.Size()-1 || value != "c" { + t.Errorf("Got %v,%v expected %v,%v", index, value, set.Size()-1, "c") + } +} + +func TestSetIteratorFirst(t *testing.T) { + set := New() + set.Add("a", "b", "c") + it := set.Iterator() + if actualValue, expectedValue := it.First(), true; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if index, value := it.Index(), it.Value(); index != 0 || value != "a" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 0, "a") + } +} + +func TestSetIteratorLast(t *testing.T) { + set := New() + set.Add("a", "b", "c") + it := set.Iterator() + if actualValue, expectedValue := it.Last(), true; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if index, value := it.Index(), it.Value(); index != 2 || value != "c" { + t.Errorf("Got %v,%v expected %v,%v", index, value, 3, "c") + } +} + +func TestSetSerialization(t *testing.T) { + set := New() + set.Add("a", "b", "c") + + var err error + assert := func() { + if actualValue, expectedValue := set.Size(), 3; actualValue != expectedValue { + t.Errorf("Got %v expected %v", actualValue, expectedValue) + } + if actualValue := set.Contains("a", "b", "c"); actualValue != true { + t.Errorf("Got %v expected %v", actualValue, true) + } + if err != nil { + t.Errorf("Got error %v", err) + } + } + + assert() + + json, err := set.ToJSON() + assert() + + err = set.FromJSON(json) + assert() +} + +func benchmarkContains(b *testing.B, set *Set, size int) { + for i := 0; i < b.N; i++ { + for n := 0; n < size; n++ { + set.Contains(n) + } + } +} + +func benchmarkAdd(b *testing.B, set *Set, size int) { + for i := 0; i < b.N; i++ { + for n := 0; n < size; n++ { + set.Add(n) + } + } +} + +func benchmarkRemove(b *testing.B, set *Set, size int) { + for i := 0; i < b.N; i++ { + for n := 0; n < size; n++ { + set.Remove(n) + } + } +} + +func BenchmarkHashSetContains100(b *testing.B) { + b.StopTimer() + size := 100 + set := New() + for n := 0; n < size; n++ { + set.Add(n) + } + b.StartTimer() + benchmarkContains(b, set, size) +} + +func BenchmarkHashSetContains1000(b *testing.B) { + b.StopTimer() + size := 1000 + set := New() + for n := 0; n < size; n++ { + set.Add(n) + } + b.StartTimer() + benchmarkContains(b, set, size) +} + +func BenchmarkHashSetContains10000(b *testing.B) { + b.StopTimer() + size := 10000 + set := New() + for n := 0; n < size; n++ { + set.Add(n) + } + b.StartTimer() + benchmarkContains(b, set, size) +} + +func BenchmarkHashSetContains100000(b *testing.B) { + b.StopTimer() + size := 100000 + set := New() + for n := 0; n < size; n++ { + set.Add(n) + } + b.StartTimer() + benchmarkContains(b, set, size) +} + +func BenchmarkHashSetAdd100(b *testing.B) { + b.StopTimer() + size := 100 + set := New() + b.StartTimer() + benchmarkAdd(b, set, size) +} + +func BenchmarkHashSetAdd1000(b *testing.B) { + b.StopTimer() + size := 1000 + set := New() + for n := 0; n < size; n++ { + set.Add(n) + } + b.StartTimer() + benchmarkAdd(b, set, size) +} + +func BenchmarkHashSetAdd10000(b *testing.B) { + b.StopTimer() + size := 10000 + set := New() + for n := 0; n < size; n++ { + set.Add(n) + } + b.StartTimer() + benchmarkAdd(b, set, size) +} + +func BenchmarkHashSetAdd100000(b *testing.B) { + b.StopTimer() + size := 100000 + set := New() + for n := 0; n < size; n++ { + set.Add(n) + } + b.StartTimer() + benchmarkAdd(b, set, size) +} + +func BenchmarkHashSetRemove100(b *testing.B) { + b.StopTimer() + size := 100 + set := New() + for n := 0; n < size; n++ { + set.Add(n) + } + b.StartTimer() + benchmarkRemove(b, set, size) +} + +func BenchmarkHashSetRemove1000(b *testing.B) { + b.StopTimer() + size := 1000 + set := New() + for n := 0; n < size; n++ { + set.Add(n) + } + b.StartTimer() + benchmarkRemove(b, set, size) +} + +func BenchmarkHashSetRemove10000(b *testing.B) { + b.StopTimer() + size := 10000 + set := New() + for n := 0; n < size; n++ { + set.Add(n) + } + b.StartTimer() + benchmarkRemove(b, set, size) +} + +func BenchmarkHashSetRemove100000(b *testing.B) { + b.StopTimer() + size := 100000 + set := New() + for n := 0; n < size; n++ { + set.Add(n) + } + b.StartTimer() + benchmarkRemove(b, set, size) +} diff --git a/sets/linkedhashset/serialization.go b/sets/linkedhashset/serialization.go new file mode 100644 index 0000000..806b908 --- /dev/null +++ b/sets/linkedhashset/serialization.go @@ -0,0 +1,31 @@ +// 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 linkedhashset + +import ( + "encoding/json" + "github.com/emirpasic/gods/containers" +) + +func assertSerializationImplementation() { + var _ containers.JSONSerializer = (*Set)(nil) + var _ containers.JSONDeserializer = (*Set)(nil) +} + +// ToJSON outputs the JSON representation of list's elements. +func (set *Set) ToJSON() ([]byte, error) { + return json.Marshal(set.Values()) +} + +// FromJSON populates list's elements from the input JSON representation. +func (set *Set) FromJSON(data []byte) error { + elements := []interface{}{} + err := json.Unmarshal(data, &elements) + if err == nil { + set.Clear() + set.Add(elements...) + } + return err +}