Pergunta
Como faço uma comparação de duas strings ou arrays em Ruby?
Solução
diff.rb é o que você deseja, que está disponível em http://users.cybercity.dk/~dsl8950/ruby/diff.html via arquivo da internet:
http://web.archive.org/web/20140421214841/http://users.cybercity.dk:80/~dsl8950/ruby/diff.html
Outras dicas
Para matrizes, use o operador menos.Por exemplo:
>> foo = [1, 2, 3]
=> [1, 2, 3]
>> goo = [2, 3, 4]
=> [2, 3, 4]
>> foo - goo
=> [1]
Aqui a última linha remove tudo do foo que também está no goo, deixando apenas o elemento 1.Não sei como fazer isso para duas strings, mas até que alguém que saiba poste sobre isso, você pode simplesmente converter cada string em um array, usar o operador menos e depois converter o resultado novamente.
Fiquei frustrado com a falta de uma boa biblioteca para isso em Ruby, então escrevi http://github.com/samg/diffy.Ele usa diff
nos bastidores e se concentra em ser conveniente e fornecer opções de saída bonitas.
Para strings, eu experimentaria primeiro o Ruby Gem que @sam-saffron mencionou abaixo.É mais fácil de instalar:http://github.com/pvande/differ/tree/master
gem install differ
irb
require 'differ'
one = "one two three"
two = "one two 3"
Differ.format = :color
puts Differ.diff_by_word(one, two).to_s
Differ.format = :html
puts Differ.diff_by_word(one, two).to_s
O HTMLDiff mencionado por @da01 acima funcionou para mim.
script/plugin install git://github.com/myobie/htmldiff.git
# bottom of environment.rb
require 'htmldiff'
# in model
class Page < ActiveRecord::Base
extend HTMLDiff
end
# in view
<h1>Revisions for <%= @page.name %></h1>
<ul>
<% @page.revisions.each do |revision| %>
<li>
<b>Revised <%= distance_of_time_in_words_to_now revision.created_at %> ago</b><BR>
<%= Page.diff(
revision.changes['description'][0],
revision.changes['description'][1]
) %>
<BR><BR>
</li>
<% end %>
# in style.css
ins.diffmod, ins.diffins { background: #d4fdd5; text-decoration: none; }
del.diffmod, del.diffdel { color: #ff9999; }
Parece muito bom.A propósito, usei isso com o acts_as_audited
plugar.
Há também diff-lcs
que está disponível como uma joia. Não é atualizado desde 2004, mas temos usado sem nenhum problema.
Editar: Uma nova versão foi lançada em 2011.Parece que está de volta ao desenvolvimento ativo.
t=s2.chars; s1.chars.map{|c| c == t.shift ? c : '^'}.join
Esta linha simples dá uma ^
nas posições que não correspondem.Muitas vezes isso é suficiente e pode ser copiado/colado.
Acabei de encontrar um novo projeto que parece bastante flexível:
http://github.com/pvande/differ/tree/master
Experimentando e tentarei postar algum tipo de relatório.
Tive a mesma dúvida e a solução que encontrei não é 100% Ruby, mas é a melhor para mim.O problema do diff.rb é que ele não possui um formatador bonito, para mostrar as diferenças de forma humanizada.Então usei o diff do sistema operacional com este código:
def diff str1, str2
system "diff #{file_for str1} #{file_for str2}"
end
private
def file_for text
exp = Tempfile.new("bk", "/tmp").open
exp.write(text)
exp.close
exp.path
end
Apenas para o benefício do pessoal do Windows:diffy parece brilhante, mas acredito que só funcionará no *nix (corrija-me se estiver errado).Certamente não funcionou na minha máquina.
Differ funcionou muito bem para mim (Windows 7 x64, Ruby 1.8.7).
Talvez Array.diff via monkey-patch ajude...
http://grosser.it/2011/07/07/ruby-array-diffother-difference-between-2-arrays/
Para obter a resolução caractere por caractere, adicionei uma nova função ao gema damerau-levenshtein
require "damerau-levenshtein"
differ = DamerauLevenshtein::Differ.new
differ.run "Something", "Smothing"
# returns ["S<ins>o</ins>m<subst>e</subst>thing",
# "S<del>o</del>m<subst>o</subst>thing"]
ou com análise:
require "damerau-levenshtein"
require "nokogiri"
differ = DamerauLevenshtein::Differ.new
res = differ.run("Something", "Smothing!")
nodes = Nokogiri::XML("<root>#{res.first}</root>")
markup = nodes.root.children.map do |n|
case n.name
when "text"
n.text
when "del"
"~~#{n.children.first.text}~~"
when "ins"
"*#{n.children.first.text}*"
when "subst"
"**#{n.children.first.text}**"
end
end.join("")
puts markup