How to search strings of one file in another file and print line number of the match in ruby?

Viewed 93

I have this 2 files:

file1

Ruby
C
Visual Basic
R
Objective-C
Basic
HTML

file2

5. ab cde fg Java hij kl
2. ab PHP dddf llf 
4. cde fg z o Objective-C oode
8. a12b cde JavaScript kdk
6. ab99r cde Visual Basic llso dkd
1. lkd dsk Ruby kksdk
3. Python dsdls
7. kdjd C jdjd
9. CSS dkdsk
7. kkd Basic jjs
3. rooor R kdk

I´d like to search the strings in file1 and if they are found within file2, then print the "the string of file1", followed by line number in file2, and the line of file2 where each string is present.

The output format would be like this:

find "string" of file1|Line in file2 where "string" of file1 was found| text of line in file2 where "string" of file1 was found

For the sample file1 and file2 the output I´m looking for is:

Ruby|6|1. lkd dsk Ruby kksdk
C|8|7. kdjd C jdjd
Visual Basic|5|6. ab99r cde Visual Basic llso dkd
R|11|3. rooor R kdk
Objective-C|3|4. cde fg z o Objective-C oode
Basic|10|7. kkd Basic jjs
HTML|Not found

I've tried with the following code, but doesn´t find anything. My real file1 has about 32K lines and real file2 has about 48K lines.

require 'set'

f2_set = File.readlines("file2.txt").map(&:chomp).to_set
File.foreach("file1.txt") { |line| puts line if f2_set.include?(line) }

Thanks for any help

UPDATE

Below I show file1 and file2 proposed by @CarySwoveland and how would be the output for those inputs.

enter image description here

UPDATE2

Below samples of file1, file2 and output file3.

2 Answers

The problem as it is stated does not have a correct solution because there is no way to explain Ruby that Basic (next to the last element in matchers list) should not be taken into account when matching the line 6.. More sophisticated ruleset is required to accomplish what you want.

That would be a good starting point though:

matchers = File.readlines("file1.txt")
lines = File.readlines("file2.txt")

matchers.inject([]) do |acc, match|
  acc | lines.each.with_index(1).map do |line, idx|
    [match, idx, line].join("|") if line =~ /\b(#{match}\b)/
  end.compact
end
#⇒ ["Ruby|6|1. lkd dsk Ruby kksdk",
#   "C|3|4. cde fg z o Objective-C oode",
#   "C|8|7. kdjd C jdjd",
#   "Visual Basic|5|6. ab99r cde Visual Basic llso dkd",
#   "R|11|3. rooor R kdk",
#   "Objective-C|3|4. cde fg z o Objective-C oode",
#   "Basic|5|6. ab99r cde Visual Basic llso dkd",
#   "Basic|10|7. kkd Basic jjs"]

I assume each line in file 1 appears in at most one line in file 2 and each line of file 2 contains no more than one language in file 1, which is consistent with the example given in the question.

Let's first construct the files. To make life more interesting, I've modified the contents of both files given in the question.

file1 =<<-END
Ruby
C
Visual Basic
C++
R
Objective-C++
Basic
HTML
END

FName1 = 'file1'
File.write(FName1, file1)
  #=> 51

file2 =<<-END
5. ab cde fg Java hij kl
2. ab PHP dddf llf 
4. cde fg z o Objective-C++ oode
8. a12b cde JavaScript kdk
6. ab99r cde Visual Basic llso dkd
1. lkd dsk Ruby kksdk
3. Python dsdls
7. kdjd C jdjd
9. CSS dkdsk
10. blah C++ blah
7. kkd Basic jjs
3. rooor R kdk
END

FName2 = 'file2'
File.write(FName2, file2)
  #=> 256

First read the lines of FName1 into an array.

languages = File.readlines(FName1, chomp:true)
  #=> ["Ruby", "C", "Visual Basic", "C++",
  #    "R", "Objective-C++", "Basic", "HTML"]  

Now, for convenience, order the elements of languages be decreasing length.

sorted_languages = languages.sort_by(&:length).reverse
  #=> ["Objective-C++", "Visual Basic", "Basic",
  #    "Ruby", "HTML", "C++", "C", "R"] 

I've sorted the elements of languages by decreasing word length so that an attempt to match a line of FName2 with 'Objective-C++' will be made before an attempt is made to match 'C++', and 'C++' will be considered before 'C'. Similarly, 'Visual Basic' will be considered as a match before 'Basic' is considered.

Next, create a hash whose keys are those lines in FName1 that appear in a line of FName2 and whose values are hashes identifying the line number and line in FName2 for the given key.

language_to_file2 = File.foreach(FName2, chomp: true).
  with_index(1).
  with_object({}) do |(line,n),h|
    language = sorted_languages.find { |language| line.include?(language) }
    h[language] = { line: line, nbr: n } unless language.nil?
  end
  #=> {"Objective-C++"=>{:line=>"4. cde fg z o Objective-C++ oode", :nbr=>3},
  #    "Visual Basic" =>{:line=>"6. ab99r cde Visual Basic llso dkd", :nbr=>5},
  #    "Ruby"         =>{:line=>"1. lkd dsk Ruby kksdk", :nbr=>6},
  #    "C"            =>{:line=>"7. kdjd C jdjd", :nbr=>8},
  #    "C++"          =>{:line=>"10. blah C++ blah", :nbr=>10},
  #    "Basic"        =>{:line=>"7. kkd Basic jjs", :nbr=>11},
  #    "R"            =>{:line=>"3. rooor R kdk", :nbr=>12}}

We may now display the desired result.

languages.each do |language|
  print "#{language}|"
  if language_to_file2.key?(language)
    h = language_to_file2[language]
    puts "%d|%s" % [h[:nbr], h[:line]]
  else
    puts "Not found"
  end
end
Ruby|6|1. lkd dsk Ruby kksdk
C|8|7. kdjd C jdjd
Visual Basic|5|6. ab99r cde Visual Basic llso dkd
C++|10|10. blah C++ blah
R|12|3. rooor R kdk
Objective-C++|3|4. cde fg z o Objective-C++ oode
Basic|11|7. kkd Basic jjs
HTML|Not found
Related