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



201
202
203
204
205
206
207
208
209
210
# File 'lib/tukey/data_set.rb', line 201

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 1 if data_array? && !other.data_array?
  return -1 if !data_array? && other.data_array?
  return label.id <=> other.label.id if label && other.label && label.id <=> other.label.id
  return data.size <=> other.data.size if data_array? && other.data_array?
  data <=> other.data
end

#==(other) ⇒ Object

is used for comparison of two instances directly



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

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



58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/tukey/data_set.rb', line 58

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



260
261
262
263
264
# File 'lib/tukey/data_set.rb', line 260

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)


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

def branch?
  children.any?
end

#child_branchesObject



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

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

#data_array?Boolean

Returns:

  • (Boolean)


314
315
316
# File 'lib/tukey/data_set.rb', line 314

def data_array?
  data.is_a? Array
end

#deep_dupObject



229
230
231
232
233
234
235
236
237
238
239
240
# File 'lib/tukey/data_set.rb', line 229

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



341
342
343
344
345
# File 'lib/tukey/data_set.rb', line 341

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

#empty?Boolean

Returns:

  • (Boolean)


83
84
85
86
87
88
89
# File 'lib/tukey/data_set.rb', line 83

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)


221
222
223
# File 'lib/tukey/data_set.rb', line 221

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.


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

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?
  fail 'Cannot filter value DataSets' unless data_array?
  return self.dup if self.data.empty?

  self.data.each_with_object(DataSet.new(label: label.deep_dup, data: nil, parent: parent, id: id)) 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 && !deep_filter_result.data.empty?
        # 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
  end
end

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



168
169
170
171
172
173
174
175
176
177
# File 'lib/tukey/data_set.rb', line 168

def find(subtree_id = nil, &block)
  return super if block_given? # It recursively searches descendants for data set matching block
  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



179
180
181
# File 'lib/tukey/data_set.rb', line 179

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

#hashObject



225
226
227
# File 'lib/tukey/data_set.rb', line 225

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

#label_pathObject



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

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

#leaf?Boolean

Returns:

  • (Boolean)


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

def leaf?
  children.none?
end

#leaf_labelsObject



107
108
109
110
111
112
# File 'lib/tukey/data_set.rb', line 107

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

#leafsObject



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

def leafs
  children.select(&:leaf?)
end

#merge(other_data_set, &block) ⇒ Object



287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
# File 'lib/tukey/data_set.rb', line 287

def merge(other_data_set, &block)
  merged_data_set = dup
  if data_array? && other_data_set.data_array? # Merge sets
    other_children = other_data_set.children.dup
    merged_children = children.map do |child|
      other_child = other_children.find { |ods| ods.label == child.label }
      if other_child
        other_children.delete(other_child)
        child.merge(other_child, &block)
      else
        child
      end
    end
    merged_children += other_children # The remaining other children (without matching child in this data set)
    merged_data_set.data = merged_children
  elsif !data_array? && !other_data_set.data_array? # Merge values
    if block_given? # Combine data using block
      merged_data_set.data = yield(label, value, other_data_set.value)
    else # Simply overwrite data with other data
      merged_data_set.data = other_data_set.value
    end
  else
    fail ArgumentError, "Can't merge array DataSet with value DataSet"
  end
  merged_data_set
end

#oneling?Boolean

Returns:

  • (Boolean)


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

def oneling?
  leaf? ? siblings.none? : siblings.reject(&:leaf?).none?
end

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



318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
# File 'lib/tukey/data_set.rb', line 318

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



266
267
268
269
270
# File 'lib/tukey/data_set.rb', line 266

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:



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

def reduce
  yield reducable_values
end

#root?Boolean

Returns:

  • (Boolean)


79
80
81
# File 'lib/tukey/data_set.rb', line 79

def root?
  parent.nil?
end

#siblingsObject



53
54
55
56
# File 'lib/tukey/data_set.rb', line 53

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

#sumObject



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

def sum
  # Leafs are considered a sum of their underlying data_sets,
  # therefore we can just sum the leafs if present.
  return value == [] ? nil : value if leaf? # TODO: Make redundant by not allowing [] in `data` to begin with
  values = (leafs.any? ? leafs.map(&:value) : children.map(&:sum)).compact
  return nil if values.empty?
  values.inject(&:+)
end

#to_comparable_hObject



183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/tukey/data_set.rb', line 183

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

#transform_labels!(&block) ⇒ Object



272
273
274
275
276
# File 'lib/tukey/data_set.rb', line 272

def transform_labels!(&block)
  self.label = yield(label, self)
  data.each { |d| d.transform_labels!(&block) } if data_array?
  self
end

#transform_values!(&block) ⇒ Object



278
279
280
281
282
283
284
285
# File 'lib/tukey/data_set.rb', line 278

def transform_values!(&block)
  if data_array?
    self.data = data.map { |d| d.transform_values!(&block) }
  else
    self.data = yield(value, self)
  end
  self
end

#twig?Boolean

Returns:

  • (Boolean)


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

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

#valueObject



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

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