Skip to content

Instantly share code, notes, and snippets.

@ProGM
Created July 18, 2016 10:24
Show Gist options
  • Select an option

  • Save ProGM/79e72876dbbcbbd79a2beec893c4f6e8 to your computer and use it in GitHub Desktop.

Select an option

Save ProGM/79e72876dbbcbbd79a2beec893c4f6e8 to your computer and use it in GitHub Desktop.

Revisions

  1. ProGM created this gist Jul 18, 2016.
    104 changes: 104 additions & 0 deletions deep_merge_tree.rb
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,104 @@
    class Tree
    attr_reader :data

    def initialize(data)
    @data = data
    end

    def +(other)
    Tree.new(deep_merge(@data, other.is_a?(Tree) ? other.data : other))
    end

    delegate :empty?, to: :data

    def replace(other)
    @data.replace(other.data)
    end

    def root
    level(0)
    end

    def levels
    output = []
    i = -1
    while i += 1
    current_level = level(i)
    break if current_level.all?(&:nil?)
    output << current_level
    end
    output
    end

    def level(level_number)
    all_elements_at_level(@data, level_number)
    end

    private

    def deep_merge(a, b)
    case a
    when Hash
    return merge_hashes(a, b) if b.is_a?(Hash)
    return merge_array_hash(b, a) if b.is_a?(Array)
    [b, a]
    when Array
    return merge_arrays(a, b) if b.is_a?(Array)
    return merge_array_hash(a, b) if b.is_a?(Hash)
    [b] + a
    else
    return [a, b] if b.is_a?(Hash)
    return [a] + b if b.is_a?(Array)
    a == b ? a : [a, b]
    end
    end

    def merge_array_hash(a, b)
    if a.last.is_a? Hash
    a[0...-1] + [merge_hashes(a.last, b)]
    else
    a + [b]
    end
    end

    def merge_hashes(a, b)
    a.deep_merge(b) do |_, this_val, other_val|
    deep_merge(this_val, other_val)
    end
    end

    def merge_arrays(a, b)
    keys = merge_array_keys(a, b)
    hashes = merge_hashes(a.last.is_a?(Hash) ? a.last : {}, b.last.is_a?(Hash) ? b.last : {})
    if hashes.empty?
    keys
    else
    (keys - hashes.keys) + [hashes]
    end
    end

    def merge_array_keys(a, b)
    (a.reject { |e| e.is_a?(Hash) } + b.reject { |e| e.is_a?(Hash) }).uniq
    end

    def all_elements_at_level(data, level_number)
    return ground_level(data) if level_number == 0
    case data
    when Hash
    data.map { |_, v| all_elements_at_level(v, level_number - 1) }
    when Array
    data.map { |e| all_elements_at_level(e, level_number) }.flatten
    end
    end

    def ground_level(data)
    case data
    when Hash
    data.keys
    when Array
    data.map { |e| all_elements_at_level(e, 0) }.flatten
    else
    data
    end
    end
    end