Class: DataSet

Inherits:
Object
  • Object
show all
Includes:
Enumerable
Defined in:
lib/tukey/data_set.rb,
lib/tukey/data_set/label.rb

Defined Under Namespace

Classes: Label

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(data: nil, label: nil, parent: nil, id: nil) ⇒ DataSet

Returns a new instance of DataSet.



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/tukey/data_set.rb', line 13

def initialize(data: nil, label: nil, parent: nil, id: nil)
  self.data = data # We have to use `self` here because we have a custom setter for `data`
  @parent = parent
  @id = id || SecureRandom.uuid

  return unless label

  if label.is_a?(DataSet::Label)
    @label = label
  elsif label.is_a?(String)
    @label = DataSet::Label.new(label)
  elsif label.is_a?(Hash)
    @label = DataSet::Label.new(label.delete(:name), **label)
  else
    fail ArgumentError, 'Given unsupported label type to DataSet initialize'
  end
end

Instance Attribute Details

#dataObject

Returns the value of attribute data.



9
10
11
# File 'lib/tukey/data_set.rb', line 9

def data
  @data
end

#idObject (readonly)

Returns the value of attribute id.



11
12
13
# File 'lib/tukey/data_set.rb', line 11

def id
  @id
end

#labelObject

Returns the value of attribute label.



7
8
9
# File 'lib/tukey/data_set.rb', line 7

def label
  @label
end

#parentObject

Returns the value of attribute parent.



10
11
12
# File 'lib/tukey/data_set.rb', line 10

def parent
  @parent
end

Instance Method Details

#<<(item) ⇒ Object Also known as: add_item



31
32
33
34
35
36
# File 'lib/tukey/data_set.rb', line 31

def <<(item)
  self.data ||= []
  fail(CannotAddToNonEnumerableData, parent: self, item: item) unless data_array?
  item.parent = self
  data.push(item)
end

#<=>(other) ⇒ Object



197
198
199
200
201
202
203
204
# File 'lib/tukey/data_set.rb', line 197

def <=>(other)
  return 0 if data == other.data && label == other.label
  return 1 if data && other.data.nil?
  return -1 if data.nil? && other.data
  return label.id <=> other.label.id if label.id <=> other.label.id
  return data <=> other.data if data.is_a?(Numeric) && other.data.is_a?(Numeric)
  data.size <=> other.data.size
end

#==(other) ⇒ Object

is used for comparison of two instances directly



207
208
209
210
211
# File 'lib/tukey/data_set.rb', line 207

def ==(other)
  other_data = other.data.nil? ? nil : (other.data.is_a?(Enumerable) ? other.data.sort : other.data )
  own_data = data.nil? ? nil : (data.is_a?(Enumerable) ? data.sort : data )
  other.label == label && other_data == own_data
end

#ancestorsObject



54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/tukey/data_set.rb', line 54

def ancestors
  return [] if parent.nil?
  ancs = []
  par = parent
  require 'pry'
  until par.nil?
    puts par.label.name
    ancs.push par
    par = par.parent
  end
  ancs.reverse
end

#averageObject



251
252
253
254
255
# File 'lib/tukey/data_set.rb', line 251

def average
  values = [reducable_values].flatten.compact
  return nil if values.empty?
  (values.inject(&:+).to_f / values.size).to_f
end

#branch?Boolean

Returns:

  • (Boolean)


87
88
89
# File 'lib/tukey/data_set.rb', line 87

def branch?
  children.any?
end

#child_branchesObject



91
92
93
# File 'lib/tukey/data_set.rb', line 91

def child_branches
  children.select(&:branch?)
end

#childrenObject



44
45
46
47
# File 'lib/tukey/data_set.rb', line 44

def children
  return data if data_array?
  []
end

#combine(other_data_set, operator) ⇒ Object



263
264
265
266
267
268
269
270
271
272
273
# File 'lib/tukey/data_set.rb', line 263

def combine(other_data_set, operator)
  combined_data_set = dup
  if data_array? && other_data_set.data_array?
    combined_data_set.data = combine_data_array(other_data_set.children, operator)
  elsif !data_array? && !other_data_set.data_array?
    combined_data_set.data = combine_data_value(other_data_set.value, operator)
  else
    fail ArgumentError, "Can't combine array DataSet with value DataSet"
  end
  combined_data_set
end

#data_array?Boolean

Returns:

  • (Boolean)


275
276
277
# File 'lib/tukey/data_set.rb', line 275

def data_array?
  data.is_a? Array
end

#deep_dupObject



223
224
225
226
227
228
229
230
231
232
233
234
# File 'lib/tukey/data_set.rb', line 223

def deep_dup
  new_set = DataSet.new(id: id)
  new_set.label = DataSet::Label.new(dup_value(label.name), id: dup_value(label.id), meta: label.meta.marshal_dump) if label

  if data_array?
    new_set.data = children.map(&:deep_dup)
  else
    new_set.data = data
  end

  new_set
end

#each {|_self| ... } ⇒ Object

Yields:

  • (_self)

Yield Parameters:

  • _self (DataSet)

    the object that the method was called on



302
303
304
305
306
# File 'lib/tukey/data_set.rb', line 302

def each(&block)
  yield self
  children.each { |member| member.each(&block) } if data_array?
  self
end

#empty?Boolean

Returns:

  • (Boolean)


79
80
81
82
83
84
85
# File 'lib/tukey/data_set.rb', line 79

def empty?
  if data_array?
    data.all?(&:empty?)
  else
    data.respond_to?(:empty?) ? !!data.empty? : !data
  end
end

#eql?(other) ⇒ Boolean

eql? and hash are both used for comparisons when you call ‘.uniq` on an array of data sets.

Returns:

  • (Boolean)


215
216
217
# File 'lib/tukey/data_set.rb', line 215

def eql?(other)
  self == other
end

#filter(leaf_label_id = nil, keep_leafs: false, orphan_strategy: :destroy, &block) ⇒ Object

Filter method that returns a new (dup) data_set with certain nodes filtered out Filtering is done through either passing:

1. a 'leaf_label_id'. All matching nodes will be present in the new set
in their original position in the tree.

2. by passing a block that returns either `true`, `nil` or `false` for a given node:
      true: A node is kept, _including its children_
      nil: The matcher is indifferent about the node and will continue recursing the tree
            a. When at some point `true` is returned for a descendant node the whole branch will be
              kept up to and including the node for which the block returned `true`.
            b. If `true` is not returned the whole branch will not be included in the filter result,
              unless the option `keep_leafs` was set to true, in which case only nodes that were cut
              off with `false` will be excluded in the result.
      false: When the block returns false for a given node that node is taken out of the results
             this inludes its children, unless the option `orphan_strategy` was set to `:adopt` in
             which case the children will be filtered using the same block and appended to first ancestor
             node that was not excluded by the filter.


128
129
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
# File 'lib/tukey/data_set.rb', line 128

def filter(leaf_label_id = nil, keep_leafs: false, orphan_strategy: :destroy, &block)
  fail ArgumentError, 'No block and no leaf_label_id passed' if !block_given? && leaf_label_id.nil?

  self.data.each_with_object(DataSet.new(id: id, label: label.deep_dup)) do |set, parent_set|
    if block_given?
      condition_met = yield(parent_set, set)
    else
      condition_met = set.leaf? ? (set.label.id == leaf_label_id) : nil
    end

    set_dup = set.deep_dup

    # We want to have this node and its children
    if condition_met == true
      parent_set.add_item(set_dup)
    # Complex looking clause, but useful for performance and DRY-ness
    elsif set.data_array? && (condition_met.nil? || (condition_met == false && orphan_strategy == :adopt))
      deep_filter_result = set_dup.filter(leaf_label_id, keep_leafs: keep_leafs, orphan_strategy: orphan_strategy, &block)

      # Here is where either the taking along or adopting of nodes happens
      if deep_filter_result.data
        # Filtering underlying children and adding the potential filter result to parent.
        parent_set.add_item(deep_filter_result) if condition_met.nil?

        # We are losing the node, but since 'orphan_strategy' == :adopt we will adopt the orphans that match the filter
        deep_filter_result.children.each { |c| parent_set.add_item(c) } if condition_met == false
      end
    # We are indifferent to the match (nil), but since the node is a leaf we will keep it
    elsif condition_met.nil? && set.leaf?
      parent_set.add_item(set_dup) if keep_leafs
    end

    parent_set
  end
end

#find(subtree_id = nil, &block) ⇒ Object



164
165
166
167
168
169
170
171
172
173
# File 'lib/tukey/data_set.rb', line 164

def find(subtree_id = nil, &block)
  return super if block_given?
  return self if id == subtree_id
  return nil unless data_array?
  data.each do |child|
    match = child.find(subtree_id)
    return match if match
  end
  nil
end

#find_by(query) ⇒ Object



175
176
177
# File 'lib/tukey/data_set.rb', line 175

def find_by(query)
  return find { |s| s.to_comparable_h.deep_merge(query) == s.to_comparable_h }
end

#hashObject



219
220
221
# File 'lib/tukey/data_set.rb', line 219

def hash
  "#{data.hash}#{label.hash}".to_i
end

#label_pathObject



67
68
69
# File 'lib/tukey/data_set.rb', line 67

def label_path
  [ancestors, self].flatten.map(&:label)
end

#leaf?Boolean

Returns:

  • (Boolean)


99
100
101
# File 'lib/tukey/data_set.rb', line 99

def leaf?
  children.none?
end

#leaf_labelsObject



103
104
105
106
107
108
# File 'lib/tukey/data_set.rb', line 103

def leaf_labels
  return [] if leaf?
  return [] if children.none?
  return children.map(&:label) if twig?
  children.map(&:leaf_labels).flatten.uniq
end

#oneling?Boolean

Returns:

  • (Boolean)


71
72
73
# File 'lib/tukey/data_set.rb', line 71

def oneling?
  siblings.none?
end

#pretty_inspect(level = 0, final_s: '') ⇒ Object



279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
# File 'lib/tukey/data_set.rb', line 279

def pretty_inspect(level = 0, final_s: '')
  prefix = ''

  if root?
    prefix << '* '
  else
    prefix << (' ' * (level) * 3) + '|- '
  end

  if label
    node_s = "#{prefix}#{label.name}"
  else
    node_s = "#{prefix} (no label)"
  end
  node_s += ": #{value}" if leaf?
  final_s += "#{node_s} \n"

  return final_s if children.none?

  children.each { |c| final_s << c.pretty_inspect(level + 1) }
  final_s
end

#reducable_values(set = nil) ⇒ Object



257
258
259
260
261
# File 'lib/tukey/data_set.rb', line 257

def reducable_values(set = nil)
  set ||= self
  return set.children.map { |c| reducable_values(c) } if set.data_array?
  set.data
end

#reduce {|reducable_values| ... } ⇒ Object

Yields:



241
242
243
# File 'lib/tukey/data_set.rb', line 241

def reduce
  yield reducable_values
end

#root?Boolean

Returns:

  • (Boolean)


75
76
77
# File 'lib/tukey/data_set.rb', line 75

def root?
  parent.nil?
end

#siblingsObject



49
50
51
52
# File 'lib/tukey/data_set.rb', line 49

def siblings
  return [] if parent.nil?
  parent.children.reject { |c| c == self }
end

#sumObject



245
246
247
248
249
# File 'lib/tukey/data_set.rb', line 245

def sum
  values = [reducable_values].flatten.compact
  return nil if values.empty?
  values.inject(&:+)
end

#to_comparable_hObject



179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/tukey/data_set.rb', line 179

def to_comparable_h
  ch = {
    id: self.id,
  }

  ch[:data] = data unless data_array?

  if label
    ch[:label] = {
      id: label.id,
      name: label.name,
      meta: label.meta.to_h,
    }
  end

  ch
end

#twig?Boolean

Returns:

  • (Boolean)


95
96
97
# File 'lib/tukey/data_set.rb', line 95

def twig?
  !leaf? && children.all?(&:leaf?)
end

#valueObject



236
237
238
239
# File 'lib/tukey/data_set.rb', line 236

def value
  fail NotImplementedError, 'DataSet is not a leaf and thus has no value' unless leaf?
  data
end