two_chainz 0.0.0 → 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +21 -0
- data/README.md +69 -0
- data/lib/two_chainz.rb +2 -5
- data/lib/two_chainz/generator.rb +110 -0
- data/lib/two_chainz/version.rb +3 -0
- data/lib/two_chainz/words_table.rb +79 -0
- metadata +8 -3
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Copyright 2013 Jake Boxer
|
2
|
+
http://jakeboxer.com/
|
3
|
+
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
a copy of this software and associated documentation files (the
|
6
|
+
"Software"), to deal in the Software without restriction, including
|
7
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
# Two Chainz
|
2
|
+
|
3
|
+
> She got a big booty so I call her Big Booty
|
4
|
+
> Scrr... scrr... wrists moving, cooking, getting to it
|
5
|
+
> I'm in the kitchen, yams everywhere
|
6
|
+
> Just made a jug, I got bands everywhere
|
7
|
+
>
|
8
|
+
– [2 Chainz - Birthday Song](http://rapgenius.com/2-chainz-birthday-song-lyrics) (which was generated entirely with this gem)
|
9
|
+
|
10
|
+
**Two Chainz** is a Ruby gem for generating random sentences with [Markov chains](http://en.wikipedia.org/wiki/Markov_chain).
|
11
|
+
|
12
|
+
## Quickstart
|
13
|
+
|
14
|
+
``` ruby
|
15
|
+
require 'two_chainz'
|
16
|
+
|
17
|
+
generator = TwoChainz::Generator.new
|
18
|
+
|
19
|
+
generator.hear("We just want the credit where it's due")
|
20
|
+
generator.hear("Imma worry bout me, give a fuck about you")
|
21
|
+
generator.hear("Just as a reminder to myself")
|
22
|
+
generator.hear("I wear every single chain even when I'm in the house")
|
23
|
+
|
24
|
+
generator.spit(:words => 12) # => "We just as a fuck chain even when I'm in the credit"
|
25
|
+
```
|
26
|
+
|
27
|
+
## Less important shit
|
28
|
+
|
29
|
+
If you instantiate the generator normally, spitting will be random.
|
30
|
+
|
31
|
+
``` ruby
|
32
|
+
generator = TwoChainz::Generator.new
|
33
|
+
|
34
|
+
generator.hear("I love you I love you")
|
35
|
+
generator.hear("You are alright I guess")
|
36
|
+
|
37
|
+
generator.spit(:words => 2) # => "I guess"
|
38
|
+
generator.spit(:words => 2) # => "You I"
|
39
|
+
```
|
40
|
+
|
41
|
+
You can seed the generator if you want consistency.
|
42
|
+
|
43
|
+
``` ruby
|
44
|
+
generator = TwoChainz::Generator.new(:seed => 3)
|
45
|
+
```
|
46
|
+
|
47
|
+
If you want even more consistency, run it in boring mode. This will always continue the chain the same way: the most common next word is picked every time, and the alphabetically-first word is picked if there's a tie.
|
48
|
+
|
49
|
+
``` ruby
|
50
|
+
generator = TwoChainz::Generator.new(:boring => true)
|
51
|
+
|
52
|
+
generator.hear("I love you I love you I love you")
|
53
|
+
generator.hear("You are alright I guess")
|
54
|
+
|
55
|
+
generator.spit(:words => 2) # => "I love"
|
56
|
+
generator.spit(:words => 2) # => "I love"
|
57
|
+
```
|
58
|
+
|
59
|
+
Instead of specifying how many words you want, you can specify a maximum number of characters. You'll get a sentence that is guaranteed to be that many characters or fewer (and it will usually come pretty close).
|
60
|
+
|
61
|
+
``` ruby
|
62
|
+
generator = TwoChainz::Generator.new
|
63
|
+
|
64
|
+
generator.hear("Once they caught us off-guard")
|
65
|
+
generator.hear("The Mac-10 was in the grass and")
|
66
|
+
generator.hear("I ran like a cheetah with thoughts of an assassin")
|
67
|
+
|
68
|
+
generator.spit(:max_chars => 20) # => "Once they cheetah"
|
69
|
+
```
|
data/lib/two_chainz.rb
CHANGED
@@ -0,0 +1,110 @@
|
|
1
|
+
class TwoChainz::Generator
|
2
|
+
# Public: Create a new TwoChainz::Generator instance.
|
3
|
+
#
|
4
|
+
# options - Hash of options.
|
5
|
+
# :seed - (Optional integer) Seed to use when spitting. If nothing
|
6
|
+
# is provided, Ruby core's Random::new_seed will be used.
|
7
|
+
# :boring - (Optional boolean) Don't do any randomness. Always pick
|
8
|
+
# the most common thing. Mainly for testing. Defaults to
|
9
|
+
# false.
|
10
|
+
#
|
11
|
+
# Returns a TwoChainz::Generator
|
12
|
+
def initialize(options={})
|
13
|
+
@words_table = TwoChainz::WordsTable.new
|
14
|
+
|
15
|
+
unless options[:boring]
|
16
|
+
seed = options[:seed]
|
17
|
+
@random = seed ? Random.new(seed) : Random.new
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Public: Hear some words and remember them for future spitting.
|
22
|
+
#
|
23
|
+
# words - String of words to hear.
|
24
|
+
#
|
25
|
+
# Returns the number of unique new words the generator heard (integer)
|
26
|
+
def hear(words)
|
27
|
+
heard_words = 0
|
28
|
+
previous_word = nil
|
29
|
+
|
30
|
+
words.scan(/[\w\']+/) do |current_word|
|
31
|
+
# If we haven't heard this word before, increment the newly-heard words
|
32
|
+
# count.
|
33
|
+
heard_words += 1 unless @words_table.include?(current_word)
|
34
|
+
|
35
|
+
# Increment the number of times the current word has been the successor of
|
36
|
+
# the previous word.
|
37
|
+
@words_table.increment(previous_word, current_word)
|
38
|
+
|
39
|
+
previous_word = current_word
|
40
|
+
end
|
41
|
+
|
42
|
+
# Record what the last word was.
|
43
|
+
@words_table.increment(previous_word) if previous_word
|
44
|
+
|
45
|
+
heard_words
|
46
|
+
end
|
47
|
+
|
48
|
+
# Public: Produce a randomized sentence based on the words that have been
|
49
|
+
# heard.
|
50
|
+
#
|
51
|
+
# options - Hash of options. At least one is required.
|
52
|
+
# :words - (Integer) the number of words to be generated.
|
53
|
+
# :max_chars - (Integer) the maximum number of characters to be
|
54
|
+
# generated.
|
55
|
+
#
|
56
|
+
# Returns a string.
|
57
|
+
def spit(options = {})
|
58
|
+
if @words_table.words.empty?
|
59
|
+
raise StandardError, "The generator hasn't heard anything yet"
|
60
|
+
end
|
61
|
+
|
62
|
+
words = options[:words] && Integer(options[:words])
|
63
|
+
max_chars = options[:max_chars] && Integer(options[:max_chars])
|
64
|
+
|
65
|
+
sentence = []
|
66
|
+
|
67
|
+
if words
|
68
|
+
words.times do |i|
|
69
|
+
previous_word = sentence.last || :beginning
|
70
|
+
sentence << word_after(previous_word)
|
71
|
+
end
|
72
|
+
elsif max_chars
|
73
|
+
sentence_length = -1 # Start at -1 cuz of the first space
|
74
|
+
|
75
|
+
while sentence_length < max_chars
|
76
|
+
previous_word = sentence.last || :beginning
|
77
|
+
word = word_after(previous_word)
|
78
|
+
sentence_length += word.length + 1 # Include space
|
79
|
+
|
80
|
+
sentence << word_after(previous_word) unless sentence_length > max_chars
|
81
|
+
end
|
82
|
+
else
|
83
|
+
raise ArgumentError, "Either :words or :max_chars must be specified"
|
84
|
+
end
|
85
|
+
|
86
|
+
sentence.join(' ')
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
# Internal: Get a word that comes after the specified one.
|
92
|
+
#
|
93
|
+
# previous_word - The word that will be coming before whichever one we pick.
|
94
|
+
#
|
95
|
+
# Returns a string.
|
96
|
+
def word_after(previous_word)
|
97
|
+
choices = @words_table.words_after(previous_word)
|
98
|
+
|
99
|
+
# Pick the most popular next word.
|
100
|
+
# TODO(jakeboxer): Make this random in non-boring situations.
|
101
|
+
next_word = choices.max_by {|word, count| count}.first
|
102
|
+
|
103
|
+
# If the most popular next word is the sentence ending, pick the
|
104
|
+
# alphabetical first word.
|
105
|
+
# TODO(jakeboxer): Make this random in non-boring situations.
|
106
|
+
next_word = heard_words.sort.first if next_word == :ending
|
107
|
+
|
108
|
+
next_word
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
class TwoChainz::WordsTable
|
2
|
+
# Public: Create a new TwoChainz::WordsTable instance.
|
3
|
+
#
|
4
|
+
# Returns a TwoChainz::WordsTable
|
5
|
+
def initialize
|
6
|
+
# The table is a hash. Think of its entries as "rows".
|
7
|
+
# Each row is another hash. Think of its entries as "cells".
|
8
|
+
# Cells default to 0.
|
9
|
+
@table = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
# Public: Increment the value in the table for the specified pair of words.
|
13
|
+
#
|
14
|
+
# first_word - The word that comes first in the pair. If this is nil, the
|
15
|
+
# second word in the pair will be recorded as the beginning of
|
16
|
+
# a sentence.
|
17
|
+
# second_word - The word that comes last in the pair. If this is nil or
|
18
|
+
# omitted, the first word in the pair will be recorded as the
|
19
|
+
# ending of a sentence.
|
20
|
+
#
|
21
|
+
# Returns the new count of the entry.
|
22
|
+
def increment(first_word=nil, second_word=nil)
|
23
|
+
# Don't do anything if we didn't get a first or second word
|
24
|
+
return 0 unless first_word || second_word
|
25
|
+
|
26
|
+
# Only one of these will run, since we error if both are nil
|
27
|
+
first_word ||= :beginning
|
28
|
+
second_word ||= :ending
|
29
|
+
|
30
|
+
add_row(first_word)
|
31
|
+
add_row(second_word) unless second_word == :ending
|
32
|
+
|
33
|
+
@table[first_word][second_word] += 1
|
34
|
+
end
|
35
|
+
|
36
|
+
# Public: Whether or not the words table has the specified word.
|
37
|
+
#
|
38
|
+
# word - Word to check for inclusion of.
|
39
|
+
#
|
40
|
+
# Returns a boolean
|
41
|
+
def include?(word)
|
42
|
+
@table.keys.include?(word)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Public: Get all the words that have been incremented at least once.
|
46
|
+
#
|
47
|
+
# Returns an array.
|
48
|
+
def words
|
49
|
+
@table.keys - [:beginning]
|
50
|
+
end
|
51
|
+
|
52
|
+
# Public: Get all the words that have come after the specified word. In the
|
53
|
+
# result, the keys are the words and the values are the number of times that
|
54
|
+
# word has appeared after the specified word.
|
55
|
+
#
|
56
|
+
# Can also include the :ending key, which indicates that the sentence ended
|
57
|
+
# after that word.
|
58
|
+
#
|
59
|
+
# word - Word to find all following words for
|
60
|
+
#
|
61
|
+
# Examples
|
62
|
+
#
|
63
|
+
# increment('your', 'love')
|
64
|
+
# increment('your', 'time')
|
65
|
+
# increment('your', 'love')
|
66
|
+
# words_after('your')
|
67
|
+
# # => {"love" => 2, "time" => 1}
|
68
|
+
#
|
69
|
+
# Returns a hash.
|
70
|
+
def words_after(word)
|
71
|
+
Hash[@table[word] || {}]
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def add_row(word)
|
77
|
+
@table[word] ||= Hash.new(0)
|
78
|
+
end
|
79
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: two_chainz
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -18,7 +18,12 @@ executables: []
|
|
18
18
|
extensions: []
|
19
19
|
extra_rdoc_files: []
|
20
20
|
files:
|
21
|
+
- lib/two_chainz/generator.rb
|
22
|
+
- lib/two_chainz/version.rb
|
23
|
+
- lib/two_chainz/words_table.rb
|
21
24
|
- lib/two_chainz.rb
|
25
|
+
- LICENSE
|
26
|
+
- README.md
|
22
27
|
homepage: https://github.com/jakeboxer/two_chainz
|
23
28
|
licenses: []
|
24
29
|
post_install_message:
|
@@ -30,13 +35,13 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
30
35
|
requirements:
|
31
36
|
- - ! '>='
|
32
37
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
38
|
+
version: 1.9.2
|
34
39
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
35
40
|
none: false
|
36
41
|
requirements:
|
37
42
|
- - ! '>='
|
38
43
|
- !ruby/object:Gem::Version
|
39
|
-
version:
|
44
|
+
version: 1.3.6
|
40
45
|
requirements: []
|
41
46
|
rubyforge_project:
|
42
47
|
rubygems_version: 1.8.23
|