<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Posts | Gonum</title><link>https://www.gonum.org/post/</link><atom:link href="https://www.gonum.org/post/index.xml" rel="self" type="application/rss+xml"/><description>Posts</description><generator>Source Themes Academic (https://sourcethemes.com/academic/)</generator><language>en-us</language><copyright>Copyright © 2017 The Gonum Authors</copyright><image><url>https://www.gonum.org/media/gophernumerical.png</url><title>Posts</title><link>https://www.gonum.org/post/</link></image><item><title>Using Gonum Graphs to Solve Word Ladder Puzzles</title><link>https://www.gonum.org/post/word_ladder/</link><pubDate>Sun, 12 Jul 2020 12:00:00 +0000</pubDate><guid>https://www.gonum.org/post/word_ladder/</guid><description>&lt;!--
List of word ladder doublets from Martin Gardiner's article.
rogue beast
shoes crust
grass green
black white
costs pence
quell bravo
kettle holder
furies barrel
tears smile
pitch tents
flour bread
raven miser
wheat bread
steal coins
beans shelf
-->
&lt;h2 id="word-ladders">Word Ladders&lt;/h2>
&lt;p>
&lt;a href="https://books.google.com/books?id=I9oVP8TlyqIC&amp;amp;pg=PA22" target="_blank" rel="noopener">Word ladders&lt;/a>, originally
&lt;a href="https://books.google.com/books?id=JkQCAAAAQAAJ&amp;amp;pg=PP1" target="_blank" rel="noopener">Doublets&lt;/a>, is a word game that was invented by Lewis Carrol in 1877. The game involves finding the smallest number of single letter changes required to change a starting word to a target of the same length, only using intermediates that are valid English words. For example, &amp;ldquo;head&amp;rdquo; to &amp;ldquo;tail&amp;rdquo;:&lt;/p>
&lt;pre>&lt;code>HEAD
heal
teal
tell
tall
TAIL
&lt;/code>&lt;/pre>
&lt;p>An obvious way to resolve this kind of problem mechanically is to represent the word ladder as a path walk through a graph where the nodes are words and edges exist between words with a
&lt;a href="https://en.wikipedia.org/wiki/Hamming_distance" target="_blank" rel="noopener">Hamming distance&lt;/a> of one. With this representation we can then find the shortest path between the start and end words using something like
&lt;a href="https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm" target="_blank" rel="noopener">Dijkstra&amp;rsquo;s shortest path algorithm&lt;/a>.&lt;/p>
&lt;p>Gonum provides routines for finding shortest paths in the
&lt;a href="https://pkg.go.dev/gonum.org/v1/gonum/graph/path?tab=doc" target="_blank" rel="noopener">&lt;code>graph/path&lt;/code>&lt;/a> package. These functions take a
&lt;a href="https://pkg.go.dev/gonum.org/v1/gonum/graph?tab=doc#Graph" target="_blank" rel="noopener">&lt;code>graph.Graph&lt;/code>&lt;/a> or
&lt;a href="https://pkg.go.dev/gonum.org/v1/gonum/graph/traverse?tab=doc#Graph" target="_blank" rel="noopener">&lt;code>traverse.Graph&lt;/code>&lt;/a> interface type. The use of interface types for representing graphs in Gonum provides a lot of flexibility for representing interesting problems, but also comes with a cost of knowing how to implement the concrete type correctly or how and when to use
&lt;a href="https://pkg.go.dev/gonum.org/v1/gonum/graph/simple?tab=doc" target="_blank" rel="noopener">the concrete graph types that we provide&lt;/a>.&lt;/p>
&lt;p>It is worth noting that the simple graph package is included primarily to allow Gonum graph functions to be tested (a multigraph equivalent
&lt;a href="https://pkg.go.dev/gonum.org/v1/gonum/graph/multi?tab=doc" target="_blank" rel="noopener">&lt;code>graph/multi&lt;/code>&lt;/a> exists for the same purpose), and that the graph implementations provided may not be the most efficient implementation for any particular graph-based problem; they are reasonably good, generally applicable and relatively simple implementations.&lt;/p>
&lt;p>Because we expect that users may need to implement their own graph types that satisfy the &lt;code>graph&lt;/code> package interfaces we provide
&lt;a href="https://pkg.go.dev/gonum.org/v1/gonum/graph/testgraph?tab=doc" target="_blank" rel="noopener">&lt;code>graph/testgraph&lt;/code>&lt;/a>, which is a set of testing routines that can be used as a framework to test a graph implementation&amp;rsquo;s adherence to the Gonum graph interface definitions. An example of how this package is used is
&lt;a href="https://github.com/gonum/gonum/blob/master/graph/simple/directed_test.go" target="_blank" rel="noopener">here&lt;/a>. If there is interest, a future post may go through the process of constructing tests for a new graph type using &lt;code>graph/testgraph&lt;/code>.&lt;/p>
&lt;p>With that background out of the way, let&amp;rsquo;s explore some ways that we can use Gonum graphs to solve word ladders in the general case, and then extend the problem to find extreme cases of word ladders, all while examining the performance characteristics of our implementations.&lt;/p>
&lt;p>Gonum graphs contain a
&lt;a href="https://pkg.go.dev/gonum.org/v1/gonum/graph?tab=doc#Node" target="_blank" rel="noopener">&lt;code>graph.Node&lt;/code>&lt;/a> type whose sole method returns a graph-unique integer ID. So, the naive way to solve the problem would be to read in the word list into a &lt;code>[]string&lt;/code>, construct a graph based on the Hamming distance relationship that we require and then use the graph node IDs as indexes into the slice. There is a little bit of cleaning up to do since the game does not distinguish letter case and we need to ensure that all the words in our list are the same length.&lt;/p>
&lt;pre>&lt;code>package main
import (
&amp;quot;bufio&amp;quot;
&amp;quot;flag&amp;quot;
&amp;quot;fmt&amp;quot;
&amp;quot;log&amp;quot;
&amp;quot;os&amp;quot;
&amp;quot;strings&amp;quot;
&amp;quot;gonum.org/v1/gonum/graph/path&amp;quot;
&amp;quot;gonum.org/v1/gonum/graph/simple&amp;quot;
)
func main() {
first := flag.String(&amp;quot;first&amp;quot;, &amp;quot;&amp;quot;, &amp;quot;first word in word ladder (required - length must match last)&amp;quot;)
last := flag.String(&amp;quot;last&amp;quot;, &amp;quot;&amp;quot;, &amp;quot;last word in word ladder (required - length must match first)&amp;quot;)
flag.Parse()
if *first == &amp;quot;&amp;quot; || *last == &amp;quot;&amp;quot; || len(*first) != len(*last) {
flag.Usage()
os.Exit(2)
}
for _, p := range []*string{first, last} {
s := strings.ToLower(*p)
if !isWord(s) {
fmt.Fprintf(os.Stderr, &amp;quot;word must not contain punctuation or numerals: %q\n&amp;quot;, *p)
os.Exit(2)
}
*p = s
}
// Read in a list of unique words from the input stream.
// Include the first and last words in the ladder in case
// they do not exists in the dictionary.
words := map[string]int64{*first: 0, *last: 1}
sc := bufio.NewScanner(os.Stdin)
for sc.Scan() {
w := sc.Text()
if len(w) != len(*first) || !isWord(w) {
continue
}
w = strings.ToLower(w)
if _, exists := words[w]; exists {
continue
}
words[w] = int64(len(words))
}
if err := sc.Err(); err != nil {
log.Fatalf(&amp;quot;failed to read word list: %v&amp;quot;, err)
}
list := make([]string, len(words))
for w, id := range words {
list[id] = w
}
// Construct a graph using Hamming distance one edges from
// list of words.
g := simple.NewUndirectedGraph()
for u, uid := range words {
for _, v := range neighbours(u, words) {
vid := words[v]
g.SetEdge(simple.Edge{F: simple.Node(uid), T: simple.Node(vid)})
}
}
// Find the shortest paths from the first word...
pth := path.DijkstraFrom(simple.Node(words[*first]), g)
// ... to the last word.
ladder, _ := pth.To(words[strings.ToLower(*last)])
// Print each step in the ladder.
for _, w := range ladder {
fmt.Println(list[w.ID()])
}
}
// isWord returns whether s is entirely alphabetical.
func isWord(s string) bool {
for _, c := range []byte(s) {
if lc(c) &amp;lt; 'a' || 'z' &amp;lt; lc(c) {
return false
}
}
return true
}
// lc returns the lower case of b.
func lc(b byte) byte {
return b | 0x20
}
// neighbours returns a slice of string of words in the words map
// that are within Hamming distance one from the query word.
func neighbours(word string, words map[string]int64) []string {
var adj []string
for j := range word {
for d := byte('a'); d &amp;lt;= 'z'; d++ {
b := make([]byte, len(word))
for i, c := range []byte(word) {
if i == j {
b[i] = d
} else {
b[i] = c
}
}
w := string(b)
if w != word {
if _, ok := words[w]; ok {
// We have found a neighbouring word so we
// can add it to our list of neighbours.
adj = append(adj, w)
}
}
}
}
return adj
}
&lt;/code>&lt;/pre>
&lt;p>Running
&lt;a href="https://www.gonum.org/code/word_ladders/words-0/main.go">this code&lt;/a> with Carroll&amp;rsquo;s example gives us an answer in 0.09s, using ~8.5MB. (&lt;code>xtime&lt;/code> is defined
&lt;a href="https://blog.golang.org/pprof" target="_blank" rel="noopener">here&lt;/a>, &lt;code>/usr/share/dict/words&lt;/code> is provided by the Ubuntu wamerican package.)&lt;/p>
&lt;pre>&lt;code>$ xtime words-0 -first head -last tail &amp;lt;/usr/share/dict/words
head
heal
teal
tell
tall
tail
0.08u 0.00s 0.07r 8516kB words-0 -first head -last tail
&lt;/code>&lt;/pre>
&lt;p>This implementation certainly works, but the business logic is spread thoughout the input handling and the main loop, so we can clean it up by using the fact that the graph packages work with interface values. To do this, we will define a new graph type that holds nodes that are aware of the word that they represent.&lt;/p>
&lt;p>The &lt;code>wordGraph&lt;/code> type still makes use of a Gonum simple graph implementation and the logic of the calculation is essentially identical.&lt;/p>
&lt;pre>&lt;code>type wordGraph struct {
n int
ids map[string]int64
*simple.UndirectedGraph
}
&lt;/code>&lt;/pre>
&lt;p>The nodes in the graph hold the word they represent and also satisfy &lt;code>fmt.Stringer&lt;/code> so that we don&amp;rsquo;t need to do any work to get the word back when we have a path.&lt;/p>
&lt;pre>&lt;code>type node struct {
word string
id int64
}
func (n node) ID() int64 { return n.id }
func (n node) String() string { return n.word }
&lt;/code>&lt;/pre>
&lt;pre>&lt;code>package main
import (
&amp;quot;bufio&amp;quot;
&amp;quot;flag&amp;quot;
&amp;quot;fmt&amp;quot;
&amp;quot;log&amp;quot;
&amp;quot;os&amp;quot;
&amp;quot;strings&amp;quot;
&amp;quot;gonum.org/v1/gonum/graph&amp;quot;
&amp;quot;gonum.org/v1/gonum/graph/path&amp;quot;
&amp;quot;gonum.org/v1/gonum/graph/simple&amp;quot;
)
func main() {
first := flag.String(&amp;quot;first&amp;quot;, &amp;quot;&amp;quot;, &amp;quot;first word in word ladder (required - length must match last)&amp;quot;)
last := flag.String(&amp;quot;last&amp;quot;, &amp;quot;&amp;quot;, &amp;quot;last word in word ladder (required - length must match first)&amp;quot;)
flag.Parse()
if *first == &amp;quot;&amp;quot; || *last == &amp;quot;&amp;quot; || len(*first) != len(*last) {
flag.Usage()
os.Exit(2)
}
// Make a new word graph and include the first and last
// words in the ladder in case they do not exists in the
// dictionary.
wg := newWordGraph(len(*first))
for _, p := range []*string{first, last} {
s := strings.ToLower(*p)
if !isWord(s) {
fmt.Fprintf(os.Stderr, &amp;quot;word must not contain punctuation or numerals: %q\n&amp;quot;, *p)
os.Exit(2)
}
*p = s
wg.include(s)
}
// Read in a list of unique words from the input stream.
sc := bufio.NewScanner(os.Stdin)
for sc.Scan() {
wg.include(sc.Text())
}
if err := sc.Err(); err != nil {
log.Fatalf(&amp;quot;failed to read word list: %v&amp;quot;, err)
}
pth := path.DijkstraAllFrom(wg.nodeFor(*first), wg)
ladders, _ := pth.AllTo(wg.nodeFor(*last).ID())
for _, l := range ladders {
fmt.Println(l)
}
}
// wordGraph is a graph of Hamming distance-1 word paths. It encapsulates
// a Gonum simple.UndirectedGraph to provide a domain-specific API for
// handling word ladder searches.
type wordGraph struct {
n int // n is the length of words described by the graph.
ids map[string]int64
*simple.UndirectedGraph
}
// newWordGraph returns a new wordGraph for words of n characters.
func newWordGraph(n int) wordGraph {
return wordGraph{
n: n,
ids: make(map[string]int64),
UndirectedGraph: simple.NewUndirectedGraph(),
}
}
// include adds word to the graph and connects it to its Hamming distance-1
// neighbours.
func (g *wordGraph) include(word string) {
if len(word) != g.n || !isWord(word) {
return
}
word = strings.ToLower(word)
if _, exists := g.ids[word]; exists {
return
}
// We know the node is not yet in the graph, so we can add it.
u := g.UndirectedGraph.NewNode()
uid := u.ID()
u = node{word: word, id: uid}
g.UndirectedGraph.AddNode(u)
g.ids[word] = uid
// Join to all the neighbours from words we already know.
for _, v := range neighbours(word, g.ids) {
v := g.UndirectedGraph.Node(g.ids[v])
g.SetEdge(simple.Edge{F: u, T: v})
}
}
// isWord returns whether s is entirely alphabetical.
func isWord(s string) bool {
for _, c := range []byte(s) {
if lc(c) &amp;lt; 'a' || 'z' &amp;lt; lc(c) {
return false
}
}
return true
}
// lc returns the lower case of b.
func lc(b byte) byte {
return b | 0x20
}
// neighbours returns a slice of string of words in the words map
// that are within Hamming distance one from the query word.
func neighbours(word string, words map[string]int64) []string {
// The body of neighbours is identical to the version
// in the original implementations.
}
// nodeFor returns a graph.Node representing the word for inclusion in a wordGraph.
func (g wordGraph) nodeFor(word string) graph.Node {
id, ok := g.ids[word]
if !ok {
return nil
}
return g.UndirectedGraph.Node(id)
}
// node is a word node in a wordGraph.
type node struct {
word string
id int64
}
func (n node) ID() int64 { return n.id }
func (n node) String() string { return n.word }
&lt;/code>&lt;/pre>
&lt;p>Although the logic is almost identical, the line count is higher, this however comes with a more modular implementation which is easier to understand and would be easier to maintain in a more complex application.&lt;/p>
&lt;p>An important aspect of the implementation is that the concrete type wrapping the &lt;code>*simple.UndirectedGraph&lt;/code> adds domain-specific API that means the client code does not need to deal with the graph, but makes the graph API available to the path-finding function. This is a design intention for the graph packages.&lt;/p>
&lt;p>Running
&lt;a href="https://www.gonum.org/code/word_ladders/words-1/main.go">the new version&lt;/a> gives us the same performance.&lt;/p>
&lt;pre>&lt;code>$ xtime words-1 -first head -last tail &amp;lt;/usr/share/dict/words
head
heal
hell
tell
tall
tail
0.07u 0.01s 0.06r 9008kB words-1 -first head -last tail
&lt;/code>&lt;/pre>
&lt;p>If you have run the code, you will notice that you won&amp;rsquo;t necessarily get the word ladder matching the one here; there are multiple co-equal shortest paths between &amp;ldquo;head&amp;rdquo; and &amp;ldquo;tail&amp;rdquo;. &lt;code>gonum/path&lt;/code> provides a way to find all shortest paths between a pair of node which requires only a minor alteration of the code. Instead of calling &lt;code>path.DijkstraFrom&lt;/code> we&amp;rsquo;ll use
&lt;a href="https://pkg.go.dev/gonum.org/v1/gonum/graph/path?tab=doc#DijkstraAllFrom" target="_blank" rel="noopener">&lt;code>path.DijkstraAllFrom&lt;/code>&lt;/a> and loop over the slice of paths returned by
&lt;a href="https://pkg.go.dev/gonum.org/v1/gonum/graph/path?tab=doc#DijkstraAllFrom.AllTo" target="_blank" rel="noopener">&lt;code>pth.AllTo&lt;/code>&lt;/a>.&lt;/p>
&lt;pre>&lt;code> pth := path.DijkstraAllFrom(wg.nodeFor(*first), wg)
ladders, _ := pth.AllTo(wg.nodeFor(*last).ID())
for _, l := range ladders {
fmt.Println(l)
}
&lt;/code>&lt;/pre>
&lt;p>
&lt;a href="https://www.gonum.org/code/word_ladders/words-1a/main.go">This&lt;/a> now gives us all the shortest word ladders from &amp;ldquo;head&amp;rdquo; to &amp;ldquo;tail&amp;rdquo;.&lt;/p>
&lt;pre>&lt;code>$ xtime words-1a -first head -last tail &amp;lt;/usr/share/dict/words
[head read reid raid rail tail]
[head heal neal neil nail tail]
[head heal teal tell tall tail]
[head held hell tell tall tail]
[head heal hell tell tall tail]
[head held hell hall tall tail]
[head heal hell hall tall tail]
[head hear heir hair hail tail]
[head held hell hall hail tail]
[head heal hell hall hail tail]
0.07u 0.00s 0.07r 8876kB words-1a -first head -last tail
&lt;/code>&lt;/pre>
&lt;p>Getting back to the single path problem, the next step is to see what we can do to improve the performance of the program. The observation that can drive this is that when we are constructing the graph in the naive implementation and the &lt;code>wordGraph&lt;/code> wrapping of that approach, we perform neighbourhood calculations even for words that are unreachable from our doublet. This work will never be used. Depending on the word length and the doublet we choose this can make a reasonable difference.&lt;/p>
&lt;p>The main function remains the same, but &lt;code>wordGraph&lt;/code> is replaced with the following (and goimports adjusts our import list) so that we determine neighbourhoods lazily as we reach them during the shortest path search.&lt;/p>
&lt;p>In order to lazily evaluate a word&amp;rsquo;s neighbourhood we will make use of a design feature of Gonum graphs where node and edge iteration is handled by Go interface types. This allows us to replace the built in node iterator with an application-specific iterator that knows more about the nature of the graph we are working with.&lt;/p>
&lt;p>To do this, we need to adjust the &lt;code>wordGraph&lt;/code> a little, adding &lt;code>From&lt;/code> and &lt;code>Edge&lt;/code> methods to the type and returning our new node iterator from the &lt;code>From&lt;/code> method call.&lt;/p>
&lt;pre>&lt;code>// wordGraph is a graph of Hamming distance-1 word paths using lazy implicit
// edge calculation.
type wordGraph struct {
n int
words []string
ids map[string]int64
}
// newWordGraph returns a new wordGraph for words of n characters.
func newWordGraph(n int) wordGraph {
return wordGraph{n: n, ids: make(map[string]int64)}
}
// include adds word to the graph and connects it to its Hamming distance-1
// neighbours.
func (g *wordGraph) include(word string) {
if len(word) != g.n || !isWord(word) {
return
}
word = strings.ToLower(word)
if _, exists := g.ids[word]; exists {
return
}
g.ids[word] = int64(len(g.words))
g.words = append(g.words, word)
}
// isWord returns whether s is entirely alphabetical.
func isWord(s string) bool {
for _, c := range []byte(s) {
if lc(c) &amp;lt; 'a' || 'z' &amp;lt; lc(c) {
return false
}
}
return true
}
// lc returns the lower case of b.
func lc(b byte) byte {
return b | 0x20
}
// nodeFor returns a graph.Node representing the word for inclusion in a wordGraph.
func (g wordGraph) nodeFor(word string) graph.Node {
id, ok := g.ids[word]
if !ok {
return nil
}
return node{word, id}
}
// From implements the graph.Graph From method.
func (g wordGraph) From(id int64) graph.Nodes {
if uint64(id) &amp;gt;= uint64(len(g.words)) {
return graph.Empty
}
return newNeighbours(g.words[id], g.ids)
}
// Edge implements the graph.Graph Edge method.
func (g wordGraph) Edge(uid, vid int64) graph.Edge {
if uid == vid {
return nil
}
if uint64(uid) &amp;gt;= uint64(len(g.words)) {
return nil
}
if uint64(vid) &amp;gt;= uint64(len(g.words)) {
return nil
}
u := g.words[uid]
v := g.words[vid]
d := hamming(u, v)
if d != 1 {
return nil
}
return edge{f: node{u, uid}, t: node{v, vid}}
}
// hamming returns the Hamming distance between the words a and b.
func hamming(a, b string) int {
if len(a) != len(b) {
panic(&amp;quot;word length mismatch&amp;quot;)
}
var d int
for i, c := range []byte(a) {
if c != b[i] {
d++
}
}
return d
}
&lt;/code>&lt;/pre>
&lt;p>The node iterator itself wraps a slightly altered &lt;code>neighbours&lt;/code> function used in the previous version with some addition methods required to allow the iterator to be used, implementing the &lt;code>graph.Nodes&lt;/code> interface.&lt;/p>
&lt;pre>&lt;code>// neighbours implements the graph.Nodes interface. It is a deterministic
// iterator over sets of nodes that represent words with Hamming distance-1
// from a query word.
type neighbours struct {
word string
ids map[string]int64
j int
d byte
buf []byte
curr graph.Node
}
// newNeighbours returns a new word neighbours iterator.
func newNeighbours(word string, ids map[string]int64) *neighbours {
return &amp;amp;neighbours{word: word, ids: ids, d: 'a', buf: make([]byte, len(word))}
}
// Len implements the graph.Nodes Len method. It returns -1 to indicate the iterator
// has an unknown number of of items.
func (it *neighbours) Len() int { return -1 }
// Next implements the graph.Nodes Next method.
func (it *neighbours) Next() bool {
// The Next method is implemented using the same algorithm as used by the
// neighbours function in the original naive implementation of the search.
for it.j &amp;lt; len(it.word) {
for i, c := range []byte(it.word) {
if i == it.j {
it.buf[i] = it.d
} else {
it.buf[i] = c
}
}
it.d++
if it.d &amp;gt; 'z' {
it.j++
it.d = 'a'
}
if !bytes.Equal(it.buf, []byte(it.word)) {
// We have found a neighbouring word so we can return
// true and set the current word to this neighbour.
if _, ok := it.ids[string(it.buf)]; ok {
w := string(it.buf)
it.curr = node{w, it.ids[w]}
return true
}
}
}
it.curr = nil
return false
}
// Node implements the graph.Nodes Node method.
func (it *neighbours) Node() graph.Node { return it.curr }
// Reset implements the graph.Nodes Reset method.
func (it *neighbours) Reset() { it.j, it.d = 0, 'a' }
// node is a word node in a wordGraph.
type node struct {
word string
id int64
}
func (n node) ID() int64 { return n.id }
func (n node) String() string { return n.word }
// edge is a Hamming distance-1 relationship between words in a wordGraph.
type edge struct{ f, t node }
func (e edge) From() graph.Node { return e.f }
func (e edge) To() graph.Node { return e.t }
func (e edge) ReversedEdge() graph.Edge { return edge{f: e.t, t: e.f} }
&lt;/code>&lt;/pre>
&lt;p>
&lt;a href="https://www.gonum.org/code/word_ladders/words-2/main.go">Here&lt;/a> we see a good improvement.&lt;/p>
&lt;pre>&lt;code>$ xtime words-2 -first head -last tail &amp;lt;/usr/share/dict/words
head
heal
neal
neil
nail
tail
0.04u 0.00s 0.05r 6472kB words-2 -first head -last tail
&lt;/code>&lt;/pre>
&lt;p>Note that unlike the previous implementation for a single shortest path, this implementation will always output the same result since the &lt;code>*neighbours&lt;/code> iterator here is completely deterministic.&lt;/p>
&lt;p>A
&lt;a href="https://www.gonum.org/code/word_ladders/words-2f/main.go">simpler version of this&lt;/a> exists, that calculates a slice of neighbours for each &lt;code>From&lt;/code> call,&lt;/p>
&lt;pre>&lt;code>// From implements the graph.Graph From method.
func (g wordGraph) From(id int64) graph.Nodes {
if uint64(id) &amp;gt;= uint64(len(g.words)) {
return graph.Empty
}
return iterator.NewOrderedNodes(neighbours(g.words[id], g.ids))
}
// neighbours returns a slice of node of words in the words map
// that are within Hamming distance one from the query word.
func neighbours(word string, words map[string]int64) []graph.Node {
var adj []graph.Node
for j := range word {
for d := byte('a'); d &amp;lt;= 'z'; d++ {
b := make([]byte, len(word))
for i, c := range []byte(word) {
if i == j {
b[i] = d
} else {
b[i] = c
}
}
w := string(b)
if w != word {
if _, ok := words[w]; ok {
adj = append(adj, node{word: w, id: words[w]})
}
}
}
}
return adj
}
&lt;/code>&lt;/pre>
&lt;p>but has worse performance characteristics.&lt;/p>
&lt;pre>&lt;code>$ xtime words-2f -first head -last tail &amp;lt;/usr/share/dict/words &amp;gt;/dev/null
0.07u 0.00s 0.07r 7276kB words-2f -first head -last tail
&lt;/code>&lt;/pre>
&lt;p>
&lt;a href="https://www.gonum.org/code/word_ladders/words-2a/main.go">Again&lt;/a>, we can obtain all possible ladders for the doublet with the change to &lt;code>path.DijkstraAllFrom&lt;/code> with performance that still beats the naive implementation.&lt;/p>
&lt;pre>&lt;code>$ xtime words-2a -first head -last tail &amp;lt;/usr/share/dict/words &amp;gt;/dev/null
0.05u 0.00s 0.04r 6964kB words-2a -first head -last tail
&lt;/code>&lt;/pre>
&lt;p>So it seems that we have an answer, we should use the lazy neighbourhood approach.&lt;/p>
&lt;p>We can extend our problem though to not just find one or all solutions for a doublet given a word list, but find the longest ladder for a given word length in a word list. We can do this by replacing the &lt;code>path.DijkstraFrom&lt;/code> search stanza with the following code:&lt;/p>
&lt;pre>&lt;code> var longest struct {
length float64
ends [][2]int64
}
pths := path.DijkstraAllPaths(wg)
words := graph.NodesOf(wg.Nodes())
for i, from := range words {
for _, to := range words[i+1:] {
fid := from.ID()
tid := to.ID()
length := pths.Weight(fid, tid)
switch {
case math.IsInf(length, 1):
continue
case length &amp;gt; longest.length:
longest.length = length
longest.ends = [][2]int64{{fid, tid}}
case length == longest.length:
longest.ends = append(longest.ends, [2]int64{fid, tid})
}
}
}
fmt.Println(longest.length)
for _, ends := range longest.ends {
ladders, _ := pths.AllBetween(ends[0], ends[1])
for _, l := range ladders {
fmt.Println(l)
}
}
&lt;/code>&lt;/pre>
&lt;p>and replacing the flags we accept.&lt;/p>
&lt;pre>&lt;code> n := flag.Int(&amp;quot;n&amp;quot;, 0, &amp;quot;length of words to use for ladder (must be greater than 0)&amp;quot;)
&lt;/code>&lt;/pre>
&lt;p>This change can be made to either the implicit lazy implementation or the eager approach.&lt;/p>
&lt;p>However, the
&lt;a href="https://www.gonum.org/code/word_ladders/words-3/main.go">lazy approach&lt;/a>&lt;/p>
&lt;pre>&lt;code>$ xtime words-3 -n 4 &amp;lt;/usr/share/dict/words
19
[inca inch itch etch each bach bath oath oats opts opus onus anus ants ante anne acne ache achy ashy]
[inca inch itch etch each mach math oath oats opts opus onus anus ants ante anne acne ache achy ashy]
[inca inch itch etch each bach bath bats oats opts opus onus anus ants ante anne acne ache achy ashy]
[inca inch itch etch each mach math mats oats opts opus onus anus ants ante anne acne ache achy ashy]
[inca inch itch etch each bach bath oath oats opts opus onus anus ants ante anne acne ache ashe ashy]
[inca inch itch etch each mach math oath oats opts opus onus anus ants ante anne acne ache ashe ashy]
[inca inch itch etch each bach bath bats oats opts opus onus anus ants ante anne acne ache ashe ashy]
[inca inch itch etch each mach math mats oats opts opus onus anus ants ante anne acne ache ashe ashy]
[chum chug thug thud thad chad clad clan alan alas alms elms elma erma erna edna edda eddy edgy edge]
[chum chug thug thud thad chad chan clan alan alas alms elms elma erma erna edna edda eddy edgy edge]
[chum chug thug thud thad than chan clan alan alas alms elms elma erma erna edna edda eddy edgy edge]
[chum chug thug thud thad than khan klan alan alas alms elms elma erma erna edna edda eddy edgy edge]
[chum chug thug thud thad chad clad clan alan alas alms alma elma erma erna edna edda eddy edgy edge]
[chum chug thug thud thad chad chan clan alan alas alms alma elma erma erna edna edda eddy edgy edge]
[chum chug thug thud thad than chan clan alan alas alms alma elma erma erna edna edda eddy edgy edge]
[chum chug thug thud thad than khan klan alan alas alms alma elma erma erna edna edda eddy edgy edge]
[edge edgy eddy edda edna erna erma elma elms alms aims sims sums sues suet suit quit quid quad quay]
[edge edgy eddy edda edna erna erma elma alma alms aims sims sums sues suet suit quit quid quad quay]
87.07u 0.44s 75.44r 947092kB words-3 -n 4
&lt;/code>&lt;/pre>
&lt;p>is handily beaten by the
&lt;a href="https://www.gonum.org/code/word_ladders/words-4/main.go">eager approach&lt;/a>.&lt;/p>
&lt;pre>&lt;code>$ xtime words-4 -n 4 &amp;lt;/usr/share/dict/words &amp;gt;/dev/null
28.22u 0.34s 23.36r 863496kB words-4 -n 4
&lt;/code>&lt;/pre>
&lt;p>This is because the lazy optimisation depends on only expanding neighbourhoods once and avoiding parts that are not accessible from the given end points, but the all pairs shortest paths algorithm repeatedly expands neighbourhoods and examines the entire graph, so it makes sense to do work up front and retain the results.&lt;/p>
&lt;p>The learning we can take from this is that one particular graphical approach that suits one particular problem may be a poor fit for another, even closely related, problem. So an understanding of the problem that you are attempting to solve and at least a passing understanding of the algorithmic details of the functions that you plan to use are crucial for being able to choose an appropriate graph implementation. As an aside, the diversity of problems that graphs can be used to address has been one of the biggest challenges in designing the graph packages&amp;rsquo; API.&lt;/p>
&lt;p>For additional fun, an extension that we can look into is the widest doublet, that is the doublet with the greatest number of solutions.&lt;/p>
&lt;p>Again, the
&lt;a href="https://www.gonum.org/code/word_ladders/words-5/main.go">lazy implementation&lt;/a> is beaten by the
&lt;a href="https://www.gonum.org/code/word_ladders/words-6/main.go">eager naive implementation&lt;/a>.&lt;/p>
&lt;pre>&lt;code>$ xtime words-5 -n 4 &amp;lt;/usr/share/dict/words &amp;gt;/dev/null
769.18u 2.34s 210.60r 2170600kB words-5 -n 4
$ xtime words-6 -n 4 &amp;lt;/usr/share/dict/words &amp;gt;/dev/null
663.70u 2.38s 157.56r 2059916kB words-6 -n 4
&lt;/code>&lt;/pre>
&lt;p>For the record, the doublet here is &amp;ldquo;stow&amp;rdquo; and &amp;ldquo;dave&amp;rdquo;, with 419 solutions.&lt;/p>
&lt;p>The full code for each of the word ladder programs is available from the links in the text or by using &lt;code>go get github.com/gonum/website/static/code/word_ladders/...&lt;/code>. It depends on Gonum version 0.8.1 which added the &lt;code>path.DijkstraAllFrom&lt;/code> function.&lt;/p>
&lt;p>&lt;em>By Dan Kortschak&lt;/em>&lt;/p></description></item><item><title>Introduction to Statistics with Gonum</title><link>https://www.gonum.org/post/intro_to_stats_with_gonum/</link><pubDate>Wed, 04 Oct 2017 11:17:11 +0200</pubDate><guid>https://www.gonum.org/post/intro_to_stats_with_gonum/</guid><description>&lt;p>&lt;em>The following was cross-posted (with minor changes) from
&lt;a href="https://sbinet.github.io/posts/2017-10-04-intro-to-stats-with-gonum/" target="_blank" rel="noopener">sbinet.github.io/posts/2017-10-04-intro-to-stats-with-gonum&lt;/a>&lt;/em>&lt;/p>
&lt;p>This is the first of a series of short posts providing an introduction and code examples for using the Gonum packages.
This first post focuses on computing basic statistics using the
&lt;a href="https://godoc.org/gonum.org/v1/gonum/stat" target="_blank" rel="noopener">stat&lt;/a> package.&lt;/p>
&lt;p>This first post is based off the content of this blog post:&lt;/p>
&lt;p>&lt;a href="https://mubaris.com/posts/statistics/">https://mubaris.com/posts/statistics/&lt;/a>&lt;/p>
&lt;p>but using
&lt;a href="https://golang.org" target="_blank" rel="noopener">Go&lt;/a> and
&lt;a href="https://www.gonum.org/">Gonum&lt;/a> instead of Python and NumPy.&lt;/p>
&lt;p>
&lt;a href="https://www.gonum.org/">Gonum&lt;/a> is &lt;em>&amp;ldquo;a set of packages designed to make writing numerical and scientific algorithms productive, performant and scalable.&amp;quot;&lt;/em>
Please refer to this
&lt;a href="https://www.gonum.org/post/intro_to_gonum">post&lt;/a> for more details on Gonum.&lt;/p>
&lt;h1 id="gonum-and-statistics">Gonum and statistics&lt;/h1>
&lt;p>Gonum provides many statistical functions.
Let&amp;rsquo;s use it to calculate the mean, median, standard deviation and variance of a small dataset.&lt;/p>
&lt;pre>&lt;code class="language-go">// file: stats.go
package main
import (
&amp;quot;fmt&amp;quot;
&amp;quot;math&amp;quot;
&amp;quot;sort&amp;quot;
&amp;quot;gonum.org/v1/gonum/stat&amp;quot;
)
func main() {
xs := []float64{
32.32, 56.98, 21.52, 44.32,
55.63, 13.75, 43.47, 43.34,
12.34,
}
fmt.Printf(&amp;quot;data: %v\n&amp;quot;, xs)
// computes the weighted mean of the dataset.
// we don't have any weights (ie: all weights are 1)
// so we just pass a nil slice.
mean := stat.Mean(xs, nil)
variance := stat.Variance(xs, nil)
stddev := math.Sqrt(variance)
// stat.Quantile needs the input slice to be sorted.
sort.Float64s(xs)
fmt.Printf(&amp;quot;data: %v (sorted)\n&amp;quot;, xs)
// computes the median of the dataset.
// here as well, we pass a nil slice as weights.
median := stat.Quantile(0.5, stat.Empirical, xs, nil)
fmt.Printf(&amp;quot;mean= %v\n&amp;quot;, mean)
fmt.Printf(&amp;quot;median= %v\n&amp;quot;, median)
fmt.Printf(&amp;quot;variance= %v\n&amp;quot;, variance)
fmt.Printf(&amp;quot;std-dev= %v\n&amp;quot;, stddev)
}
&lt;/code>&lt;/pre>
&lt;p>The program above performs some rather basic statistical operations on our dataset:&lt;/p>
&lt;pre>&lt;code class="language-sh">$&amp;gt; go run stats.go
data: [32.32 56.98 21.52 44.32 55.63 13.75 43.47 43.34 12.34]
data: [12.34 13.75 21.52 32.32 43.34 43.47 44.32 55.63 56.98] (sorted)
mean= 35.96333333333334
median= 43.34
variance= 285.306875
std-dev= 16.891029423927957
&lt;/code>&lt;/pre>
&lt;p>The astute reader will no doubt notice that the variance value displayed here
differs from the one obtained with &lt;code>numpy.var&lt;/code>:&lt;/p>
&lt;pre>&lt;code class="language-python">&amp;gt;&amp;gt;&amp;gt; xs=[32.32, 56.98, 21.52, 44.32, 55.63, 13.75, 43.47, 43.34, 12.34]
&amp;gt;&amp;gt;&amp;gt; xs.sort()
&amp;gt;&amp;gt;&amp;gt; np.mean(xs)
35.963333333333338
&amp;gt;&amp;gt;&amp;gt; np.median(xs)
43.340000000000003
&amp;gt;&amp;gt;&amp;gt; np.var(xs)
253.60611111111109
&amp;gt;&amp;gt;&amp;gt; np.std(xs)
15.925015262507948
&lt;/code>&lt;/pre>
&lt;p>This is because &lt;code>numpy.var&lt;/code> uses &lt;code>len(xs)&lt;/code> as the divisor while &lt;code>gonum/stats&lt;/code>
uses the unbiased sample variance (&lt;em>ie:&lt;/em> the divisor is &lt;code>len(xs)-1&lt;/code>):&lt;/p>
&lt;pre>&lt;code class="language-python">&amp;gt;&amp;gt;&amp;gt; np.var(xs, ddof=1)
285.30687499999999
&amp;gt;&amp;gt;&amp;gt; np.std(x, ddof=1)
16.891029423927957
&lt;/code>&lt;/pre>
&lt;p>If one wants the uncorrected estimator,
&lt;a href="https://godoc.org/gonum.org/v1/gonum/stat#Moment" target="_blank" rel="noopener">stat.Moment&lt;/a> can be used instead.&lt;/p>
&lt;p>With this quite blunt tool, we can analyse some real data from real life.
We will use a dataset pertaining to the salary of European developers, all 1147 of them :).
We have this dataset in a file named &lt;code>salary.txt&lt;/code>.&lt;/p>
&lt;pre>&lt;code class="language-go">// file: stats_salary.go
package main
import (
&amp;quot;bufio&amp;quot;
&amp;quot;fmt&amp;quot;
&amp;quot;log&amp;quot;
&amp;quot;math&amp;quot;
&amp;quot;os&amp;quot;
&amp;quot;sort&amp;quot;
&amp;quot;gonum.org/v1/gonum/stat&amp;quot;
)
func main() {
f, err := os.Open(&amp;quot;salary.txt&amp;quot;)
if err != nil {
log.Fatal(err)
}
defer f.Close()
var xs []float64
scan := bufio.NewScanner(f)
for scan.Scan() {
var v float64
txt := scan.Text()
_, err = fmt.Sscanf(txt, &amp;quot;%f&amp;quot;, &amp;amp;v)
if err != nil {
log.Fatalf(
&amp;quot;could not convert to float64 %q: %v&amp;quot;,
txt, err,
)
}
xs = append(xs, v)
}
// make sure scanning the file and extracting values
// went fine, without any error.
if err = scan.Err(); err != nil {
log.Fatalf(&amp;quot;error scanning file: %v&amp;quot;, err)
}
fmt.Printf(&amp;quot;data sample size: %v\n&amp;quot;, len(xs))
mean := stat.Mean(xs, nil)
variance := stat.Variance(xs, nil)
stddev := math.Sqrt(variance)
sort.Float64s(xs)
median := stat.Quantile(0.5, stat.Empirical, xs, nil)
fmt.Printf(&amp;quot;mean= %v\n&amp;quot;, mean)
fmt.Printf(&amp;quot;median= %v\n&amp;quot;, median)
fmt.Printf(&amp;quot;variance= %v\n&amp;quot;, variance)
fmt.Printf(&amp;quot;std-dev= %v\n&amp;quot;, stddev)
}
&lt;/code>&lt;/pre>
&lt;p>And here is the output:&lt;/p>
&lt;pre>&lt;code>$&amp;gt; go run ./stats_salary.go
data sample size: 1147
mean= 55894.53879686138
median= 48000
variance= 3.0464263289031615e+09
std-dev= 55194.44110508921
&lt;/code>&lt;/pre>
&lt;p>The data file can be obtained from here:
&lt;a href="https://www.gonum.org/code/intro_to_stats_with_gonum/salary.txt">salary.txt&lt;/a>
together with a much more detailed one there:
&lt;a href="https://www.gonum.org/code/intro_to_stats_with_gonum/salary.csv">salary.csv&lt;/a>.&lt;/p>
&lt;p>&lt;em>By Sebastien Binet&lt;/em>&lt;/p></description></item><item><title>The Gonum Numerical Computing Packages</title><link>https://www.gonum.org/post/intro_to_gonum/</link><pubDate>Sun, 09 Jul 2017 12:00:00 +0000</pubDate><guid>https://www.gonum.org/post/intro_to_gonum/</guid><description>&lt;h2 id="gonum----the-basics">Gonum &amp;ndash; The Basics&lt;/h2>
&lt;p>Gonum is a set of packages designed to make writing numeric and scientific
algorithms productive, performant, and scalable.&lt;/p>
&lt;p>Gonum is a set of packages written in the
&lt;a href="https://www.golang.org" target="_blank" rel="noopener">Go programming language&lt;/a> (more on Go later), and is
not a language on its own.
This makes Gonum similar to
&lt;a href="https://www.numpy.org" target="_blank" rel="noopener">numpy&lt;/a> and
&lt;a href="https://www.scipy.org" target="_blank" rel="noopener">scipy&lt;/a>, libraries built on top of
&lt;a href="https://www.python.org" target="_blank" rel="noopener">python&lt;/a>, and different from
&lt;a href="https://julialang.org/" target="_blank" rel="noopener">Julia&lt;/a> and
&lt;a href="https://www.mathworks.com/" target="_blank" rel="noopener">Matlab&lt;/a>
which are full programming languages.&lt;/p>
&lt;p>Gonum contains libraries for
&lt;a href="https://godoc.org/gonum.org/v1/gonum/mat" target="_blank" rel="noopener">matrices and linear algebra&lt;/a>;
&lt;a href="https://godoc.org/gonum.org/v1/gonum/stat" target="_blank" rel="noopener">statistics&lt;/a>,
&lt;a href="https://godoc.org/gonum.org/v1/gonum/stat/distuv" target="_blank" rel="noopener">probability&lt;/a>
&lt;a href="https://godoc.org/gonum.org/v1/gonum/stat/distmv" target="_blank" rel="noopener">distributions&lt;/a>,
and
&lt;a href="https://godoc.org/gonum.org/v1/gonum/stat/sampleuv" target="_blank" rel="noopener">sampling&lt;/a>; tools for
&lt;a href="https://godoc.org/gonum.org/v1/gonum/diff/fd" target="_blank" rel="noopener">function differentiation&lt;/a>,
&lt;a href="https://godoc.org/gonum.org/v1/gonum/integrate/quad" target="_blank" rel="noopener">integration&lt;/a>,
and
&lt;a href="https://godoc.org/gonum.org/v1/gonum/optimize" target="_blank" rel="noopener">optimization&lt;/a>;
&lt;a href="https://godoc.org/gonum.org/v1/gonum/graph" target="_blank" rel="noopener">network&lt;/a> creation and analysis; and more.&lt;/p>
&lt;h2 id="why-use-gonum">Why use Gonum&lt;/h2>
&lt;p>We encourage you to use Go and Gonum if&lt;/p>
&lt;ul>
&lt;li>You are tired of sluggish performance, and fighting C and vectorization.&lt;/li>
&lt;li>You are struggling with managing programs as they grow larger.&lt;/li>
&lt;li>You struggle to re-use &amp;ndash; even the code you tried to make reusable.&lt;/li>
&lt;li>You would like easy access to parallel computing.&lt;/li>
&lt;li>You want code to be fully transparent, and want the ability to read the source code you use.&lt;/li>
&lt;li>You&amp;rsquo;d like a compiler to catch mistakes early, but hate fighting linker and unintelligible compile errors.&lt;/li>
&lt;/ul>
&lt;h2 id="gonum-philosophy">Gonum Philosophy&lt;/h2>
&lt;p>The Gonum packages are designed to be simple, efficient and composable.
There is a tension, especially in scientific computing, between ease and simplicity.
Gonum,
&lt;a href="https://commandcenter.blogspot.com/2012/06/less-is-exponentially-more.html" target="_blank" rel="noopener">like the Go language itself&lt;/a>,
aims first to provide reliable and predictable code, while maintaining
ease-of-use.
Gonum is different from other popular scientific libraries, in that the Go language
does not allow operator or method overloading.
On the surface, this often means that the same algorithm requires a few more
lines of code in Go.
We fully recognize that sexy one-liners are rare with Gonum.&lt;/p>
&lt;p>We accept this cost because we believe it comes with an enormous benefit.
The Gonum function and method signatures make the algorithmic outputs clear,
and it is easy to identify the code being executed and examine the implementation.
Science is built on understanding how an experiment was performed and
what the results are.
We believe scientific code should be no different.
In fact, Gonum is partly distinguished from other libraries in that you only need
one programming language to read Gonum code (that said, it is also easy to call
into existing C and Fortran libraries).
This philosophy of simplicity has many follow-on benefits.
For example, dense one-liners can be hard to understand, creating a barrier for
others to confirm correctness, and making future modifications difficult.
Additionally, one-liners often obscure the necessary operations to execute that
line of code.
This creates a tension between code that is terse, and a targeted implementation
that is efficient.
The construction of Gonum encourages users to implement algorithms in a uniform
style, making the code easy to read and consistent between users.&lt;/p>
&lt;p>The use of Go as a base language has many benefits beyond simplicity.
Go is fast, approaching the speed of C.
Go&amp;rsquo;s language primitives are great for parallel computing, and in fact Gonum
code can often be made to run in parallel by setting a flag.
Go is designed for
&lt;a href="https://talks.golang.org/2012/splash.article" target="_blank" rel="noopener">&amp;ldquo;programming in the large&amp;rdquo;&lt;/a>,
a huge benefit for building an ecosystem of libraries that work with and build
on top of one another.
Go has extremely legible compile errors, full stack traces, trivial package downloading,
automatic makefiles and import resolution, code formatting tools, easy code documentation,
a race detector, and the list goes on.&lt;/p>
&lt;p>We believe in a new foundation for scientific computing, and we hope you&amp;rsquo;ll join us.
Please check back for more discussion on the philosophy of Gonum and the design
decisions for specific packages.&lt;/p>
&lt;h2 id="getting-started-with-gonum">Getting started with Gonum&lt;/h2>
&lt;h3 id="downloading-the-libraries">Downloading the libraries&lt;/h3>
&lt;p>First, you need to
&lt;a href="https://golang.org/doc/install" target="_blank" rel="noopener">install Go&lt;/a> if it is not
already installed on your machine.
It is recommended that you use the most recent version of Go.
Within your
&lt;a href="https://golang.org/ref/mod" target="_blank" rel="noopener">module&lt;/a> run&lt;/p>
&lt;pre>&lt;code>go get gonum.org/v1/gonum@latest
&lt;/code>&lt;/pre>
&lt;p>to download the main set of Gonum packages into the package cache and set the Gonum dependency in your go.mod file.
That&amp;rsquo;s it!&lt;/p>
&lt;p>You can test that everything is working properly from within your module with&lt;/p>
&lt;pre>&lt;code>go test gonum.org/v1/gonum
&lt;/code>&lt;/pre>
&lt;p>You may be asked to update you go.mod file by the compiler a few times; it will ask you to add dependencies with &lt;code>go get&lt;/code>.
Follow these instructions.
Running the tests will take a few minutes.
Packages should all have an &amp;ldquo;ok&amp;rdquo; or &amp;ldquo;?&amp;rdquo; next to them.
While the tests are running, please feel free to browse the source code!&lt;/p>
&lt;h3 id="learning-go">Learning Go&lt;/h3>
&lt;p>The
&lt;a href="https://tour.golang.org/" target="_blank" rel="noopener">Go tour&lt;/a> is an excellent introduction
to the Go programming language.
For quick experiments with the language, the
&lt;a href="https://play.golang.org/" target="_blank" rel="noopener">Go playground&lt;/a>
is a very nice sandbox, though unfortunately you cannot import the Gonum librares.
We also recommend reading the
&lt;a href="https://golang.org/doc/faq" target="_blank" rel="noopener">Go FAQ&lt;/a> and
&lt;a href="https://golang.org/doc/effective_go.html" target="_blank" rel="noopener">effective Go&lt;/a> pages.
If you have specific questions, check out the Go language
&lt;a href="https://golang.org/ref/spec" target="_blank" rel="noopener">specification&lt;/a>, which provides an official description
of the behavior of the language.
The Go specification is remarkably short and legible compared with other
language specifications, and a better situation than for the many languages
without an official specification.&lt;/p>
&lt;h3 id="learning-gonum">Learning Gonum&lt;/h3>
&lt;p>The best way to learn about particular packages and functions is through
the source code documentation.
The godoc
&lt;a href="https://godoc.org" target="_blank" rel="noopener">website&lt;/a> automatically generates documentation
pages from publically available source code.
For most Go packages (not just Gonum), going to &lt;code>https://godoc.org/&amp;lt;package-import-path&amp;gt;&lt;/code>
will give you documentation about that particular package.
For example, &lt;code>https://godoc.org/gonum.org/v1/gonum/mat&lt;/code> will give documentation
about Gonum&amp;rsquo;s matrix package, as well as documentation for specific functions.
Go to &lt;code>https://godoc.org/gonum.org/v1/gonum/graph/topo&lt;/code> and you will see
documentation about our graph topology routines, for example for finding the
&amp;ldquo;strongly connected components&amp;rdquo; of a graph.
Note that sometimes godoc lists functions below the relevant type, for example
the function to create a new dense matrix &lt;code>mat.NewDense&lt;/code>, is listed under the
&lt;code>Dense&lt;/code> type.
At the bottom of the index you will see a list of specific code examples,
and at the very bottom of the godoc page, you can see a list of subpackages,
if any (for instance, scroll to the bottom of &lt;code>https://godoc.org/gonum.org/v1/gonum/stat&lt;/code>).&lt;/p>
&lt;p>Please also check future posts on this blog for extended commentary on Gonum
packages and their effective use.&lt;/p>
&lt;p>For questions on the Gonum libraries, please join us at the
&lt;a href="https://groups.google.com/forum/#!forum/gonum-dev" target="_blank" rel="noopener">Gonum-dev&lt;/a> mailing list.&lt;/p>
&lt;h2 id="how-can-i-help">How can I help?&lt;/h2>
&lt;p>The Gonum contributors are a self-assembled group of scientists, engineers and
programmers from around the world.
If you&amp;rsquo;re worried that you don&amp;rsquo;t know us, don&amp;rsquo;t be!
At the time of writing, none of the primary contributors have ever met in person,
and until recently, we all lived in different time zones.
Please file issues you find at our
&lt;a href="https://github.com/gonum/gonum" target="_blank" rel="noopener">github repository&lt;/a>.
Pull requests are encouraged, please see the
&lt;a href="https://github.com/gonum/gonum/blob/master/CONTRIBUTING.md" target="_blank" rel="noopener">contributing guidelines&lt;/a>
for more details.
Small changes can be proposed directly, larger changes should be discussed at the
Gonum-dev mailing list (linked above).&lt;/p>
&lt;p>&lt;em>By Brendan Tracey&lt;/em>&lt;/p></description></item></channel></rss>