class Eco::API::UseCases::GraphQL::Helpers::Location::Command::Diffs module Stages module Sortable # Class that provided elements that may have a relationship # with a provided `block` that compares two single elements (`<==>`) # it returns the sorted list of the elements in a safe way # and by keeping the original order when `<==>` returns `0` class RelationSafeSort attr_reader :list attr_reader :cluster_mode # @param cluster_mode [Boolean] whether it should optimize by trying to # first treeify and only trying to resolve the by pairings (one to one) # the diffs that are hanging from now where. # Possible values are `:prev` or `:curr` def initialize(list, cluster_mode: nil, &block) @list = list @cluster_mode = clusterer_class.mode(cluster_mode) if cluster_mode @comparer = block end def clustered? !!@cluster_mode end # @yield [a, b] elements to be sorted # When the comparer `<=>` returns `0`, it still ensures the order is correct # Please notice that native `sort` is not stable when `<=>` can return `0` def sort return list.dup if list.count <= 1 # treeifying we ensure correct order (parent, children) pending = parents_hash.dup treeify = proc do |item| next item unless (children = pending.delete(item)) {item => children.map {|child| treeify.call(child)}} end sorted_tree = top_parents.each_with_object(unpaired) do |item, tree| next unless pending.key?(item) tree << treeify.call(item) end flatten(sorted_tree) end private attr_reader :comparer def compare_two(a, b) comparer.call(a, b) end def unpaired list - paired end def paired parents_hash.keys | all_children end def parents_hash @parents_hash ||= if clustered? clusterer.clusters else paired_related_nodes.group_by(&:shift).transform_values(&:flatten) end end def clusterer @clusterer ||= clusterer_class.new(list, mode: cluster_mode) end def top_parents @top_parents ||= parents_hash.keys.reject {|item| all_children.include?(item)} end def all_children @all_children ||= parents_hash.values.flatten(1).uniq end # @yield [a, b] elements to be sorted # Pairs one to one relationships in order # @note on relationship, parent comes first def paired_related_nodes paired_combinations.each_with_object([]) do |pair, chained| next if (result = compare_two(*pair)).zero? chained << (result.negative?? pair : pair.reverse) end end def paired_combinations list.combination(2).to_a end # Flattens the tree into an Array def flatten(value) case value when Array value.each_with_object([]) do |child, out| out.concat(flatten(child)) end.uniq when Hash item, children = value.first [item].concat(flatten(children)).uniq else [value] end end def clusterer_class Eco::Data::Locations::NodeDiff::NodesDiff::ClusteredTreeify end end end end end