Class: Hash

Inherits:
Object
  • Object
show all
Defined in:
lib/hash_base/hash/apply.rb,
lib/hash_base/hash/zip_out.rb,
lib/hash_base/hash/grouping.rb,
lib/hash_base/hash/to_table.rb,
lib/hash_base/hash/deep_values.rb

Overview

utilities for ruby hashes

Copyright © 2021 Stephan Wenzel <[email protected]>

This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

Instance Method Summary collapse

Instance Method Details

#apply(&block) ⇒ Object

apply(&block): applies block to deepest element, which is not a Hash

-> hash

new hash ->


32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/hash_base/hash/apply.rb', line 32

def apply( &block )
  return to_enum(:each) unless block_given?
  hash = Hash.new
  each do |key, value|
    if  value.is_a?(Hash)
      hash[key] = value.apply( &block ) 
    else
      hash[key] = yield value
    end
  end
  hash
end

#apply!(&block) ⇒ Object

apply!(&block): applies block to deepest element, which is not a Hash

-> hash

same hash ->


53
54
55
56
57
58
59
60
61
62
63
# File 'lib/hash_base/hash/apply.rb', line 53

def apply!( &block )
  return to_enum(:each) unless block_given?
  each do |key, value|
    if value.is_a?(Hash)
      self[key] = value.apply( &block ) 
    else
      self[key] = yield value
    end
  end
  self
end

#deep_diff(other, &block) ⇒ Object

deep_diff: compares two hashes - counter part to Array.deep_diff

source: stackoverflow


69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/hash_base/hash/deep_values.rb', line 69

def deep_diff(other, &block)
  (self.keys + other.keys).uniq.inject({}) do |memo, key|
    left = self[key]
    right = other[key] 
    
    if block_given?
      next memo if yield left, right 
    else
      next memo if left == right 
    end
    if left.respond_to?(:deep_diff) && right.respond_to?(:deep_diff)
      memo[key] = left.deep_diff(right)
    else
      memo[key] = [left, right]
    end
    
    memo
  end
end

#deep_valuesObject

deep_values: gets an array of values of a nested hash as a flat array

-> hash

array ->


32
33
34
35
36
37
38
39
40
41
42
# File 'lib/hash_base/hash/deep_values.rb', line 32

def deep_values
  arr = Array.new
  each do |key, value|
    if  value.is_a?(Hash)
      arr += value.deep_values
    else
      arr << value
    end
  end #each
  arr
end

#expand(path_to_here = []) ⇒ Object

expand: reverse of group_by_positions

-> nested_hash

-> array


52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/hash_base/hash/grouping.rb', line 52

def expand(path_to_here=[])
  arry     = []
  each do |k, v|
    if v.is_a?(Hash)
      arry += v.expand(path_to_here + [k])
    elsif v.is_a?(Array)
      v.each do |el|
        arry << (path_to_here + [k] +  el  ) if     el.is_a?(Array)
        arry << (path_to_here + [k] + [el] ) unless el.is_a?(Array)
      end
    else
      arry << (path_to_here + [k] + [v])
    end
  end
  arry
end

#group_by_positions(*positions) ⇒ Object

group_by_positions: counter part to Array.group_by_positions

will only be invoked by Array.group_by_positions



31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/hash_base/hash/grouping.rb', line 31

def group_by_positions( *positions )
  ohash = ActiveSupport::OrderedHash.new
  each do |key, val|
    case val
    when Hash, Array
      ohash[key] = val.group_by_positions( *positions )
    else
      ohash[key] = val
    end
  end #each
  ohash
end

#max_depth(depth: 1) ⇒ Object

max_depth: gets max depth of a hash

-> hash

int ->


53
54
55
56
57
58
59
60
61
# File 'lib/hash_base/hash/deep_values.rb', line 53

def max_depth(depth: 1)
  max_depth = depth
  each do |k,v|
    if v.is_a?(Hash)
      max_depth = [max_depth, v.max_depth( depth: depth + 1 )].max
    end
  end
  max_depth
end

#to_table(level: 0, indent: 15, precision: 2, dosort: true, content: "data", format: nil, divisor: nil, total: nil, total_caption: "", grand_total: nil, grand_total_caption: "") ⇒ Object

to_table: creates a rectangular table from a nested hash

-> hash

  array ->

parameters

level:               (Integer) only used internally for recursive calls (do not use)
indent:              (Integer) number of spaces for padding each table element
precision            (Integer) number of precision digits for floats
dosort;              (Boolean) should each level of keys be sorted ?
content:             (String) "style" => creates a table of cell styles, else table
                               of data
format:              (String) "text" - text output, balanced with indent parameter 
                              "html" - html output
total:               (Proc)   "-> x {x.flatten.inject(:+)}" proc to create totals of 
                               arrays of deepest values
total_caption        (String) caption for total line 
grand_total          (Proc)   "-> x {x.depp_values.flatten.inject(:+)}" proc to 
                              create totals of arrays of deepest values
grant_total_caption  (string) caption for grand total line


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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/hash_base/hash/to_table.rb', line 49

def to_table( level: 0, indent: 15, precision: 2, dosort: true, content: "data", format: nil, divisor: nil, total: nil, total_caption: "", grand_total: nil, grand_total_caption: "" )
  
  arry = []
  i    = 0
  
  h = dosort ? sort : self
  
  h.each do |k, v|
  
    first_line = Array.new( i > 0 ? level : 0) {""} + [k]                                    unless content == "style"
    first_line = Array.new( i > 0 ? level : 0) {|l| "empty level_#{l}"} + ["key level_#{level}"] if content == "style"
    
    if v.is_a?(Hash)
      lines = v.to_table(level: level + 1, content: content, indent: indent, dosort: dosort, divisor: divisor, total: total, total_caption: total_caption, grand_total: grand_total, grand_total_caption: grand_total_caption)
      first_line += lines[0] # complement current line with last key
      arry << first_line
      arry += lines.drop(1) if lines.drop(1).present?
    elsif v.is_a?(Array) #must be array
      lines = []
      v.each_with_index do |av, i|
        elem = (av.is_a?(Array) ? av : [av])
        lines << (Array.new( i > 0 ? level + 1 : 0) {""} + elem )                                                            unless content == "style"
        lines << (Array.new( i > 0 ? level + 1 : 0) {|l| "empty level_#{l}"} + Array.new(elem.length){"value level_#{level}"} )  if content == "style"
      end
      first_line += lines[0] # complement current line
      arry << first_line
      arry += lines.drop(1) if lines.drop(1).present?
      
      max_len = arry.map{|r| r.length}.max
      
      unless content == "style"
        arry << (Array.new( max_len + 1) {divisor.to_s * indent} )                        if total && divisor
        arry << (["#{total_caption}"] + Array.new( max_len - 1) {""} + [total.yield(v)] ) if total
        arry << (Array.new( max_len + 1) {""} )                                           if total && divisor
      else
        arry << (Array.new( max_len + 1) {|l| "divisor level_#{l}"} )                                                        if total && divisor
        arry << (["total_caption level_0"] + Array.new( max_len - 1) {|l| "empty level_#{l+1}"} + ["total level_#{level}"] ) if total
        arry << (Array.new( max_len + 1) {|l| "empty level_#{l}"} )                                                          if total && divisor
      end
    end
    i+=1
  end #each
  
  if level == 0
  
    max_len = arry.map{|r| r.length}.max
    
    unless content == "style"
      arry << (Array.new( max_len + 1 ) {divisor.to_s * indent} )                                             if grand_total && divisor
      arry << (["#{grand_total_caption}"]      + Array.new( max_len - 1 ) {""} + [grand_total.yield(self)] )  if grand_total
    else
      arry << (Array.new( max_len + 1) {"grand_total divisor"} )                                                                   if grand_total && divisor
      arry << (["grand_total_caption level_0"] + Array.new( max_len - 1) {|l| "empty grand_total level_#{l+1}"} + ["grand_total"]) if grand_total
    end
    
    
    max_len = arry.map{|r| r.length}.max
    
    arry.map!{|l| l += Array.new(max_len - l.length) {""} }          unless content == "style"
    arry.map!{|l| l += Array.new(max_len - l.length) {"empty padding"} } if content == "style"
    
    if format == "text"
      arry.to_text(indent: indent, precision: precision)
    elsif format == "html"
      arry.to_html( styles: self.to_table(content: "style", total: true, grand_total: true))
    else
      arry
    end
  else
    arry
  end
  
end

#zip_out(&block) ⇒ Object

zip_out: puts elements into one zip file with keys as filenames

-> hash

zip ->

depends on gem ‘zip’



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/hash_base/hash/zip_out.rb', line 34

def zip_out(&block)

  return nil unless defined?(Zip)
  
  zip_stream = Zip::OutputStream.write_buffer do |zip|
  
   each do |key, obj|
      zip.put_next_entry(key)
      if block_given?
        zip.write(yield obj)
      else
         zip.write(obj)
      end
    end
  end
  
  # important - rewind the steam
  zip_stream.rewind
  
  # read out zip contents
  zip_stream.read
  
end