Class: One::Pivoter

Inherits:
Object
  • Object
show all
Includes:
Observable
Defined in:
lib/one/pivoter.rb

Overview

Class that can be used for mining data from lists of objects.

Instance Method Summary collapse

Instance Method Details

#multi_pivot(list, *pivots) ⇒ Hash

Runs multiple pivots against a list of Objects.

Examples:

Multi-pivot a list of numbers

list = [1,2,3,4,5,6,7,8,9]
pivots = []

pivots << lambda do |i|
  key = "less than or equal to 5" if i <= 5
  key ||= "greater than 5"
end

pivots << lambda do |i|
  key = "greater than or equal to 3" if i >= 3
  key ||= "less than 3"
end

# note the last pivot is an options Hash
pivots << {:delimiter => " & "}

pivoter = One::Pivot.new
result = pivoter.multi_pivot(list, *pivots)

# the result will be a Hash with the following structure
{
  "less than or equal to 5 & greater than or equal to 3" => [3, 4, 5],
  "less than or equal to 5 & less than 3" => [1, 2],
  "greater than 5 & greater than or equal to 3" => [6, 7, 8, 9]
}

Parameters:

  • list (Array<Object>)

    The list to run the pivots against

  • pivots (Array<Proc, One::Pivot>)

    An argument list that accepts N number of pivots.<br /> The last pivot in the list can be an options Hash for the multi_pivot operation.<br /> Supported options are:<br />

    • :delimiter, The delimiter to use between pivot keys. Defaults to ‘[PIVOT]’]

Returns:

  • (Hash)

    The pivoted results



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/one/pivoter.rb', line 130

def multi_pivot(list, *pivots)
  options = pivots.pop if pivots.last.is_a?(Hash)
  options ||= {}
  delimiter = options[:delimiter] || "[PIVOT]"
  pivoted = nil
  pass = 0

  while pivots.length > 0
    p = pivots.shift

    # handle the case where the pivots are One::Pivot objects
    pivot_options = {}
    if p.is_a?(One::Pivot)
      pivot_options[:identifier] = p.identifier
      p = p.pivot_proc
    end

    if pass == 0
      pivoted = pivot(list, pivot_options, &p)
    else
      new_pivoted = {}
      pivoted.each do |old_key, old_list|
        tmp_pivoted = pivot(old_list, pivot_options, &p)
        tmp_pivoted.each do |key, list|
          new_key = "#{safe_key(old_key)}#{delimiter}#{safe_key(key)}"
          new_pivoted[new_key] = list
        end
      end
      pivoted = new_pivoted
    end

    pass += 1
  end

  pivoted
end

#pivot(list, options = {}) {|item| ... } ⇒ Hash

Pivots a list of Objects grouping them into an organized Hash.

Examples:

Pivot a list of numbers into 2 groups, those less than or equal to 5 and those greater than 5

list = [1,2,3,4,5,6,7,8,9]
result = pivot(list) {|num| num <=5 }

# the result will be a Hash with the following structure
{
  true=>[1, 2, 3, 4, 5],
  false=>[6, 7, 8, 9]
}

Parameters:

  • list (Array<Object>)

    The list to pivot

  • options (optional, Hash) (defaults to: {})

    Options to pivot with

Options Hash (options):

  • :identifier (Object)

    A name that uniquely identifies the pivot operation

Yields:

  • (item)

    The block passed to pivot will be called for each item in the list

Yield Parameters:

  • item (Object)

    An item in the list

Yield Returns:

  • (Object)

    The value returned from the pivot block will serve as the key in the pivot results

Returns:

  • (Hash)

    The pivoted results



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/one/pivoter.rb', line 33

def pivot(list, options={}, &block)
  pivoted = {}
  semaphore = Mutex.new

  lists = list.each_slice(chunk_size(list)).to_a
  loops = 0

  reactor_running = EM.reactor_running?

  work = Proc.new do
    lists.each do |sub_list|

      pivot_operation = Proc.new do
        sub_list.each do |item|
          # potential long running operation with blocking IO
          value = yield(item)

          # notify observers that a pivot block was just called
          identifier = options[:identifier] || "#{item.hash}:#{block.hash}"
          changed
          # potential long running operation with blocking IO
          notify_observers(identifier, item, value)

          semaphore.synchronize {
            if value.is_a?(Array)
              if value.empty?
                  pivoted[nil] ||= []
                  pivoted[nil] << item
              else
                value.each do |val|
                  pivoted[val] ||= []
                  pivoted[val] << item
                end
              end
            else
              pivoted[value] ||= []
              pivoted[value] << item
            end
          }
        end
      end

      pivot_callback = Proc.new do
        semaphore.synchronize {
          loops += 1
          EM.stop if loops == lists.length && !reactor_running
        }
      end

      EM.defer(pivot_operation, pivot_callback)
    end
  end

  if reactor_running
    work.call
  else
    EM.run &work
  end

  pivoted
end