knapsack_solver 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +19 -0
- data/README.md +2 -0
- data/bin/knapsack_solver +12 -0
- data/knapsack_solver.gemspec +33 -0
- data/lib/knapsack_solver.rb +2 -0
- data/lib/knapsack_solver/cli.rb +49 -0
- data/lib/knapsack_solver/cli_option_checker.rb +83 -0
- data/lib/knapsack_solver/cli_option_parser.rb +68 -0
- data/lib/knapsack_solver/dataset.rb +44 -0
- data/lib/knapsack_solver/graph_printer.rb +115 -0
- data/lib/knapsack_solver/instance.rb +48 -0
- data/lib/knapsack_solver/output_printer.rb +97 -0
- data/lib/knapsack_solver/solver.rb +118 -0
- data/lib/knapsack_solver/solving_methods/branch_and_bound.rb +111 -0
- data/lib/knapsack_solver/solving_methods/dynamic_programming.rb +116 -0
- data/lib/knapsack_solver/solving_methods/fptas.rb +47 -0
- data/lib/knapsack_solver/solving_methods/heuristic_price_weight.rb +56 -0
- data/lib/knapsack_solver/version.rb +5 -0
- data/test/knapsack_solver_matchers.rb +103 -0
- data/test/knapsack_solver_spec.rb +261 -0
- data/test/spec_helper.rb +5 -0
- metadata +141 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: e7d04df167b3dc41b861e541d7fba44765765280
|
4
|
+
data.tar.gz: 1832b618f0828d2409583bf97f0804cd13ac5b24
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5615a26a781ae3cd094666aaf93c8d412083882a4e4c8c3eb800735d6513794e9f27ad81cda4d447dd6707eb8b18f4b73868ef92ef54792d243f804a8f936b1c
|
7
|
+
data.tar.gz: 697349d2dce6d2f8d9acdaf32e552594c30745aa787f46b7051ff2635908bafb8374bd27d2b3e00a43dbb05436c24a99514872786dc315e1e92fcf2b999d449c
|
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2018, Ján Sučan
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.md
ADDED
data/bin/knapsack_solver
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require File.expand_path('lib/knapsack_solver/version', __dir__)
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = 'knapsack_solver'
|
5
|
+
s.version = KnapsackSolver::VERSION
|
6
|
+
s.homepage = 'https://github.com/sucanjan/knapsack-solver'
|
7
|
+
s.license = 'MIT'
|
8
|
+
s.author = 'Jan Sucan'
|
9
|
+
s.email = 'sucanjan@fit.cvut.cz'
|
10
|
+
|
11
|
+
s.summary = '0/1 knapsack problem solver.'
|
12
|
+
s.description = <<-EOF
|
13
|
+
This gem contains command-line utility for solving 0/1 knapsack problem using
|
14
|
+
branch-and-bound method, dynamic programming, simple heuristic (weight/price)
|
15
|
+
and fully polynomial time approximation scheme.
|
16
|
+
|
17
|
+
It can measure CPU and wall-clock time spent by solving a problem, compute
|
18
|
+
relative error of the result and generate graphs from those values.
|
19
|
+
EOF
|
20
|
+
|
21
|
+
s.files = Dir['bin/*', 'lib/**/*', '*.gemspec', 'LICENSE*', 'README*', 'test/*']
|
22
|
+
s.executables = Dir['bin/*'].map { |f| File.basename(f) }
|
23
|
+
s.has_rdoc = 'yard'
|
24
|
+
|
25
|
+
s.required_ruby_version = '>= 2.2'
|
26
|
+
|
27
|
+
s.add_runtime_dependency 'gnuplot', '~> 2.6'
|
28
|
+
|
29
|
+
s.add_development_dependency 'rake', '~> 12.0'
|
30
|
+
s.add_development_dependency 'rspec', '~> 3.6'
|
31
|
+
s.add_development_dependency 'rubocop', '~> 0.50.0'
|
32
|
+
s.add_development_dependency 'yard', '~> 0.9'
|
33
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'knapsack_solver/solver'
|
2
|
+
require 'knapsack_solver/version'
|
3
|
+
require 'knapsack_solver/cli_option_parser'
|
4
|
+
require 'knapsack_solver/output_printer'
|
5
|
+
require 'knapsack_solver/graph_printer'
|
6
|
+
|
7
|
+
module KnapsackSolver
|
8
|
+
# This class implements a command-line interface for the 0/1 knapsack
|
9
|
+
# problem solver.
|
10
|
+
class CLI
|
11
|
+
# Suffix of a text file which will containg results of dataset solving
|
12
|
+
# (price, knapsack things presence, cpu time, wall clock time,
|
13
|
+
# relative_error).
|
14
|
+
RESULTS_FILNEMAE_SUFFIX = '.results'.freeze
|
15
|
+
|
16
|
+
# Suffix of a text file which will containg statistic data (average price,
|
17
|
+
# execution times, relative error)
|
18
|
+
STATS_FILNEMAE_SUFFIX = '.stats'.freeze
|
19
|
+
|
20
|
+
# Processes command-line arguments. If no option is given, converts arabic
|
21
|
+
# number to roman number and prints it to stdout.
|
22
|
+
#
|
23
|
+
# @param args [Array] the command-line arguments
|
24
|
+
def self.run(args)
|
25
|
+
options = CliOptionParser.parse(args)
|
26
|
+
return if options.nil?
|
27
|
+
datasets = args.each_with_object([]) do |file, sets|
|
28
|
+
sets << Dataset.parse(File.new(file))
|
29
|
+
end
|
30
|
+
s = Solver.new(options, datasets)
|
31
|
+
results = s.run
|
32
|
+
print_results(results, s.stats(results), options, args)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Prints output of datasets solving. Results and statistics are printed to
|
36
|
+
# stdout or to a text files. Graphs of statistic values can be created.
|
37
|
+
#
|
38
|
+
# @param results [Hash] results of dataset solvings
|
39
|
+
# @param stats [Hash] statistics from the results of dataset solvings
|
40
|
+
# @param options [Hash] Command-line line options supplied to the CLI
|
41
|
+
# @param args [Array] array of the positional command-line arguments
|
42
|
+
def self.print_results(results, stats, options, args)
|
43
|
+
OutputPrinter.new(args, RESULTS_FILNEMAE_SUFFIX, results).print(options[:output_dir])
|
44
|
+
OutputPrinter.new(args, STATS_FILNEMAE_SUFFIX, stats).print(options[:output_dir])
|
45
|
+
return unless options[:graphs_dir]
|
46
|
+
GraphPrinter.new(args, stats, options[:graphs_dir]).print
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
module KnapsackSolver
|
4
|
+
# This class checks command line arguments provided to the knapsack_solver
|
5
|
+
# binary.
|
6
|
+
class CliOptionChecker
|
7
|
+
# Checks command-line options, their arguments and positional arguments
|
8
|
+
# provided to the CLI.
|
9
|
+
#
|
10
|
+
# @param opts [Hash] parsed command-line options
|
11
|
+
# @param args [Array<String>] command-line positional arguments
|
12
|
+
def self.check(opts, args)
|
13
|
+
if !opts[:branch_and_bound] && !opts[:dynamic_programming] &&
|
14
|
+
!opts[:fptas] && !opts[:heuristic]
|
15
|
+
raise StandardError, 'At least one method of solving must be requested'
|
16
|
+
end
|
17
|
+
check_fptas_options(opts)
|
18
|
+
check_directories(opts)
|
19
|
+
check_positional_arguments(args)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Checks command-line options and arguments used by FPTAS solving method.
|
23
|
+
#
|
24
|
+
# @param opts [Hash] parsed command-line options
|
25
|
+
def self.check_fptas_options(opts)
|
26
|
+
return if !opts[:fptas] && !opts.key?(:fptas_epsilon)
|
27
|
+
check_incomplete_fptas_options(opts)
|
28
|
+
eps = opts[:fptas_epsilon].to_f
|
29
|
+
return unless eps <= 0 || eps >= 1 || eps.to_s != opts[:fptas_epsilon]
|
30
|
+
raise StandardError,
|
31
|
+
'FPTAS epsilon must be number from range (0,1)'
|
32
|
+
end
|
33
|
+
|
34
|
+
# Checks command-line options and arguments used by FPTAS solving
|
35
|
+
# method. Recignizes cases when mandatory FPTAS epsilon constant is
|
36
|
+
# missing or when it the constant is provided and FPTAS method is not
|
37
|
+
# requested.
|
38
|
+
#
|
39
|
+
# @param opts [Hash] parsed command-line options
|
40
|
+
def self.check_incomplete_fptas_options(opts)
|
41
|
+
raise StandardError, 'Missing FPTAS epsilon constant' if opts[:fptas] && !opts.key?(:fptas_epsilon)
|
42
|
+
return unless !opts[:fptas] && opts.key?(:fptas_epsilon)
|
43
|
+
raise StandardError,
|
44
|
+
'epsilon constant must not be provided when FPTAS is not selected'
|
45
|
+
end
|
46
|
+
|
47
|
+
# Checks directory for result and statistic output logs, and directory for
|
48
|
+
# graph files.
|
49
|
+
#
|
50
|
+
# @param opts [Hash] parsed command-line options
|
51
|
+
def self.check_directories(opts)
|
52
|
+
check_output_directory(opts[:output_dir]) if opts[:output_dir]
|
53
|
+
check_output_directory(opts[:graphs_dir]) if opts[:graphs_dir]
|
54
|
+
end
|
55
|
+
|
56
|
+
# Checks if at least one dataset input file was provided and if the input
|
57
|
+
# files are readable.
|
58
|
+
#
|
59
|
+
# @param args [Array<String>] positional arguments provided to the CLI
|
60
|
+
def self.check_positional_arguments(args)
|
61
|
+
raise StandardError, 'Missing datset file(s)' if args.empty?
|
62
|
+
args.each { |f| check_input_file(f) }
|
63
|
+
end
|
64
|
+
|
65
|
+
# Checks if an output directory exist and is writable.
|
66
|
+
#
|
67
|
+
# @param path [Path] path to output directory
|
68
|
+
def self.check_output_directory(path)
|
69
|
+
raise StandardError, "Directory '#{path}' does not exists" unless File.exist?(path)
|
70
|
+
raise StandardError, "'#{path}' is not a directory" unless File.directory?(path)
|
71
|
+
raise StandardError, "Directory '#{path}' is not writable" unless File.writable?(path)
|
72
|
+
end
|
73
|
+
|
74
|
+
# Checks if an input file exist and is readable.
|
75
|
+
#
|
76
|
+
# @param path [Path] path to input regular file
|
77
|
+
def self.check_input_file(path)
|
78
|
+
raise StandardError, "File '#{path}' does not exists" unless File.exist?(path)
|
79
|
+
raise StandardError, "'#{path}' is not a regular file" unless File.file?(path)
|
80
|
+
raise StandardError, "File '#{path}' is not readable" unless File.readable?(path)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'knapsack_solver/cli_option_checker'
|
3
|
+
|
4
|
+
module KnapsackSolver
|
5
|
+
# This class parses command line arguments provided to the knapsack_solver
|
6
|
+
# binary.
|
7
|
+
class CliOptionParser
|
8
|
+
# Message that describes how to use this CLI utility.
|
9
|
+
USAGE_MESSAGE = 'Usage: knapsack_solver OPTIONS DATASET_FILE...'.freeze
|
10
|
+
|
11
|
+
# Parses command-line arguments and removes them from the array of
|
12
|
+
# arguments.
|
13
|
+
#
|
14
|
+
# @param [Array] arguments the command-line arguments.
|
15
|
+
# @return [Hash] hash of recognized options.
|
16
|
+
#
|
17
|
+
# rubocop:disable Metrics/AbcSize, Metric/MethodLength, Metric/BlockLength
|
18
|
+
def self.parse(arguments)
|
19
|
+
options = {}
|
20
|
+
parser = OptionParser.new do |opts|
|
21
|
+
opts.banner = USAGE_MESSAGE
|
22
|
+
opts.on('-b', '--branch-and-bound', 'Use branch and boung method of solving') do
|
23
|
+
options[:branch_and_bound] = true
|
24
|
+
end
|
25
|
+
opts.on('-d', '--dynamic-programming', 'Use dynamic programming for solving') do
|
26
|
+
options[:dynamic_programming] = true
|
27
|
+
end
|
28
|
+
opts.on('-f', '--fptas', 'Use FPTAS for solving') do
|
29
|
+
options[:fptas] = true
|
30
|
+
end
|
31
|
+
opts.on('-r', '--heuristic', 'Use brute force method of solving') do
|
32
|
+
options[:heuristic] = true
|
33
|
+
end
|
34
|
+
opts.on('-e', '--fptas-epsilon EPS', 'Relative error for FPTAS from range (0,1)') do |eps|
|
35
|
+
options[:fptas_epsilon] = eps
|
36
|
+
end
|
37
|
+
opts.on('-o', '--output DIR', 'Directory for output log files') do |dir|
|
38
|
+
options[:output_dir] = dir
|
39
|
+
end
|
40
|
+
opts.on('-g', '--graphs DIR', 'Directory for graphs') do |dir|
|
41
|
+
options[:graphs_dir] = dir
|
42
|
+
end
|
43
|
+
opts.on('-v', '--version', 'Show program version') do
|
44
|
+
options[:version] = true
|
45
|
+
end
|
46
|
+
opts.on_tail('-h', '--help', 'Show this help message') do
|
47
|
+
options[:help] = true
|
48
|
+
end
|
49
|
+
end
|
50
|
+
parser.parse!(arguments)
|
51
|
+
process_help_and_version_opts(options, arguments, parser.to_s)
|
52
|
+
end
|
53
|
+
# rubocop:enable Metrics/AbcSize, Metric/MethodLength, Metric/BlockLength
|
54
|
+
|
55
|
+
def self.process_help_and_version_opts(options, arguments, usage_msg)
|
56
|
+
if !options[:help] && !options[:version]
|
57
|
+
CliOptionChecker.check(options, arguments)
|
58
|
+
return options
|
59
|
+
end
|
60
|
+
if options[:help]
|
61
|
+
puts usage_msg
|
62
|
+
elsif options[:version]
|
63
|
+
puts "knapsack_solver #{KnapsackSolver::VERSION}"
|
64
|
+
end
|
65
|
+
nil
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'knapsack_solver/instance'
|
2
|
+
|
3
|
+
module KnapsackSolver
|
4
|
+
# This class represents a set of 0/1 knapsack problem instances.
|
5
|
+
class Dataset
|
6
|
+
# Initializes set of 0/1 knapsack problem instances.
|
7
|
+
#
|
8
|
+
# @param id [Integer] Dataset ID number.
|
9
|
+
# @param instances [Array<Instance>] set of the 0/1 knapsack problem instances.
|
10
|
+
def initialize(id, instances)
|
11
|
+
@id = id
|
12
|
+
@instances = instances
|
13
|
+
end
|
14
|
+
|
15
|
+
# Parses set of a 0/1 knapsack problem instances from a character stream.
|
16
|
+
#
|
17
|
+
# @param stream [#eof?,#readline,#each_line] character stream holding the dataset.
|
18
|
+
# @return [Dataset] dataset instance parsed from the stream.
|
19
|
+
def self.parse(stream)
|
20
|
+
id = parse_id(stream)
|
21
|
+
instances = stream.each_line.with_object([]) { |l, o| o << Instance.parse(l) }
|
22
|
+
raise StandardError, 'dataset: missing instances' if instances.empty?
|
23
|
+
Dataset.new(id, instances)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Parses ID of a 0/1 knapsack problem dataset from a character stream.
|
27
|
+
#
|
28
|
+
# @param stream [#eof?,#readline,#each_line] character stream holding the dataset.
|
29
|
+
# @return [Integer] dataset ID number.
|
30
|
+
def self.parse_id(stream)
|
31
|
+
raise StandardError, 'dataset: missing ID' if stream.eof?
|
32
|
+
s = stream.readline.split
|
33
|
+
raise StandardError, 'dataset: first line does not contain ID' if s.size != 1
|
34
|
+
begin
|
35
|
+
raise StandardError, 'dataset: ID is negative' if Integer(s.first) < 0
|
36
|
+
rescue ArgumentError
|
37
|
+
raise StandardError, 'dataset: ID is not an integer'
|
38
|
+
end
|
39
|
+
Integer(s.first)
|
40
|
+
end
|
41
|
+
|
42
|
+
attr_reader :id, :instances
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'gnuplot'
|
2
|
+
|
3
|
+
module KnapsackSolver
|
4
|
+
# This class provides support for making graphs from statistics of datasets
|
5
|
+
# solving results. It uses Gnuplot and also generates a Gnuplot config file
|
6
|
+
# for each generated graph.
|
7
|
+
class GraphPrinter
|
8
|
+
# Initializes printer for graph data (graphs, Gnuplot config files).
|
9
|
+
#
|
10
|
+
# @param dataset_filenames [Array<String>] dataset filenames
|
11
|
+
# @param stats [Hash] statistics of results
|
12
|
+
# @param out_dir [String] statistics of results to print
|
13
|
+
def initialize(dataset_filenames, stats, out_dir)
|
14
|
+
@dataset_basenames = file_basenames(dataset_filenames)
|
15
|
+
@stats = stats
|
16
|
+
@out_dir = out_dir
|
17
|
+
end
|
18
|
+
|
19
|
+
# Create graphs from statistics and Gnuplot configuration files.
|
20
|
+
def print
|
21
|
+
stats_to_datasets.each do |title, ds|
|
22
|
+
ofn = File.join(@out_dir, title + '.png')
|
23
|
+
plot(title, ds, ofn)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
protected
|
28
|
+
|
29
|
+
# Create graph.
|
30
|
+
#
|
31
|
+
# @param title [String] title of the graph
|
32
|
+
# @param data [Array<Gnuplot::DataSet>] Gnuplot datasets to plot
|
33
|
+
# @param filename [String] path to the output image file
|
34
|
+
def plot(title, data, filename)
|
35
|
+
Gnuplot.open do |gp|
|
36
|
+
Gnuplot::Plot.new(gp, &plot_config(title, 'dataset', 'y', data, filename))
|
37
|
+
end
|
38
|
+
File.open(File.join(File.dirname(filename), File.basename(filename, '.png') + '.gnuplot'), 'w') do |gp|
|
39
|
+
Gnuplot::Plot.new(gp, &plot_config(title, 'dataset', 'y', data, filename))
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Creates Gnuplot datasets from statistics.
|
44
|
+
#
|
45
|
+
# @return [Array<Gnuplot::DataSet>] Gnuplot datasets created from the statistics.
|
46
|
+
def stats_to_datasets
|
47
|
+
graphs = @stats.values.first.values.first.first.keys
|
48
|
+
x_data = @stats.keys
|
49
|
+
datasets(graphs, x_data)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Creates Gnuplot datasets from statistics.
|
53
|
+
#
|
54
|
+
# @param graphs [Array] array of graph titles
|
55
|
+
# @param x_data [Array] array of X axis values
|
56
|
+
def datasets(graphs, x_data)
|
57
|
+
graphs.each_with_object({}) do |g, gnuplot_datasets|
|
58
|
+
@stats.each_value do |s|
|
59
|
+
gnuplot_datasets[g.to_s] = s.each_key.with_object([]) do |method, o|
|
60
|
+
o << plot_dataset(method.to_s, x_data, @stats.map { |_, v| v[method].first[g] })
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Create dataset from provided title, X axis data and Y axis data.
|
67
|
+
#
|
68
|
+
# @param title [String] Gnuplot dataset title
|
69
|
+
# @param x_data [Array] Array of X values
|
70
|
+
# @param y_data [Array] Array of Y values
|
71
|
+
# @return [Gnuplot::DataSet] Gnuplot dataset.
|
72
|
+
def plot_dataset(title, x_data, y_data)
|
73
|
+
Gnuplot::DataSet.new([x_data, y_data]) { |ds| ds.title = escape_gnuplot_special_chars(title) }
|
74
|
+
end
|
75
|
+
|
76
|
+
# Creates Gnuplot plot configuration (configuration text lines).
|
77
|
+
#
|
78
|
+
# @param title [String] graph title
|
79
|
+
# @param xlabel [String] label of X axis
|
80
|
+
# @param ylabel [String] label of Y axis
|
81
|
+
# @param plot_datasets [Array<Gnuplot::DataSet>] Gnuplot datasets for plotting
|
82
|
+
# @param out_file [String] output file
|
83
|
+
# @return [lambda] Lambda for setting plot configuration.
|
84
|
+
def plot_config(title, xlabel, ylabel, plot_datasets, out_file)
|
85
|
+
lambda do |plot|
|
86
|
+
plot.term('png')
|
87
|
+
plot.output(out_file)
|
88
|
+
plot.title("'#{escape_gnuplot_special_chars(title)}'")
|
89
|
+
plot.ylabel("'#{escape_gnuplot_special_chars(ylabel)}'")
|
90
|
+
plot.xlabel("'#{escape_gnuplot_special_chars(xlabel)}'")
|
91
|
+
plot.key('outside')
|
92
|
+
plot.data = plot_datasets
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# Escapes Gnuplot special characters.
|
97
|
+
#
|
98
|
+
# @param str [String] a string
|
99
|
+
# @return [Strnig] the string with Gnuplot special chars escaped
|
100
|
+
def escape_gnuplot_special_chars(str)
|
101
|
+
# underscore means subscript in Gnuplot
|
102
|
+
str.gsub('_', '\_')
|
103
|
+
end
|
104
|
+
|
105
|
+
# Gets basenames of supplied file paths.
|
106
|
+
#
|
107
|
+
# @param paths [Array<String>] path to files
|
108
|
+
# @return [Array<String>] basenames of the paths
|
109
|
+
def file_basenames(paths)
|
110
|
+
paths.each_with_object([]) do |path, basenames|
|
111
|
+
basenames << File.basename(path, File.extname(path))
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|