Module: DSR::Recognizer

Defined in:
lib/dsr/recognizer.rb

Class Method Summary collapse

Class Method Details

.detect_distant_groups(_array, goal: nil, ratio: nil) {|item| ... } ⇒ Object

sorts an array by a block and splits in chunks gauranteed to split into at least two chunks if you pass ratio then recursively splits if there is another gap_size > prev_gap_size * ratio if you pass goal then tries ratios from 0.0 to 1.0 to find the goal number of chunks if you pass nothing then it uses PCBR to chose the ratio for both goal and nothing variants it checks the result to be the same for all fitting ratios ratio: 0 is effectively the same as just Ruby stdlib sort and chunk by block

Parameters:

  • _array (Array<Object>)
  • ratio (Float) (defaults to: nil)
  • goal (Integer) (defaults to: nil)

Yield Parameters:

  • item (Object)

    an item of _array

Yield Returns:

  • (Numeric)

    something that can subtract from each other to measure a gap



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
122
123
124
125
126
# File 'lib/dsr/recognizer.rb', line 62

def self.detect_distant_groups _array, goal: nil, ratio: nil

  array = ::Struct.new(:item, :value, :i, :dist_left).then do |struct|
    _array.map{ |e| struct.new e, yield(e) }.sort_by(&:value)
  end
  array.each_with_index{ |e, i| e.i = i }
  array.each_cons(2){ |e1, e2| e2.dist_left = e2.value - e1.value }
  f = lambda do |from, to, r: ratio, threshold: 0|
    # next [] if from > to
    next [[array[from]]] if from == to
    max = array[from + 1..to].max_by(&:dist_left)
    # next [] unless max
    if threshold * r < max.dist_left
      [*f[from, max.i - 1, r: r, threshold: max.dist_left], *f[max.i, to, r: r, threshold: max.dist_left]]
    else
      [array[from..to]]
    end
  end

  if ratio
    f[0, array.size - 1].map{ |_| _.map &:item }
  elsif goal
    0.step(1, 0.01).map do |r|
      f[0, array.size - 1, r: r].map{ |_| _.map &:item }
    end.tap do |ratios|
      fail unless ratios.first.size == array.map(&:value).uniq.size
      ratios.each_cons(2){ |a, b| fail if a.size < b.size }
    end.select{ |_| _.size == goal }.tap do |splits|
      fail if 1 < splits.uniq.size
    end.first
  else
    require "pcbr"
    pcbr = ::PCBR.new
    0.step(1, 0.01).map.with_index do |r, i|
      [i, f[0, array.size - 1, r: r].map{ |_| _.map &:item }]
    end.tap do |ratios|
      fail unless ratios.first.last.size == array.map(&:value).uniq.size
      ratios.each_cons(2){ |(_, a), (_, b)| fail if a.size < b.size }
    end.chunk{ |_, split| split.size }.each do |_, splits|
      # v = [splits.first.first, splits.size]
      v = [
        splits.first.first,
        -splits.map(&:last).mean do |split|
          split.map(&:size).then do |sizes|
            sizes.map do |size|
              (sizes.max - sizes.min).then do |_|
                # otherwise it makes Infinity, then NaN
                _.zero? ? size : size.fdiv(_)
              end
            end.then do |sizes|
              # σ²
              sizes.mean{ |_| (_ - sizes.mean) ** 2 }
            end
          end
        end
      ]
      pcbr.store splits.map(&:last), v
    end
    (best, _, score) = pcbr.table.reverse.max_by(&:last)
    fail unless 0 < score
    fail if 1 < best.uniq.size
    best.first
  end

end

.detect_many_after_many(keys, values, &block) ⇒ Hash{Object => Array<Object>}

joins two arrays of objects into a Hash of parents and children, sorted by passed block it is possible for parent to have no children examples:

Discord search results HTML page -- based on `node.rect.y`, channel names become keys, messages become values

Parameters:

  • keys (Array)

    these become keys

  • values (Array)

    these become values

Returns:

  • (Hash{Object => Array<Object>})

    Hash=> Array

    
    
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    # File 'lib/dsr/recognizer.rb', line 26
    
    def self.detect_many_after_many keys, values, &block
      fail if keys.any?(&:nil?)   # for 'if key' in the algorithm loop
      # fail if keys.dup.uniq!
      # fail if values.dup.uniq!
      fail if (keys + values).map(&block).uniq!
      key = nil
      h = {}
      vs = []
      for e, from in (
        keys.map{ |_| [_, :from_keys] } +
        values.map{ |_| [_, :from_values] }
      ).sort_by{ |e,| block.call e }
        if :from_keys == from
          h[key] = vs if key
          key, vs = e, []
        else
          fail unless key
          vs.push e
        end
      end
      h[key] = vs if key
      h
    end

    .injective_mapping(domain, codomain) {|domain_item,| ... } ⇒ Array<Array(Object, Object)>

    https://en.wikipedia.org/wiki/Injective_function picks the closest item from codomain for each domain element raises exception if picks intersect

    Parameters:

    • domain (Array)
    • codomain (Array)

    Yield Parameters:

    • domain_item,

      codomain_item [Object]

    Yield Returns:

    • (Numeric)

      distance

    Returns:

    • (Array<Array(Object, Object)>)
    
    
    136
    137
    138
    139
    140
    141
    142
    143
    144
    # File 'lib/dsr/recognizer.rb', line 136
    
    def self.injective_mapping domain, codomain
      domain.map do |a|
        [a, codomain.each_index.group_by{ |i| yield a, codomain[i] }.min.last.assert_one]
      end.tap do |_|
        fail if _.map(&:last).uniq!
      end.map do |a, b|
        [a, codomain[b]]
      end
    end