ruby-changes:69511
From: Frederik <ko1@a...>
Date: Fri, 29 Oct 2021 18:19:10 +0900 (JST)
Subject: [ruby-changes:69511] f45af5f0a4 (master): Support gemification of tsort
https://git.ruby-lang.org/ruby.git/commit/?id=f45af5f0a4 From f45af5f0a427a85fd157bbb1461a7dc33d30eb1b Mon Sep 17 00:00:00 2001 From: Frederik Dudzik <frederik.dudzik@s...> Date: Tue, 19 Oct 2021 12:35:35 -0700 Subject: Support gemification of tsort Co-authored-by: Frederik Dudzik <frederik.dudzik@s...> Co-authored-by: Jacques Chester <jacques.chester@s...> --- lib/bundler/spec_set.rb | 2 +- .../molinillo/lib/molinillo/dependency_graph.rb | 4 +- lib/bundler/vendor/tsort/lib/tsort.rb | 453 +++++++++++++++++++++ lib/bundler/vendored_tsort.rb | 4 + spec/bundler/runtime/setup_spec.rb | 2 +- 5 files changed, 461 insertions(+), 4 deletions(-) create mode 100644 lib/bundler/vendor/tsort/lib/tsort.rb create mode 100644 lib/bundler/vendored_tsort.rb diff --git a/lib/bundler/spec_set.rb b/lib/bundler/spec_set.rb index 26d41cb9b8d..a19d18388ab 100644 --- a/lib/bundler/spec_set.rb +++ b/lib/bundler/spec_set.rb @@ -1,6 +1,6 @@ https://github.com/ruby/ruby/blob/trunk/lib/bundler/spec_set.rb#L1 # frozen_string_literal: true -require "tsort" +require_relative "vendored_tsort" module Bundler class SpecSet diff --git a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph.rb b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph.rb index 936399ed2f5..10a25d2f08a 100644 --- a/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph.rb +++ b/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph.rb @@ -1,6 +1,6 @@ https://github.com/ruby/ruby/blob/trunk/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph.rb#L1 # frozen_string_literal: true -require 'tsort' +require_relative '../../../../vendored_tsort' require_relative 'dependency_graph/log' require_relative 'dependency_graph/vertex' @@ -17,7 +17,7 @@ module Bundler::Molinillo https://github.com/ruby/ruby/blob/trunk/lib/bundler/vendor/molinillo/lib/molinillo/dependency_graph.rb#L17 vertices.values.each { |v| yield v } end - include TSort + include Bundler::TSort # @!visibility private alias tsort_each_node each diff --git a/lib/bundler/vendor/tsort/lib/tsort.rb b/lib/bundler/vendor/tsort/lib/tsort.rb new file mode 100644 index 00000000000..8454583295e --- /dev/null +++ b/lib/bundler/vendor/tsort/lib/tsort.rb @@ -0,0 +1,453 @@ https://github.com/ruby/ruby/blob/trunk/lib/bundler/vendor/tsort/lib/tsort.rb#L1 +# frozen_string_literal: true + +#-- +# tsort.rb - provides a module for topological sorting and strongly connected components. +#++ +# + +# +# TSort implements topological sorting using Tarjan's algorithm for +# strongly connected components. +# +# TSort is designed to be able to be used with any object which can be +# interpreted as a directed graph. +# +# TSort requires two methods to interpret an object as a graph, +# tsort_each_node and tsort_each_child. +# +# * tsort_each_node is used to iterate for all nodes over a graph. +# * tsort_each_child is used to iterate for child nodes of a given node. +# +# The equality of nodes are defined by eql? and hash since +# TSort uses Hash internally. +# +# == A Simple Example +# +# The following example demonstrates how to mix the TSort module into an +# existing class (in this case, Hash). Here, we're treating each key in +# the hash as a node in the graph, and so we simply alias the required +# #tsort_each_node method to Hash's #each_key method. For each key in the +# hash, the associated value is an array of the node's child nodes. This +# choice in turn leads to our implementation of the required #tsort_each_child +# method, which fetches the array of child nodes and then iterates over that +# array using the user-supplied block. +# +# require 'tsort' +# +# class Hash +# include TSort +# alias tsort_each_node each_key +# def tsort_each_child(node, &block) +# fetch(node).each(&block) +# end +# end +# +# {1=>[2, 3], 2=>[3], 3=>[], 4=>[]}.tsort +# #=> [3, 2, 1, 4] +# +# {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}.strongly_connected_components +# #=> [[4], [2, 3], [1]] +# +# == A More Realistic Example +# +# A very simple `make' like tool can be implemented as follows: +# +# require 'tsort' +# +# class Make +# def initialize +# @dep = {} +# @dep.default = [] +# end +# +# def rule(outputs, inputs=[], &block) +# triple = [outputs, inputs, block] +# outputs.each {|f| @dep[f] = [triple]} +# @dep[triple] = inputs +# end +# +# def build(target) +# each_strongly_connected_component_from(target) {|ns| +# if ns.length != 1 +# fs = ns.delete_if {|n| Array === n} +# raise TSort::Cyclic.new("cyclic dependencies: #{fs.join ', '}") +# end +# n = ns.first +# if Array === n +# outputs, inputs, block = n +# inputs_time = inputs.map {|f| File.mtime f}.max +# begin +# outputs_time = outputs.map {|f| File.mtime f}.min +# rescue Errno::ENOENT +# outputs_time = nil +# end +# if outputs_time == nil || +# inputs_time != nil && outputs_time <= inputs_time +# sleep 1 if inputs_time != nil && inputs_time.to_i == Time.now.to_i +# block.call +# end +# end +# } +# end +# +# def tsort_each_child(node, &block) +# @dep[node].each(&block) +# end +# include TSort +# end +# +# def command(arg) +# print arg, "\n" +# system arg +# end +# +# m = Make.new +# m.rule(%w[t1]) { command 'date > t1' } +# m.rule(%w[t2]) { command 'date > t2' } +# m.rule(%w[t3]) { command 'date > t3' } +# m.rule(%w[t4], %w[t1 t3]) { command 'cat t1 t3 > t4' } +# m.rule(%w[t5], %w[t4 t2]) { command 'cat t4 t2 > t5' } +# m.build('t5') +# +# == Bugs +# +# * 'tsort.rb' is wrong name because this library uses +# Tarjan's algorithm for strongly connected components. +# Although 'strongly_connected_components.rb' is correct but too long. +# +# == References +# +# R. E. Tarjan, "Depth First Search and Linear Graph Algorithms", +# <em>SIAM Journal on Computing</em>, Vol. 1, No. 2, pp. 146-160, June 1972. +# +module Bundler + module TSort + class Cyclic < StandardError + end + + # Returns a topologically sorted array of nodes. + # The array is sorted from children to parents, i.e. + # the first element has no child and the last node has no parent. + # + # If there is a cycle, TSort::Cyclic is raised. + # + # class G + # include TSort + # def initialize(g) + # @g = g + # end + # def tsort_each_child(n, &b) @g[n].each(&b) end + # def tsort_each_node(&b) @g.each_key(&b) end + # end + # + # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}) + # p graph.tsort #=> [4, 2, 3, 1] + # + # graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}) + # p graph.tsort # raises TSort::Cyclic + # + def tsort + each_node = method(:tsort_each_node) + each_child = method(:tsort_each_child) + TSort.tsort(each_node, each_child) + end + + # Returns a topologically sorted array of nodes. + # The array is sorted from children to parents, i.e. + # the first element has no child and the last node has no parent. + # + # The graph is represented by _each_node_ and _each_child_. + # _each_node_ should have +call+ method which yields for each node in the graph. + # _each_child_ should have +call+ method which takes a node argument and yields for each child node. + # + # If there is a cycle, TSort::Cyclic is raised. + # + # g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]} + # each_node = lambda {|&b| g.each_key(&b) } + # each_child = lambda {|n, &b| g[n].each(&b) } + # p TSort.tsort(each_node, each_child) #=> [4, 2, 3, 1] + # + # g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]} + # each_node = lambda {|&b| g.each_key(&b) } + # each_child = lambda {|n, &b| g[n].each(&b) } + # p TSort.tsort(each_node, each_child) # raises TSort::Cyclic + # + def TSort.tsort(each_node, each_child) + TSort.tsort_each(each_node, each_child).to_a + end + + # The iterator version of the #tsort method. + # <tt><em>obj</em>.tsort_each</tt> is similar to <tt><em>obj</em>.tsort.each</tt>, but + # modification of _obj_ during the iteration may lead to unexpected results. + # + # #tsort_each returns +nil+. + # If there is a cycle, TSort::Cyclic is raised. + # + # class G + # include TSort + # def initialize(g) + # @g = g + # end + # def tsort_each_child(n, &b) @g[n].each(&b) end + # def tsort_each_node(&b) @g.each_key(&b) end + # end + # + # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}) + # graph.tsort_each {|n| p n } + # #=> 4 + # # 2 + # # 3 + # # 1 + # + def tsort_each(&block) # :yields: node + each_node = method(:tsort_each_node) + each_child = method(:tsort_each_child) + TSort.tsort_each(each_node, each_child, &block) + end + + # The iterator version of the TSort.tsort method. + # + # The graph is represented by _each_node_ and _each_child_. + # _each_node_ should have +call+ method which yields for each node in the graph. + # _each_child_ should have +call+ method which takes a node argument and yields for each child node. + # + # g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]} + # each_node = lambda {|&b| g.each_key(&b) } + # each_child = lambda {|n, &b| g[n].each(&b) } + # TSort.tsort_each(each_node, each_child) {|n| p n } + # #=> 4 + # # 2 + # # 3 + # # 1 + # + def TSort.tsort_each(each_node, each_child) # :yields: node + return to_enum(__method__, each_node, each_child) unless block_given? + + TSort.each_strongly_connected_component(each_node, each_child) {|component| + if compo (... truncated) -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/