Merge CSV files same unique ID with Ruby - ruby

I have two CSV files
file1.csv
username;userid;full_name;follower_count;following_count;media_count;email;category
helloworld;1234;data3;data4;data5;data6;data7;data8
file2.csv
username;owner_id;owner_profile_pic_url;media_url;tagged_brand_username
helloworld;1234;data3b;data4b;data5b
I need the following output file using Ruby with blank if file1.csv username is not found in file2.csv (e.g. row 2).
output.csv
username;userid;full_name;follower_count;following_count;media_count;email;category;owner_profile_pic_url;media_url;tagged_brand_username
helloworld;1234;data3;data4;data5;data6;data7;data8;data3b;data4b;data5b
helloworld;1234;data3;data4;data5;data6;data7;data8;;;
Currently I'm doing it using a Excel vlookup function.
Thanks

There's a lot to unpack in this script. Essentially you need to read both CSV files into a hash, merge file2 into file1, and write it back to a CSV.
require "csv"
dict = Hash.new
options = { col_sep: ";", headers: true}
# read file1
CSV.foreach("file1.csv", options) do |row|
row = row.to_h
user = "#{row['username']+row['userid']}"
dict[user] = row
end
# read file2
CSV.foreach("file2.csv", options) do |row|
row = row.to_h
user = "#{row['username']+row['owner_id']}"
row.delete('owner_id')
dict[user] = row.merge(dict[user]) if dict[user]
end
# turn hash into rows
rows = [['username','userid','full_name','follower_count','following_count','media_count','email','category','owner_profile_pic_url','media_url','tagged_brand_username']]
dict.each do |key, value|
row = rows[0].map{|h| value[h] || "" }
rows.push(row)
end
# write to csv
File.write("output.csv", rows.map{|r| r.to_csv(col_sep: ";") }.join)
This covers both when there is a match and no username match in file1.
# file1.csv
username;userid;full_name;follower_count;following_count;media_count;email;category
helloworld;1234;data3;data4;data5;data6;data7;data8
goodbyeworld;5678;data3;data4;data5;data6;data7;file2.csv
# file2.csv
username;owner_id;owner_profile_pic_url;media_url;tagged_brand_username
helloworld;1234;data3b;data4b;data5b
# output.csv
username;userid;full_name;follower_count;following_count;media_count;email;category;owner_profile_pic_url;media_url;tagged_brand_username
helloworld;1234;data3;data4;data5;data6;data7;data8;data3b;data4b;data5b
goodbyeworld;5678;data3;data4;data5;data6;data7;data8;"";"";""
As mentioned, the fact that there is two lines with the same ID in output.csv is very confusing. Next time just add an extra row showing what happens if there's no match. While this is a good question, we have guidelines on how to write an excellent question.

There are two existing CSV input files and we wish to create one CSV output file:
FNAME1 = 'file1.csv'
FNAME2 = 'file2.csv'
FILE_OUT = 'output.csv'
Let's first create the two input files.
File.write(FNAME1, "username;userid;full_name;follower_count;following_count;media_count;email;category\nhelloworld;1234;data3;data4;data5;data6;data7;data8\n")
#=> 136
File.write(FNAME2, "username;owner_id;owner_profile_pic_url;media_url;tagged_brand_username\nhelloworld;1234;data3b;data4b;data5b\n")
#=> 109
Now go through the steps to read those files, manipulate their contents and write the output file.
require 'csv'
First read both input files and save their contents in variables.
def read_csv(fname)
CSV.read(fname, col_sep: ';', headers: true)
end
csv1 = read_csv(FNAME1)
#=> #<CSV::Table mode:col_or_row row_count:2>
csv2 = read_csv(FNAME2)
#=> #<CSV::Table mode:col_or_row row_count:2>
Note:
csv1.to_a
#=> [["username", "userid", "full_name", "follower_count", "following_count",
# "media_count", "email", "category"],
# ["helloworld", "1234", "data3", "data4", "data5",
# "data6", "data7", "data8"]]
csv2.to_a
#=> [["username", "owner_id", "owner_profile_pic_url", "media_url", "tagged_brand_username"],
# ["helloworld", "1234", "data3b", "data4b", "data5b"]]
As you see, these are ordinary arrays, so if we wished we could at this point forget they came from CSV files and use standard Ruby methods to create the desired output file.
Now see if the values of "username" are the same in both files:
username1 = csv1['username'].first
#=> "helloworld"
username2 = csv2['username'].first
#=> "helloworld"
csv1['username'] creates an array of all values in the "helloworld" column. Here that is simply ["helloworld"]; hence .first. Same for csv2, of course.
If username1 == username2 #=> false we perform an action that I am not clear about, then quit. Henceforth, I assume the two usernames are equal.
Read the headers of both files into arrays.
headers1 = csv1.headers
#=> ["username", "userid", "full_name", "follower_count", "following_count",
# "media_count", "email", "category"]
headers2 = csv2.headers
#=> ["username", "owner_id", "owner_profile_pic_url", "media_url",
# "tagged_brand_username"]
The output file is to contain all the columns in headers1 and all the columns in headers2 with the exception of "username" and "owner_id" in headers2, so let's next get rid of those headers in headers2:
headers2 -= ["username", "owner_id"]
#=> ["owner_profile_pic_url", "media_url", "tagged_brand_username"]
Next retrieve the values of the headers in the first file:
values1 = headers1.flat_map { |h| csv1[h] }
#=> ["helloworld", "1234", "data3", "data4", "data5", "data6", "data7", "data8"]
and the values of the remaining headers in the second file:
values2 = headers2.flat_map { |h| csv2[h] }
#=> ["data3b", "data4b", "data5b"]
We will modify values2 below so we need to save its current size:
values2_size = values2.size
#=> i
The first line in the output file after the header line is to contain the values:
values1 += values2
#=> ["helloworld", "1234", "data3", "data4", "data5", "data6", "data7", "data8",
# "data3b", "data4b", "data5b"]
and the second line is to contain:
values2 = values1 - values2
#=> ["helloworld", "1234", "data3", "data4", "data5", "data6", "data7", "data8",
plus values2_size #=> 3 empty fields.
We could use CSV methods to write this to file, but there is really no advantage in doing so over using regular file methods. We can simply write the following string to file.
str = [(headers1 + headers2).join(';'),
values1.join(';'),
values2.join(';') + ';' * values2_size
].join("\n")
puts str
username;userid;full_name;follower_count;following_count;media_count;email;category;owner_profile_pic_url;media_url;tagged_brand_username
helloworld;1234;data3;data4;data5;data6;data7;data8;data3b;data4b;data5b
helloworld;1234;data3;data4;data5;data6;data7;data8;;;
Let's do it.
File.write(FILE_OUT, str)
#=> 265
Note that, if a and b are arrays, a += b and a -= b expand to a = a + b and a = a - b, respectively. The CSV methods I've used are documented here.
I will leave it to the OP to combine the operations I've discussed into a method.

Related

manipulating csv with ruby

I have a CSV from which I've removed the irrelevant data.
Now I need to split "Name and surname" into 2 columns by space but ignoring a 3rd column in case there are 3 names, then invert the order of the columns "Name and surname" and "Phone" (phone first) and then put them into a file ignoring the headers. I've never actually learned Ruby but I've played with Python 10 years ago. Can you help me? This is what I was able to do until now:
E.g.
require 'csv'
csv_table = CSV.read(ARGV[0], :headers => true)
keep = ["Name and surname", "Phone", "Email"]
new_csv_table = csv_table.by_col!.delete_if do |column_name,column_values|
!keep.include? column_name
end
new_csv_table.to_csv
Begin by creating a CSV file.
str =<<~END
Name and surname,Phone,Email
John Doe,250-256-3145,John#Doe.com
Marsha Magpie,250-256-3154,Marsha#Magpie.com
END
File.write('t_in.csv', str)
#=> 109
Initially, let's read the file, add two columns, "Name" and "Surname", and optionally delete the column, "Name and surname", without regard to column order.
First read the file into a CSV::Table object.
require 'csv'
tbl = CSV.read('t_in.csv', headers: true)
#=> #<CSV::Table mode:col_or_row row_count:3>
Add the new columns.
tbl.each do |row|
row["Name"], row["Surname"] = row["Name and surname"].split
end
#=> #<CSV::Table mode:col_or_row row_count:3>
Note that if row["Name and surname"] had equaled “John Paul Jones”, we would have obtained row["Name"] #=> “John” and row["Surname"] #=> “Paul”.
If the column "Name and surname" is no longer required we can delete it.
tbl.delete("Name and surname")
#=> ["John Doe", "Marsha Magpie"]
Write tbl to a new CSV file.
CSV.open('t_out.csv', "w") do |csv|
csv << tbl.headers
tbl.each { |row| csv << row }
end
#=> #<CSV::Table mode:col_or_row row_count:3>
Let's see what was written.
puts File.read('t_out.csv')
displays
Phone,Email,Name,Surname
250-256-3145,John#Doe.com,John,Doe
250-256-3154,Marsha#Magpie.com,Marsha,Magpie
Now let's rearrange the order of the columns.
header_order = ["Phone", "Name", "Surname", "Email"]
CSV.open('t_out.csv', "w") do |csv|
csv << header_order
tbl.each { |row| csv << header_order.map { |header| row[header] } }
end
puts File.read('t_out.csv')
#=> #<CSV::Table mode:col_or_row row_count:3>
displays
Phone,Name,Surname,Email
250-256-3145,John,Doe,John#Doe.com
250-256-3154,Marsha,Magpie,Marsha#Magpie.com

How to detect the column separator of a CSV file in Ruby?

I've had several issues where CSV files that our board members uploaded weren't getting parsed correctly because of inconsistent format: comma-separated, semicolon-separated, tab-separated... Usually they didn't even know which separator had been used because Excel / LibreOffice Calc don't specify it when exporting to CSV.
This function assumes that the CSV has at least 4 columns. It looks for the alternation of separators and non-separators in the file's first line (= usually the headers, which are unlikely to have extra commas or weird characters) and then returns the separator that is most common.
def self.detect_separator(file)
firstline = File.open(file, &:readline)
if firstline
separators = ",;\t|#"
non_sep = "[^" + separators + "]+"
sep = "([" + separators + "])"
reg = Regexp.new(non_sep + sep + non_sep + sep + non_sep + sep + non_sep + sep)
m = firstline.match(reg)
if m
four_separators = m[1..-1].join('') # this line should have four separators but may have less if the data is less conclusive
detected_separator = separators.split('').map {|x| [four_separators.count(x),x]}.max
return detected_separator[1] if detected_separator
end
end
nil
end
Let's begin by constructing a possible CSV file.
arr = [
["abc", "d;ef", "hi;j", "k;l;mnp"],
["efg", "i;jk", "mn;p", "q;r;stu"],
["tuv", "w;xy", "zg;b", "c;d;e;f"]
]
FName = 't.csv'
require 'csv'
We will use commas to separate the columns1.
CSV.open(FName, mode='w', col_sep: ',') { |csv| arr.each { |s| csv << s } }
Let's look at the file we've created.
puts File.read(FName)
# abc,d;ef,hi;j,k;l;mnp
# efg,i;jk,mn;p,q;r;stu
# tuv,w;xy,zg;b,c;d;e;f
Now let's read this file, first with a comma as the column separator and then with a semicolon as the separator.
arr_comma = CSV.read(FName, col_sep: ',')
#=> [["abc", "d;ef", "hi;j", "k;l;mnp"],
# ["efg", "i;jk", "mn;p", "q;r;stu"],
# ["tuv", "w;xy", "zg;b", "c;d;e;f"]]
arr_semicolon = CSV.read(FName, col_sep: ';')
#=> [["abc,d", "ef,hi", "j,k", "l", "mnp"],
# ["efg,i", "jk,mn", "p,q", "r", "stu"],
# ["tuv,w", "xy,zg", "b,c", "d", "e", "f"]]
As expected, arr_comma is an array of three 4-element arrays. By contrast, arr_semicolon, contains two 5-element arrays and one 6-element array. Assuming empty strings are included for missing fields (e.g., ...ef;;gh;...), this tells us that the file is consistent with a comma having been used as the column separator, but not a semicolon.
We could write a small method to check that each row has the same number of columns for a given column separator.
def sep_check(filename, sep)
CSV.read(FName, col_sep: sep).map(&:size).uniq.size == 1
end
sep_check(FName, ',')
#=> true
sep_check(FName, ';')
#=> false
Note that CSV rows may contain quoted strings. If quoted strings contain the column-separator character they are not treated as column separators. Here's an example.
arr = [
["abc", "d;ef", "h\"q,r\"i;j", "k;l;mnp"],
["efg", "i;jk", "mn;p", "q;r;stu"],
["tuv", "w;xy", "zg;b", "c;d;e;f"]
]
CSV.open(FName, mode='w', col_sep: ',') { |csv| arr.each { |s| csv << s } }
puts File.read(FName)
# abc,d;ef,"h""q,r""i;j",k;l;mnp
# efg,i;jk,mn;p,q;r;stu
# tuv,w;xy,zg;b,c;d;e;f
sep_check(FName, ',')
#=> true
sep_check(FName, ';')
#=> CSV::MalformedCSVError (Illegal quoting in line 1.)
This is another condition that excludes a semicolon from being the column separator, but not a comma.
1. Including , col_sep: ',' is optional, as the default value for col_sep is a comma.

Ruby - Merge CSV duplicate columns with same SKU

I have created a CSV file about my eshop that contains multiple items with different SKUs. Some SKUs appear more than once because they can be in more than one category (but the Title and Price will always be the same for a given SKU). Example:
SKU,Title,Category,Price
001,Soap,Bathroom,0.5
001,Soap,Kitchen,0.5
002,Water,Kitchen,0.4
002,Water,Garage,0.4
003,Juice,Kitchen,0.8
I now wish to create from that file another CSV file that has no duplicate SKU's and aggregates the "Category" attributes as follows:
SKU,Title,Category,Price
001,Soap,Bathroom/Kitchen,0.5
002,Water,Kitchen/Garage,0.4
003,Juice,Kitchen,0.8
How can I do that?
It's my understand you wish to read a CSV file, perform some operations on the data and then write the result to a new CSV file. You could do that as follows.
Code
require 'csv'
def convert(csv_file_in, csv_file_out, group_field, aggregate_field)
csv = CSV.read(FNameIn, headers: true)
headers = csv.headers
arr = csv.group_by { |row| row[group_field] }.
map do |_,a|
headers.map { |h| h==aggregate_field ?
(a.map { |row| row[aggregate_field] }.join('/')) : a.first[h] }
end
CSV.open(FNameOut, "wb") do |csv|
csv << headers
arr.each { |row| csv << row }
end
end
Example
Let's create a CSV file with the following data:
s =<<_
SKU,Title,Category,Price
001,Soap,Bathroom,0.5
001,Soap,Kitchen,0.5
002,Water,Kitchen,0.4
002,Water,Garage,0.4
003,Juice,Kitchen,0.8
_
FNameIn = 'testin.csv'
FNameOut = 'testout.csv'
IO.write(FNameIn, s)
#=> 135
Now execute the method with these values:
convert(FNameIn, FNameOut, "SKU", "Category")
and confirm FNameOut was written correctly:
puts IO.read(FNameOut)
SKU,Title,Category,Price
001,Soap,Bathroom/Kitchen,0.5
002,Water,Kitchen/Garage,0.4
003,Juice,Kitchen,0.8
Explanation
The steps are as follows:
csv_file_in = FNameIn
csv_file_out = FNameOut
group_field = "SKU"
aggregate_field = "Category"
csv = CSV.read(FNameIn, headers: true)
See CSV::read.
headers = csv.headers
#=> ["SKU", "Title", "Category", "Price"]
h = csv.group_by { |row| row[group_field] }
#=> {"001"=>[
#<CSV::Row "SKU":"001" "Title":"Soap" "Category":"Bathroom" "Price":"0.5">,
# #<CSV::Row "SKU":"001" "Title":"Soap" "Category":"Kitchen" "Price":"0.5">
# ],
# "002"=>[
# #<CSV::Row "SKU":"002" "Title":"Water" "Category":"Kitchen" "Price":"0.4">,
# #<CSV::Row "SKU":"002" "Title":"Water" "Category":"Garage" "Price":"0.4">
# ],
# "003"=>[
# #<CSV::Row "SKU":"003" "Title":"Juice" "Category":"Kitchen" "Price":"0.8">
# ]
# }
arr = h.map do |_,a|
headers.map { |h| h==aggregate_field ?
(a.map { |row| row[aggregate_field] }.join('/')) : a.first[h] }
end
#=> [["001", "Soap", "Bathroom/Kitchen", "0.5"],
# ["002", "Water", "Kitchen/Garage", "0.4"],
# ["003", "Juice", "Kitchen", "0.8"]]
See CSV#headers and Enumerable#group_by, an oft-used method. Lastly, write the output file:
CSV.open(FNameOut, "wb") do |csv|
csv << headers
arr.each { |row| csv << row }
end
See CSV::open. Now let's return to the calculation of arr. This is most easily explained by inserting some puts statements and executing the code.
arr = h.map do |_,a|
puts " _=#{_}"
puts " a=#{a}"
headers.map do |h|
puts " header=#{h}"
if h==aggregate_field
a.map { |row| row[aggregate_field] }.join('/')
else
a.first[h]
end.
tap { |s| puts " mapped to #{s}" }
end
end
See Object#tap. The following is displayed.
_=001
a=[#<CSV::Row "SKU":"001" "Title":"Soap" "Category":"Bathroom" "Price":"0.5">,
#<CSV::Row "SKU":"001" "Title":"Soap" "Category":"Kitchen" "Price":"0.5">]
header=SKU
mapped to 001
header=Title
mapped to Soap
header=Category
mapped to Bathroom/Kitchen
header=Price
mapped to 0.5
_=002
a=[#<CSV::Row "SKU":"002" "Title":"Water" "Category":"Kitchen" "Price":"0.4">,
#<CSV::Row "SKU":"002" "Title":"Water" "Category":"Garage" "Price":"0.4">]
header=SKU
mapped to 002
header=Title
mapped to Water
header=Category
mapped to Kitchen/Garage
header=Price
mapped to 0.4
_=003
a=[#<CSV::Row "SKU":"003" "Title":"Juice" "Category":"Kitchen" "Price":"0.8">]
header=SKU
mapped to 003
header=Title
mapped to Juice
header=Category
mapped to Kitchen
header=Price
mapped to 0.8
It seems that in order for this to be correct, we must assume the SKU number and the price are always the same. Since you know the only key you want to merge data between is Category here is how you can do it.
Assuming this is your test.csv in the same path as the ruby script:
# test.csv
SKU,Title,Category,Price
001,Soap,Bathroom,0.5
001,Soap,Kitchen,0.5
002,Water,Kitchen,0.4
002,Water,Garage,0.4
003,Juice,Kitchen,0.8
Ruby script in same directory as your test.csv file
# fix_csv.rb
require 'csv'
rows = CSV.read 'test.csv', :headers => true
skews = rows.group_by{|row| row['SKU']}.keys.uniq
values = rows.group_by{|row| row['SKU']}
merged = skews.map do |key|
group = values.select{|k,v| k == key}.values.flatten.map(&:to_h)
category = group.map{|k,v| k['Category']}.join('/')
new_data = group[0]
new_data['Category'] = category
new_data
end
CSV.open('merged_data.csv', 'w') do |csv|
csv << merged.first.keys # writes the header row
merged.each do |hash|
csv << hash.values
end
end
puts 'see contents of merged_data.csv'

Wrapping output of an array to CSV conversion in quotations in Ruby

What I'm wanting to find out is how to have every entry passed from the array to the CSV at the end of the program be wrapped by " "'s to allow Excel to read it correctly. I know this needs to be done before or during the "push" at line 34, but doing "streets.push('"'+street_name+'"')" results in every entry being surrounded by THREE quotation marks, which doesn't make much sense to me.
#!ruby.exe
require 'csv'
puts "Please enter a file name:" #user input file name (must be in same
folder as this file)
file = gets.chomp
begin
File.open(file, 'r')
rescue
print "Failed to open #{file}\n"
exit
end #makes sure that the file exists, if it does not it posts an error
data_file = File.new(file)
data = [] #initializes array for addresses from .csv
counter=0 #set counter up to allow for different sized files to be used
without issue
CSV.foreach(data_file, headers: true) do |row|
data << row.to_hash
counter+=1
end #goes through .csv one line ar a time
data.reject(&:empty?)
puts "Which column do you want to parse?"
column = gets.chomp
i=0
streets = []
while (i<counter)
address = data[i][column]
street_name = address.gsub(/^((\d[a-zA-Z])|[^a-zA-Z])*/, '')
streets.push(street_name)
i+=1
end
streets.reject(&:empty?)
puts "What do you want the output to be called?"
new_file = gets.chomp
CSV.open(new_file, "w", :write_headers=> true, :headers => [column]) do |hdr|
hdr << streets
end
You can pass the :force_quotes option to the CSV library to have it quote everything in the csv for you:
base_options = {headers: ['first,col', 'second column'], write_headers: true}
options = [{}, {force_quotes: true}]
data = [
['a', 'b'],
['c', 'd'],
['e', 'f']
]
options.each do |option|
result = CSV.generate(base_options.merge(option)) do |csv|
data.each do |datum|
csv << datum
end
end
puts "#{option}:\n#{result}"
end
For instance, in this small script, by default, the only thing that gets quoted is the first column header because it contains a comma. By passing in force_quotes: true, in the second pass though, everything gets quoted.
Output:
{}:
"first,col",second column
a,b
c,d
e,f
{:force_quotes=>true}:
"first,col","second column"
"a","b"
"c","d"
"e","f"
You can use map to process the array before putting it in csv.
streets.map!{|s| '"'+s+'"'}

selective replacing of printf statements

I am trying to search for a bunch of print statements that I want to filter as follows:
I want to select all dbg_printfs.
Out of all of those I want to select those that have value.stringValue().
Out of those I only want those that do not have value.stringValue().value().
Finally, I want to replace those lines with value.stringValue() to value.stringValue().value().
I don't know why my current code isn't working?
fileObj = File.new(filepath, "r")
while (line = fileObj.gets)
line.scan(/dbg_printf/) do
line.scan(/value.stringValue()/) do
if !line.scan(/\.value\(\)/)
line.gsub!(/value.stringValue()/, 'value.stringValue().value()')
end
end
end
fileObj.close
Primarily, your problem seems to be that you expect altering the string returned from gets to alter the contents of the file. There isn't actually that kind of relationship between strings and files. You need to explicitly write the modifications to the file. Personally, I would probably write that code like this:
modified_contents = IO.readlines(filepath).map do |line|
if line =~ /dbg_printf/
# This regex just checks for value.stringValue() when not followed by .value()
line.gsub /value\.stringValue\(\)(?!\.value\(\))/, 'value.stringValue().value()'
else
line
end
end
File.open(filepath, 'w') {|file| file.puts modified_contents }
The problem is that you are not writing the changed lines back to the same file or a new file. To write them to the same file, read the file into an array, change the array and then write it back to the same or a different file (the later being the more prudent). Here's one way to do that with few lines of code.
Code
fin_name and fout_name are the names (with paths) of the input and output files, respectively.
def filter_array(fin_name, fout_name)
arr_in = File.readlines(fin_name)
arr_out = arr_in.map { |l| (l.include?('dbg_printfs') &&
l.include?('value.stringValue()') &&
!l.include?('value.stringValue().value()')) ?
'value.stringValue() to value.stringValue().value()' : l }
File.open(fout_name, 'w') { |f| f.puts arr_out }
end
Because you are reading code files, they will not be so large that reading them all at once into memory will be a problem.
Example
First, we'll construct an input file:
array = ["My dbg_printfs was a value.stringValue() as well.",
"Her dbg_printfs was a value.stringValue() but not " +
"a value.stringValue().value()",
"value.stringValue() is one of my favorites"]
fin_name = 'fin'
fout_name = 'fout'
File.open(fin_name, 'w') { |f| f.puts array }
We can confirm its contents with:
File.readlines(fin_name).map { |l| puts l }
Now try it:
filter_array(fin_name, fout_name)
Read the output file to see if it worked:
File.readlines(fout_name).map { |l| puts l }
#=> value.stringValue() to value.stringValue().value()
# Her dbg_printfs was a value.stringValue() but not a value.stringValue().value()
# value.stringValue() is one of my favorites
It looks OK.
Explanation
def filter_array(fin_name, fout_name)
arr_in = File.readlines(fin_name)
arr_out = arr_in.map { |l| (l.include?('dbg_printfs') &&
l.include?('value.stringValue()') &&
!l.include?('value.stringValue().value()')) ?
'value.stringValue() to value.stringValue().value()' : l }
File.open(fout_name, 'w') { |f| f.puts arr_out }
end
For the above example,
arr_in = File.readlines('fin')
#=> ["My dbg_printfs was a value.stringValue() as well.\n",
# "Her dbg_printfs was a value.stringValue() but not a value.stringValue().value()\n",
# "value.stringValue() is one of my favorites\n"]
The first element of arr_in passed to map is:
l = "My dbg_printfs] was a value.stringValue() as well."
We have
l.include?('dbg_printfs') #=> true
l.include?('value.stringValue()') #=> true
!l.include?('value.stringValue().value()') #=> true
so that element is mapped to:
"value.stringValue() to value.stringValue().value()"
Neither of the other two elements are replaced by this string, because
!l.include?('value.stringValue().value()') #=> false
and
l.include?('dbg_printfs') #=> false
respectively. Hence,
arr_out = arr_in.map { |l| (l.include?('dbg_printfs') &&
l.include?('value.stringValue()') &&
!l.include?('value.stringValue().value()')) ?
'value.stringValue() to value.stringValue().value()' : l }
#=> ["value.stringValue() to value.stringValue().value()",
# "Her dbg_printfs was a value.stringValue() but not a value.stringValue().value()\n",
# "value.stringValue() is one of my favorites\n"]
The final step is writing arr_out to the output file.

Resources