I'm trying to use some ruby code that I've found in Github. I've downloaded the code and did the necessary imports the "requires" and tried to run it as it is described in the readme file on github repository. The code is the following:
In the file pcset_test.rb the code is the following:
require './pcset.rb'
require 'test/unit'
#
# When possible, test cases are adapted from
# Introduction to Post-Tonal Theory by Joseph N. Straus,
# unless obvious or otherwise noted.
#
class PCSetTest < Test::Unit::TestCase
def test_init
#assert_raise(ArgumentError) {PCSet.new []}
assert_raise(ArgumentError) {PCSet.new [1, 2, 3, 'string']}
assert_raise(ArgumentError) {PCSet.new "string"}
assert_raise(ArgumentError) {PCSet.new [1, 2, 3.6, 4]}
assert_equal([0, 1, 2, 9], PCSet.new([0, 1, 2, 33, 13]).pitches)
assert_equal([3, 2, 1, 11, 10, 0], PCSet.new_from_string('321bac').pitches)
assert_equal([0,2,4,5,7,11,9], PCSet.new([12,2,4,5,7,11,9]).pitches)
assert_nothing_raised() {PCSet.new []}
end
def test_inversion
end
def test_transposition
end
def test_multiplication
end
#
# set normal prime forte #
# 0,2,4,7,8,11 7,8,11,0,2,4 0,1,4,5,7,9 6-31
# 0,1,2,4,5,7,11 11,0,1,2,4,5,7 0,1,2,3,5,6,8 7-Z36
# 0,1,3,5,6,7,9,10,11 5,6,7,9,10,11,0,1,3 0,1,2,3,4,6,7,8,10 9-8
#
def test_normal_form
testPC = PCSet.new [0,4,8,9,11]
assert_kind_of(PCSet, testPC.normal_form)
assert_equal([8,9,11,0,4], testPC.normal_form.pitches)
assert_equal([10,1,4,6], PCSet.new([1,6,4,10]).normal_form.pitches)
assert_equal([2,4,8,10], PCSet.new([10,8,4,2]).normal_form.pitches)
assert_equal([7,8,11,0,2,4], PCSet.new([0,2,4,7,8,11]).normal_form.pitches)
assert_equal([11,0,1,2,4,5,7], PCSet.new([0,1,2,4,5,7,11]).normal_form.pitches)
assert_equal([5,6,7,9,10,11,0,1,3], PCSet.new([0,1,3,5,6,7,9,10,11]).normal_form.pitches)
end
def test_prime_form
assert_equal([0,1,2,6], PCSet.new([5,6,1,7]).prime.pitches)
assert_equal([0,1,4], PCSet.new([2,5,6]).prime.pitches)
assert_equal([0,1,4,5,7,9], PCSet.new([0,2,4,7,8,11]).prime.pitches)
assert_equal([0,1,2,3,5,6,8], PCSet.new([0,1,2,4,5,7,11]).prime.pitches)
assert_equal([0,1,2,3,4,6,7,8,10], PCSet.new([0,1,3,5,6,7,9,10,11]).prime.pitches)
end
def test_set_class
testPcs = PCSet.new([2,5,6])
testPrime = testPcs.prime
assert_equal([
[2,5,6], [3,6,7], [4,7,8], [5,8,9], [6,9,10], [7,10,11],
[8,11,0],[9,0,1], [10,1,2],[11,2,3],[0,3,4], [1,4,5],
[6,7,10],[7,8,11],[8,9,0], [9,10,1],[10,11,2],[11,0,3],
[0,1,4], [1,2,5], [2,3,6], [3,4,7], [4,5,8], [5,6,9]
].sort, PCSet.new([2,5,6]).set_class.map{|x| x.pitches})
assert_equal(testPcs.set_class.map{|x| x.pitches}, testPrime.set_class.map{|x| x.pitches})
end
def test_interval_vector
assert_equal([2,1,2,1,0,0], PCSet.new([0,1,3,4]).interval_vector)
assert_equal([2,5,4,3,6,1], PCSet.new([0,1,3,5,6,8,10]).interval_vector)
assert_equal([0,6,0,6,0,3], PCSet.new([0,2,4,6,8,10]).interval_vector)
end
def test_complement
assert_equal([6,7,8,9,10,11], PCSet.new([0,1,2,3,4,5]).complement.pitches)
assert_equal([3,4,5], PCSet.new([0,1,2], 6).complement.pitches)
end
#
# Test values from (Morris 1991), pages 105-111
# Citation:
# Morris. Class Notes for Atonal Music Theory
# Lebanon, NH. Frog Peak Music, 1991.
#
def test_invariance_vector
assert_equal([1,0,0,0,5,6,5,5],PCSet.new([0,2,5]).invariance_vector)
assert_equal([2,2,2,2,6,6,6,6],PCSet.new([0,1,6,7]).invariance_vector)
assert_equal([6,6,6,6,6,6,6,6],PCSet.new([0,2,4,6,8,10]).invariance_vector)
assert_equal([1,0,0,0,0,0,0,0],PCSet.new([0,1,2,3,4,5,8]).invariance_vector)
assert_equal([1,0,0,1,0,0,0,0],PCSet.new([0,1,2,3,5,6,8]).invariance_vector)
assert_equal([12,12,12,12,0,0,0,0],PCSet.new([0,1,2,3,4,5,6,7,8,9,10,11]).invariance_vector)
end
#
# Test values from (Huron 1994). Huron rounds, thus the 0.01 margin of error.
# Citation:
# Huron. Interval-Class Content in Equally Tempered Pitch-Class Sets:
# Common Scales Exhibit Optimum Tonal Consonance.
# Music Perception (1994) vol. 11 (3) pp. 289-305
#
def test_huron
h1 = PCSet.new([0,1,2,3,4,5,6,7,8,9,10,11]).huron
assert_in_delta(-0.2, h1[0], 0.01)
assert_in_delta(0.21, h1[1], 0.01)
h2 = PCSet.new([0,2,4,5,7,9,11]).huron
assert_in_delta(4.76, h2[0], 0.01)
assert_in_delta(0.62, h2[1], 0.01)
end
def test_coherence
end
end
And in the file pcset.rb the folloing code:
#
# => PCSet Class for Ruby
# => Beau Sievers
# => Hanover, Fall 2008.
#
#
# TODO: Make this a module to avoid namespace collisions.
# Lilypond and MusicXML output
#
include Math
def choose(n, k)
return [[]] if n.nil? || n.empty? && k == 0
return [] if n.nil? || n.empty? && k > 0
return [[]] if n.size > 0 && k == 0
c2 = n.clone
c2.pop
new_element = n.clone.pop
choose(c2, k) + append_all(choose(c2, k-1), new_element)
end
def append_all(lists, element)
lists.map { |l| l << element }
end
def array_to_binary(array)
array.inject(0) {|sum, n| sum + 2**n}
end
# the following method is horrifically inelegant
# but avoids monkey-patching.
# TODO: do this right, incl. error checking
def pearsons(x, y)
if !x.is_a?(Array) || !y.is_a?(Array) then raise StandardError, "x and y must be arrays", caller end
if x.size != y.size then raise StandardError, "x and y must be same size", caller end
sum_x = x.inject(0) {|sum, n| sum + n}
sum_y = y.inject(0) {|sum, n| sum + n}
sum_square_x = x.inject(0) {|sum, n| sum + n * n}
sum_square_y = y.inject(0) {|sum, n| sum + n * n}
xy = []
x.zip(y) {|a, b| xy.push(a * b)}
sum_xy = xy.inject(0) {|sum, n| sum + n}
num = sum_xy - ((sum_x * sum_y)/x.size)
den = Math.sqrt((sum_square_x - ((sum_x*sum_x)/x.size)) * (sum_square_y - ((sum_y*sum_y)/x.size)))
(num/den)
end
class PCSet
include Comparable
attr_reader :pitches, :base, :input
def initialize(pcarray, base = 12)
if pcarray.instance_of?(Array) && pcarray.all?{|pc| pc.instance_of?(Fixnum)}
#base, #input = base, pcarray
#pitches = pcarray.map{ |x| x % #base }.uniq
else
raise ArgumentError, "Improperly formatted PC array", caller
end
end
def PCSet.new_from_string(pcstring, base = 12)
if base > 36 then raise StandardError, "Use PCSet.new to create pcsets with a base larger than 36", caller end
pcarray = []
pcstring.downcase.split(//).each do |c|
if c <= 'z' and c >= '0' then pcarray.push(c.to_i(36)) end
end
PCSet.new pcarray, base
end
def <=>(pcs)
#pitches <=> pcs.pitches
end
def [](index)
#pitches[index]
end
# Intersection
def &(other)
PCSet.new #pitches & other.pitches
end
# Union
def |(other)
PCSet.new #pitches | other.pitches
end
def inspect
#pitches.inspect
end
def length
#pitches.length
end
def invert(axis = 0)
PCSet.new #pitches.map {|x| (axis-x) % #base}
end
def invert!(axis = 0)
#pitches.map! {|x| (axis-x) % #base}
end
def transpose(interval)
PCSet.new #pitches.map {|x| (x + interval) % #base}
end
def transpose!(interval)
#pitches.map! {|x| (x + interval) % #base}
end
def multiply(m = 5)
PCSet.new #pitches.map {|x| (x * m) % #base}
end
def multiply!(m = 5)
#pitches.map! {|x| (x * m) % #base}
end
def zero
transpose(-1 * #pitches[0])
end
def zero!
transpose!(-1 * #pitches[0])
end
def transpositions
(0..(#base-1)).to_a.map{|x| #pitches.map {|y| (y + x) % #base}}.sort.map {|x| PCSet.new x}
end
def transpositions_and_inversions(axis = 0)
transpositions + invert(axis).transpositions
end
#
# Normal form after Straus. Morris and AthenaCL do this differently.
#
def normal_form
tempar = #pitches.sort
arar = [] # [[1,4,7,8,10],[4,7,8,10,1], etc.] get each cyclic variation
tempar.each {arar.push PCSet.new(tempar.unshift(tempar.pop))}
most_left_compact(arar)
end
def normal_form!
#pitches = normal_form.pitches
end
def is_normal_form?
self.pitches == self.normal_form.pitches
end
def set_class
transpositions_and_inversions.map{|pcs| pcs.normal_form}.sort
end
def prime
most_left_compact([normal_form.zero, invert.normal_form.zero])
end
def prime!
self.pitches = self.prime.pitches
end
def is_prime?
self.pitches == self.prime.pitches
end
def complement
new_pitches = []
#base.times do |p|
if !#pitches.include? p then
new_pitches.push p
end
end
PCSet.new new_pitches
end
def full_interval_vector
pairs = choose(#pitches, 2) # choose every pc pair
intervals = pairs.map {|x| (x[1] - x[0]) % #base} # calculate every interval
i_vector = Array.new(#base-1).fill(0)
intervals.each {|x| i_vector[x-1] += 1} # count the intervals
i_vector
end
def interval_vector
i_vector = full_interval_vector
(0..((#base-1)/2)-1).each {|x| i_vector[x] += i_vector.pop}
i_vector
end
#
# Morris's invariance vector
#
def invariance_vector(m = 5)
t = transpositions.map!{|pcs| self & pcs}
ti = invert.transpositions.map!{|pcs| self & pcs}
tm = multiply(m).transpositions.map!{|pcs| self & pcs}
tmi = invert.multiply(m).transpositions.map!{|pcs| self & pcs}
tc = complement.transpositions.map!{|pcs| self & pcs}
tic = complement.invert.transpositions.map!{|pcs| self & pcs}
tmc = complement.multiply(m).transpositions.map!{|pcs| self & pcs}
tmic = complement.invert.multiply(m).transpositions.map!{|pcs| self & pcs}
[t, ti, tm, tmi, tc, tic, tmc, tmic].map{|x| x.reject{|pcs| pcs.pitches != #pitches}.length}
end
# Huron's aggregate dyadic consonance measure.
# Huron. Interval-Class Content in Equally Tempered Pitch-Class Sets:
# Common Scales Exhibit Optimum Tonal Consonance.
# Music Perception (1994) vol. 11 (3) pp. 289-305
def huron
if #base != 12 then raise StandardError, "PCSet.huron only makes sense for mod 12 pcsets", caller end
# m2/M7 M2/m7 m3/M6 M3/m6 P4/P5 A4/d5
huron_table = [-1.428, -0.582, 0.594, 0.386, 1.240, -0.453]
interval_consonance = []
interval_vector.zip(huron_table) {|x, y| interval_consonance.push(x * y) }
aggregate_dyadic_consonance = interval_consonance.inject {|sum, n| sum + n}
[aggregate_dyadic_consonance, pearsons(interval_vector, huron_table)]
end
#
# Balzano's vector of relations. Citation for all Balzano methods:
#
# Balzano. "The Pitch Set as a Level of Description for Studying Musical
# Pitch Perception" in Music, Mind, and Brain ed. Clynes. Plenum Press. 1982.
#
def vector_of_relations
(0..length-1).to_a.map do |i|
(0..length-1).to_a.map do |j|
(#pitches[(i + j) % length] - #pitches[i]) % #base
end
end
end
#
# Checks if the set satisfies Balzano's uniqueness.
#
def is_unique?
vector_of_relations.uniq.size == vector_of_relations.size
end
#
# Checks if the set satisfies Balzano's scalestep-semitone coherence.
# For all s[i] and s[i1]:
# j < k => v[i][j] < v[i1][k]
# Where j and k are scalestep-counting indices.
# And unless v[i][j] == 6 (a tritone), in which case the strict inequality is relaxed.
#
def is_coherent?
v = vector_of_relations
truth_array = []
all_pair_indices = choose((0..length-1).to_a, 2)
all_pair_indices.each do |i, i1|
all_pair_indices.each do |j, k|
if v[i][j] == 6
truth_array.push(v[i][j] <= v[i1][k])
else
truth_array.push(v[i][j] < v[i1][k])
end
if v[i1][j] == 6
truth_array.push(v[i1][j] <= v[i][k])
else
truth_array.push(v[i1][j] < v[i][k])
end
end
end
!truth_array.include?(false)
end
#
# Strict Balzano coherence, no inequality relaxation for tritones.
#
def is_strictly_coherent?
v = vector_of_relations
truth_array = []
all_pair_indices = choose((0..length-1).to_a, 2)
all_pair_indices.each do |i, i1|
all_pair_indices.each do |j, k|
truth_array.push(v[i][j] < v[i1][k])
truth_array.push(v[i1][j] < v[i][k])
end
end
!truth_array.include?(false)
end
def notes(middle_c = 0)
noteArray = ['C','C#','D','D#','E','F','F#','G','G#','A','A#','B']
if #base != 12 then raise StandardError, "PCSet.notes only makes sense for mod 12 pcsets", caller end
out_string = String.new
transpose(-middle_c).pitches.each do |p|
out_string += noteArray[p] + ", "
end
out_string.chop.chop
end
def info
print "modulo: #{#base}\n"
print "raw input: #{#input.inspect}\n"
print "pitch set: #{#pitches.inspect}\n"
print "notes: #{notes}\n"
print "normal: #{normal_form.inspect}\n"
print "prime: #{prime.inspect}\n"
print "interval vector: #{interval_vector.inspect}\n"
print "invariance vector: #{invariance_vector.inspect}\n"
print "huron ADC: #{huron[0]} pearsons: #{huron[1]}\n"
print "balzano coherence: "
if is_strictly_coherent?
print "strictly coherent\n"
elsif is_coherent?
print "coherent\n"
else
print "false\n"
end
end
# def lilypond
#
# end
#
# def musicXML
#
# end
###############################################################################
private
#
# Convert every pitch array to a binary representation, e.g.:
# [0,2,4,8,10] -> 010100010101
# 2^n: BA9876543210
# The smallest binary number is the most left-compact.
#
def most_left_compact(pcset_array)
if !pcset_array.all? {|pcs| pcs.length == pcset_array[0].length}
raise ArgumentError, "PCSet.most_left_compact: All PCSets must be of same cardinality", caller
end
zeroed_pitch_arrays = pcset_array.map {|pcs| pcs.zero.pitches}
binaries = zeroed_pitch_arrays.map {|array| array_to_binary(array)}
winners = []
binaries.each_with_index do |num, i|
if num == binaries.min then winners.push(pcset_array[i]) end
end
winners.sort[0]
end
end
I'm calling them as follows:
> my_pcset = PCSet.new([0,2,4,6,8,10])
> my_pcset2 = PCSet.new([1,5,9])
It shoud return:
> my_pcset = PCSet.new([0,2,4,6,8,10])
=> [0, 2, 4, 6, 8, 10]
> my_pcset2 = PCSet.new([1,5,9])
=> [1, 5, 9]
But is returning nothing.
The code is available on github
Thanks
Try this in terminal: irb -r ./path_to_directory/pcset.rb and then initialize the objects.
I think the documentation for the repo is bad as it does not explain how you should be running this.
The result of
my_pcset = PCSet.new([0,2,4,6,8,10])
should set my_pcset to an instance of a PCSet not an array, so these lines from the README file are confusing at best.
3. How to use it
Make new PCSets:
my_pcset = PCSet.new([0,2,4,6,8,10])
=> [0, 2, 4, 6, 8, 10]
my_pcset2 = PCSet.new([1,5,9])
=> [1, 5, 9]
Looking at the code, I see inspect has been delegated to #pitches
def inspect
#pitches.inspect
end
I think if you inspect my_pcset you will get the expected result.
my_pcset = PCSet.new([0,2,4,6,8,10])
p my_pcset # will print [0, 2, 4, 6, 8, 10]
or `my_pcset.inspect` will return what you are expecting.
As it currently stands, this question is not a good fit for our Q&A format. We expect answers to be supported by facts, references, or expertise, but this question will likely solicit debate, arguments, polling, or extended discussion. If you feel that this question can be improved and possibly reopened, visit the help center for guidance.
Closed 10 years ago.
What are your favorite code snippets with ruby collections? Preferably they should be discovery for you, be expressive, readable and introduce some fun in your coding practice.
Pattern-matching in arrays (for local variables and parameters):
(a, b), c = [[:a, :b], :c]
[a,b,c]
=> [:a, :b, :c]
(a,), = [[:a]]
a
=> :a
Assigning from non-arrays to multiple variables:
abc, a, b =* "abc".match(/(a)(b)./)
=> ["abc", "a", "b"]
nil1, =* "abc".match(/xyz/)
=> []
Initialize array elements with the same expression:
5.times.map { 1 }
=> [1,1,1,1]
Array.new(5) { 1 }
=> [1,1,1,1,1]
Initialize array with the same value:
[2]*5
=>[2,2,2,2,2]
Array.new 5, 2
=>[2,2,2,2,2]
Sum elements of an array:
[1,2,3].reduce(0, &:+)
=> 6
Find all indices that match condition:
a.each_with_index.find_all { |e, i| some_predicate(e) }.map(&:last)
Alternate CSS classes:
(1..4).zip(%w[cls1 cls2].cycle)
=> [[1, "cls1"], [2, "cls2"], [3, "cls1"], [4, "cls2"]]
Unzipping:
keys, values = {a: 1, b: 2}.to_a.transpose
keys
=> [:a, :b]
Exploring boolean member methods of a string:
"".methods.sort.grep(/\?/)
Exploring string-specific methods:
"".methods.sort - [].methods
Lazy Fibonacci series with memoization, taken from Neeraj Singh:
fibs = { 0 => 0, 1 => 1 }.tap do |fibs|
fibs.default_proc = ->(fibs, n) { fibs[n] = fibs[n-1] + fibs[n-2] }
end
fibs.take(10).map(&:last).each(&method(:puts))
An implementation of Counting Sort:
module Enumerable
def counting_sort(k)
reduce(Array.new(k+1, 0)) {|counting, n| counting.tap { counting[n] += 1 }}.
map.with_index {|count, n| [n] * count }.flatten
end
end
An implementation of sum aka prefix sum:
module Enumerable
def scan(initial=nil, sym=nil, &block)
args = if initial then [initial] else [] end
unless block_given?
args, sym, initial = [], initial, first unless sym
block = ->(acc, el) { acc.send(sym, el) }
end
[initial || first].tap {|res|
reduce(*args) {|acc, el|
block.(acc, el).tap {|e|
res << e
}
}
}
end
end
Here, I experimented with having Hash#each yield KeyValuePairs instead of two-element Arrays. It's quite surprising, how much code still works, after doing such a brutal monkey-patch. Yay, duck typing!
class Hash
KeyValuePair = Struct.new(:key, :value) do
def to_ary
return key, value
end
end
old_each = instance_method(:each)
define_method(:each) do |&blk|
old_each.bind(self).() do |k, v|
blk.(KeyValuePair.new(k, v))
end
end
end
Something I have been playing around with is making Enumerable#=== perform recursive structural pattern matching. I have no idea if this is in any way useful. I don't even know if it actually works.
module Enumerable
def ===(other)
all? {|el|
next true if el.nil?
begin
other.any? {|other_el| el === other_el }
rescue NoMethodError => e
raise unless e.message =~ /any\?/
el === other
end
}
end
end
Another thing I toyed around with recently was re-implementing all methods in Enumerable, but using reduce instead of each as the basis. In this case, I know it doesn't actually work properly.
module Enumerable
def all?
return reduce(true) {|res, el| break false unless res; res && el } unless block_given?
reduce(true) {|res, el| break false unless res; res && yield(el) }
end
def any?
return reduce(false) {|res, el| break true if res || el } unless block_given?
reduce(false) {|res, el| break true if res || yield(el) }
end
def collect
reduce([]) {|res, el| res << yield(el) }
end
alias_method :map, :collect
def count(item=undefined = Object.new)
return reduce(0) {|res, el| res + 1 if el == item } unless undefined.equal?(item)
unless block_given?
return size if respond_to? :size
return reduce(0) {|res, el| res + 1 }
end
reduce(0) {|res, el| res + 1 if yield el }
end
def detect(ifnone=nil)
reduce(ifnone) {|res, el| if yield el then el end unless res }
end
alias_method :find, :detect
def drop(n=1)
reduce([]) {|res, el| res.tap { res << el unless n -= 1 >= 0 }}
end
def drop_while
reduce([]) {|res, el| res.tap { res << el unless yield el }}
end
def each
tap { reduce(nil) {|_, el| yield el }}
end
def each_with_index
tap { reduce(-1) {|i, el| (i+1).tap {|i| yield el, i }}}
end
def find_all
reduce([]) {|res, el| res.tap {|res| res << el if yield el }}
end
alias_method :select, :find_all
def find_index(item=undefined = Object.new)
return reduce(-1) {|res, el| break res + 1 if el == item } unless undefined.equals?(item)
reduce(-1) {|res, el| break res + 1 if yield el }
end
def grep(pattern)
return reduce([]) {|res, el| res.tap {|res| res << el if pattern === el }} unless block_given?
reduce([]) {|res, el| res.tap {|res| res << yield(el) if pattern === el }}
end
def group_by
reduce(Hash.new {|hsh, key| hsh[key] = [] }) {|res, el| res.tap { res[yield el] = el }}
end
def include?(obj)
reduce(false) {|res, el| break true if res || el == obj }
end
def reject
reduce([]) {|res, el| res.tap {|res| res << el unless yield el }}
end
end
Initialize multiple values from an array:
a = [1,2,3]
b, *c = a
assert_equal [b, c], [1, [2,3]]
d, = a
assert_equal d, a[0]
My own are:
Initialize array elements with same expression:
5.times.map { some_expression }
Initialize array with same value:
[value]*5
Sum elements of an array:
[1,2,3].reduce(0, &:+)
Find all indices that match condition:
a.each_with_index.find_all { |e, i| some_predicate(e) }.map(&:last)
Not really snippets, but I like these generic constructions (I show only how to use them, the implementation is easily found on the web).
Conversion Array -> Hash (to_hash or mash, the idea is the same, see Facets implementation):
>> [1, 2, 3].mash { |k| [k, 2*k] }
=> {1=>2, 2=>4, 3=>6}
Map + select/detect: You want to do a map and get only the first result (so a map { ... }.first would inefficient):
>> [1, 2, 3].map_select { |k| 2*k if k > 1 }
=> [4, 6]
>> [1, 2, 3].map_detect { |k| 2*k if k > 1 }
=> 4
Lazy iterations (lazy_map, lazy_select, ...). Example:
>> 1.upto(1e100).lazy_map { |x| 2 *x }.first(5)
=> [2, 4, 6, 8, 10]
Count the number of items that meet either one condition or another:
items.count do |item|
next true unless first_test?(item)
next true unless second_test?(item)
false
end
count means you don't have to do i = 0 and i += 1.
next means that you can finish that iteration of the block and still supply the answer, rather than hanging around until the end.
(If you wanted, you could replace the last two lines of the block with the single line ! second_test?(item), but that'd make it look messier)
Exploring boolean member methods of a string:
"".methods.sort.grep(/\?/)
Exploring string-specific methods:
"".methods.sort - [].methods