class Engine

Public Class Methods

run(hits:, pulses:) click to toggle source

The magickal euclidean algorithmical engine!

# File lib/euclidean_sequencer/engine.rb, line 4
def self.run(hits:, pulses:)
  euclid(hits, pulses)
end

Private Class Methods

distribute_and_pop_sequence(sequence, zero_sets, remainder, denominator) click to toggle source
# File lib/euclidean_sequencer/engine.rb, line 76
def self.distribute_and_pop_sequence(sequence, zero_sets, remainder, denominator)
  # Choose the approiate counter for the current size of the sequence:
  distributions = (remainder > denominator) ? denominator : remainder

  distributions.times do |distribution|
    zero_sets.times do
      sequence[distribution] << sequence.pop unless sequence[distribution].nil?
    end
  end
  sequence
end
euclid(hits, pulses, sequence = nil) click to toggle source
# File lib/euclidean_sequencer/engine.rb, line 10
def self.euclid(hits, pulses, sequence = nil)
  # Contruct the sequence if new otherwise continue with current sequence:
  sequence ||= generate_sequence(hits, pulses)

  # Determine if the algorithm has concluded or another iteration is neccessy:
  return sequence.sum('') if hits == 0

  # Count the sequence element types (remainder or denominator) for distribution:
  remainder, denominator = find_remainder_and_denominator(sequence)

  # Determine how many sets of zeros can be nabbed from the pulses at a time,
  # insuring all the zeros get used up before the algorithm ends.
  zero_sets = find_zero_sets(hits, pulses, remainder, denominator, sequence.length)

  # Distribute the remainder into the denominator and pop the unnecessary elements:
  sequence = distribute_and_pop_sequence(sequence, zero_sets, remainder, denominator)

  # Recursion:
  return euclid(pulses % hits, hits, sequence)
end
find_remainder_and_denominator(sequence) click to toggle source
# File lib/euclidean_sequencer/engine.rb, line 38
def self.find_remainder_and_denominator(sequence)
remainder, denominator = [0,0]
 sequence.each do |element|
    if element == sequence.last
      remainder = remainder + 1
    else
      denominator = denominator + 1
    end
  end
  [remainder,denominator]
end
find_zero_sets(hits, pulses, remainder, denominator, sequence_length) click to toggle source
# File lib/euclidean_sequencer/engine.rb, line 50
def self.find_zero_sets(hits, pulses, remainder, denominator, sequence_length)
  if pulses > hits * 2
    # Must avoid division by zero:
    unless hits == 0
      if denominator == 0
        zero_sets = 1
      else
        # Grab as many zeros as possible:
        zero_sets = remainder / denominator
      end
      # But don't grab as many zeros as impossible:
      if zero_sets > sequence_length
        zero_sets = 0
      end
      # Scrapping the bottom of the zero barrel:
      if zero_sets == 0 and remainder > 1
        zero_sets = 1
      end
    end
  else
    # The ratio of hits to pulses isn't so low as to start hording zeros:
    zero_sets = 1
  end
  zero_sets
end
generate_sequence(hits, pulses) click to toggle source
# File lib/euclidean_sequencer/engine.rb, line 31
def self.generate_sequence(hits, pulses)
  sequence = []
  pulses.times {|pulse| sequence[pulse] = "0"}
  hits.times { |hit| sequence[hit] = "1" }
  sequence
end