In this markdown you will see the usage of all the datastructures and algorithms available through the standard library and this gem.
For them to work you firstly need to require the library with require 'victory'
.
- Ranges and Loops
- Complex
- Array
- Hash
- Struct
- Matrix
- Set
- OpenStruct
- Tuple
- Bitset
- Graph
- IOHelpers
- Descriptive Statistics
- Concurrent Structures
5.times { |x| puts x } # displays numbers from 0 to 5
3.upto(7) { |x| puts x } # displays numbers from 3 to 7
5.downto(3) { |x| puts x } # displays numbers from 5 to 3
0.step(10, 2) { |x| puts x } # displays 0, 2, 4, 6, 8, 10
12.step(6, -2) { |x| puts x } # displays 12, 10, 8, 6
(0...1).step(0.1) { |x| puts x } # displays 0.0, 0.1, ... , 0.8, 0.9
loop { |x| puts x } # infinite loop
a = 1+2i
# => (1+2i)
a.real
# => 1
a.imag
# => 2
a.rect
# => [1, 2]
r, i = a.rect
r + i.i
# => (1+2i)
[]
# => []
Array.new(4)
# => [nil, nil, nil, nil]
Array.new(4, 4)
# => [4, 4, 4, 4]
a = [:a, :b, :c, :d]
a[2]
# => :c
a[1] = 3
a
# => [:a, 3, :c, :d]
b = [1,2,3]
b[10] = :foo
b
# => [1, 2, 3, nil, nil, nil, nil, nil, nil, nil, :foo]
{}
Hash.new(default = nil)
# => {}
{"a" => 100, "b" => 200}
# => {"a"=>100, "b"=>200}
{a: 100, b: 200}
# => {:a=>100, :b=>200}
a = {}
a[1] = 2
a
# => {1=>2}
a[1]
# => 2
Face = Struct.new(:hair, :eyes, :skin)
my_face = Face.new :blond, :blue, :awesome
# => #<struct Face hair=:blond, eyes=:blue, skin=:awesome>
my_face[:hair]
# => :blond
my_face.hair
# => :blond
my_face.nose
# NoMethodError: undefined method `nose' for #<struct Face hair=:blond, eyes=:blue, skin=:awesome>
my_face[:nose]
# NameError: no member 'nose' in struct
class PayChecks < Struct.new(:week1, :week2, :week3, :week4)
def total
inject :+
end
end
pay_checks = PayChecks.new 100, 800, 300, 45
# => #<struct PayChecks week1=100, week2=800, week3=300, week4=45>
pay_checks.total
# => 1245
For the following data structures you will need to include Containers
or prefix every container with it.
m = Matrix[[1, 2, 3], [3, 4, 5], [6, 7, 8]]
# => Matrix[[1, 2, 3], [3, 4, 5], [6, 7, 8]]
m[0,0]
# => 1
m[1,1]
# => 4
m.row(2)
# => Vector[6, 7, 8]
m.column(1)
# => Vector[2, 4, 7]
s = Set.new
# => #<Set: {}>
s << :a
# => #<Set: {:a}>
s << :a
# => #<Set: {:a}>
s << :b
# => #<Set: {:a, :b}>
x = OpenStruct.new
# => #<OpenStruct>
x[:a]
# => nil
x
# => #<OpenStruct>
x[:a] = 0
# => 0
x
# => #<OpenStruct a=0>
x.a
# => 0
x.b
# => nil
x
# => #<OpenStruct a=0>
x[:b] = 123
# => 123
x
# => #<OpenStruct a=0, b=123>
t = T[1, 2, 3]
a, _, c = *t
a
# => 1
c
# => 3
t[2]
# => 2
From Bitset:
You create a bitset like this:
>> Bitset.new(8)
=> 00000000
Here we created an 8-bit bitset. All bits are initialized to 0.
We can also create a bitset based on a string of ones and zeros.
>> Bitset.from_s('00010001')
=> 00010001
or from an array. Falsey values (false and nil) are converted to zeroes; all other values, including 0 and "", are converted to ones.
>> Bitset.new [false, nil, 3, 0]
=> 0011
To input an array of ones and zeroes:
>> Bitset.new([0,1,1,0].map(&:positive?))
=> 0110
Obviously you can also set and clear bits...
>> bitset = Bitset.new(8)
=> 00000000
>> bitset[3] = true
=> 00010000
>> bitset[3] = false
=> 00000000
>> bitset.set(1, 3, 5, 7)
=> 01010101
>> bitset.clear(1, 5)
=> 00010001
Arrays of Integers can also be passed to #clear and #set (c/o brendon9x).
The point of a bitset is to be, effectively, an array of single bits. It should support basic set and bitwise operations. So, let's look at a few of those.
>> a = Bitset.from_s('00001111')
=> 00001111
>> b = Bitset.from_s('01010101')
=> 01010101
>> a & b
=> 00000101
>> a | b
=> 01011111
>> b - a
=> 01010000
>> a ^ b
=> 01011010
>> ~a
=> 11110000
>> a.hamming(b)
=> 4
>> a.cardinality
=> 4
>> a.reverse
=> 11110000
# Tell whether all of the given bit numbers are set
>> a.set? 6
=> true
# Return a new Bitset composed of bits #1, #3, #5, #4, and #1
# again. Unlike Array#values_at, this function currently only
# accepts an array of Fixnums as its argument.
>> a.values_at [1,3,5,4,1]
=> 00110
# Tell whether all of the given bit numbers are clear
>> a.clear? 1,3,5
=> false
# Tell whether all bits are clear
>> a.empty?
=> false
# Pass all bits to the block
>> b.each { |v| puts v }
=> false
true
false
...
# Pass the positions of all set bits to the block
>> b.each_set { |bit| puts bit }
=> 1
3
5
7
# Return an array of the positions of all set bits
>> b.each_set # AKA b.to_a
=> [1, 3, 5, 7]
# b.each_set(index) == b.each_set[index], but faster.
>> b.each_set(-3) # Negative index wraps around.
=> 3
# b.each_set(index, len) == b.each_set[index, len], but faster.
>> b.each_set(2,2) # Block is also allowed
=> [5,7]
# The following methods modify a Bitset in place very quickly:
>> a.intersect!(b) # like a &= b
>> a.union!(b) # like a |= b
>> a.difference!(b) # like a -= b
>> a.xor!(b) # like a ^= b
>> a.reset! # Zeroes all bits
# Above, "like" does not mean "identical to." a |= b creates a new
# Bitset object. a.union!(b) changes an existing object which
# affects all variables that point to the same object.
# Attempting to apply bitwise binary operators or their in-place
# equivalents between bitsets of different sizes will raise an
# ArgumentError.
>> b.to_binary_array
=> [0, 1, 0, 1, 0, 1, 0, 1]
# b.dup and b.clone are also available.
# Marshal.dump and Marshal.load are also supported. If you want to
# save a few bytes and don't need Marshal.load to work, you can
# use #pack and Bitset.unpack instead.
g = Graph[1,2, 2,3, 2,4]
g.to_s
# => "(1=2)(2=3)(2=4)"
dg = DirectedGraph[1,2 ,2,3 ,2,4, 4,5, 6,4, 1,6]
dg.to_s
# => "(1-2)(1-6)(2-3)(2-4)(4-5)(6-4)"
g = Containers::Graph[1,2, 1,3, 2,4, 4,5, 3,5]
weights = {[1,2] => 2, [2,4] => 1, [4,5] => 1, [1,3] => 1, [3,5] => 5}
g.dijkstra_shortest_paths(weights, 1)
# => {1=>[1], 2=>[1, 2], 3=>[1, 3], 4=>[1, 2, 4], 5=>[1, 2, 4, 5]}
g.dijkstra_shortest_path(weights, 1, 5)
# => [1, 2, 4, 5]
g = Containers::Graph[1,2, 1,3, 2,4, 4,5, 3,5]
weights = {[1,2] => 2, [2,4] => 1, [4,5] => 1, [1,3] => 1, [3,5] => 5}
g.bellman_ford_shortest_paths(weights, 1)
# => {1=>[1], 2=>[1, 2], 3=>[1, 3], 4=>[1, 2, 4], 5=>[1, 2, 4, 5]}
g = Containers::Graph[1,2, 1,3, 2,4, 4,5, 3,5]
g.topsort_iterator.to_a
# => [1, 3, 2, 4, 5]
Can read from file
or string
.
Reader.file("a")
# => ["1,2,3", "4,5,6", "7,8,9"]
Reader.file("a", col_sep: ",")
# => [["1", "2", "3"], ["4", "5", "6"], ["7", "8", "9"]]
Reader.file("a", col_sep: ",", as: "i")
# => [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
Reader.file("a", col_sep: ",", mapper: ->(x) {x.to_i * x.to_i})
# => [[1, 4, 9], [16, 25, 36], [49, 64, 81]]
Reader.string("0.1#0.2#0.3", line_sep: "#", as: 'r')
# => [(1/10), (1/5), (3/10)]
Supported types for as
are:
s
for string (default)i
for integerf
for floatr
for rationalc
for complexsym
for symbol
Can write to file
or string
.
a = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
Writer.string(a, line_sep: '@', col_sep: '.')
# => "1.2.3@4.5.6@7.8.9"
Writer.string(a, line_sep: "\n", col_sep: ' ', mapper: ->(x) {"0,#{x}"})
# => "0,1 0,2 0,3\n0,4 0,5 0,6\n0,7 0,8 0,9"
Writer.file("b", a, line_sep: "\n", col_sep: ' ', mapper: ->x {"0,#{x}"})
From descriptive_statistics:
data = [2,6,9,3,5,1,8,3,6,9,2]
# => [2, 6, 9, 3, 5, 1, 8, 3, 6, 9, 2]
data.number
# => 11.0
data.sum
# => 54.0
data.mean
# => 4.909090909090909
data.median
# => 5.0
data.variance
# => 7.7190082644628095
data.standard_deviation
# => 2.778310325442932
data.percentile(30)
# => 3.0
data.percentile(70)
# => 6.0
data.percentile_rank(8)
# => 81.81818181818183
data.mode
# => 2
data.range
# => 8
data.descriptive_statistics
=begin
=> {:number=>11.0,
:sum=>54,
:variance=>7.7190082644628095,
:standard_deviation=>2.778310325442932,
:min=>1,
:max=>9,
:mean=>4.909090909090909,
:mode=>2,
:median=>5.0,
:range=>8.0,
:q1=>2.5,
:q2=>5.0,
:q3=>7.0}
=end
They are included from concurrent-ruby. To use them:
Containers::Concurrent::Array
include Containers
Concurrent::Array
From Concurrent Ruby Features:
- Async: A mixin module that provides simple asynchronous behavior to a class. Loosely based on Erlang's gen_server.
- ScheduledTask: Like a Future scheduled for a specific future time.
- TimerTask: A Thread that periodically wakes up to perform work at regular intervals.
- Promises:
Unified implementation of futures and promises which combines features of previous
Future
,Promise
,IVar
,Event
,dataflow
,Delay
, and (partially)TimerTask
into a single framework. It extensively uses the new synchronization layer to make all the features non-blocking and lock-free, with the exception of obviously blocking operations like#wait
,#value
. It also offers better performance.
Collection classes that were originally part of the (deprecated) thread_safe
gem:
- Array A thread-safe subclass of Ruby's standard Array.
- Hash A thread-safe subclass of Ruby's standard Hash.
- Set A thread-safe subclass of Ruby's standard Set.
- Map A hash-like object
that should have much better performance characteristics, especially under high concurrency,
than
Concurrent::Hash
. - Tuple A fixed size array with volatile (synchronized, thread safe) getters/setters.
Value objects inspired by other languages:
- Maybe A thread-safe, immutable object representing an optional value, based on Haskell Data.Maybe.
Structure classes derived from Ruby's Struct:
- ImmutableStruct Immutable struct where values are set at construction and cannot be changed later.
- MutableStruct Synchronized, mutable struct where values can be safely changed at any time.
- SettableStruct Synchronized, write-once struct where values can be set at most once, either at construction or any time thereafter.
Thread-safe variables:
- Agent: A way to manage shared, mutable, asynchronous, independent state. Based on Clojure's Agent.
- Atom: A way to manage shared, mutable, synchronous, independent state. Based on Clojure's Atom.
- AtomicBoolean A boolean value that can be updated atomically.
- AtomicFixnum A numeric value that can be updated atomically.
- AtomicReference An object reference that may be updated atomically.
- Exchanger A synchronization point at which threads can pair and swap elements within pairs. Based on Java's Exchanger.
- MVar A synchronized single element container. Based on Haskell's MVar and Scala's MVar.
- ThreadLocalVar A variable where the value is different for each thread.
- TVar A transactional variable implementing software transactional memory (STM). Based on Clojure's Ref.
- See the thread pool overview, which also contains a list of other Executors available.
- CountDownLatch A synchronization object that allows one thread to wait on multiple other threads.
- CyclicBarrier A synchronization aid that allows a set of threads to all wait for each other to reach a common barrier point.
- Event Old school kernel-style event.
- ReadWriteLock A lock that supports multiple readers but only one writer.
- ReentrantReadWriteLock A read/write lock with reentrant and upgrade features.
- Semaphore A counting-based locking mechanism that uses permits.
- AtomicMarkableReference
class TestSolution
include Algorithms::Greedy::Solution
def score; @data; end
def next_solutions
[
TestSolution.new(@data),
TestSolution.new(@data + 1),
TestSolution.new(@data - 1),
]
end
end
initial_solution = TestSolution.new(0)
g = Algorithms::Greedy.init(initial_solution)
g.run(10)
g.best_solution.data
# => 10
class TestArraySolution < Algorithms::GeneticAlgorithm::ArraySolution; def score; @data.sum; end end
solution = TestArraySolution.new(Array.new(50, 0), possible_elements: (0..50).to_a)
genetic_alg = Algorithms::GeneticAlgorithm.init([solution])
genetic_alg.run(100)
genetic_alg.best_solution.score
class TestSolution
include Algorithms::SimulatedAnnealing::Solution
def score
x1, x2 = *@data
0.2 + x1 * x1 + x2 * x2 - 0.1 * Math.cos(6 * Math::PI * x1) - 0.1 * Math.cos(6 * Math::PI * x2)
end
def next_solution
x1, x2 = *@data
new_x1 = (x1 + rand * 2 - 1) / 2
new_x2 = (x2 + rand * 2 - 1) / 2
new_data = T[new_x1, new_x2]
TestSolution.new(new_data)
end
end
solution = TestSolution.new(T[-1, -1])
sim_annealing = Algorithms::SimulatedAnnealing.init(solution)
sim_annealing.run(1000)
sim_annealing.best_solution