Ruby: Как удалить повторяющиеся строки из текста документа? - PullRequest
1 голос
/ 24 января 2020

Я хочу удалить повторяющиеся строки из текста, например:

1.aabba
2.abaab
3.aabba
4.aabba

После запуска:

1.aabba
2.abaab

Пробовал до сих пор:

lines = File.readlines("input.txt")
lines = File.read('/path/to/file')
lines.split("\n").uniq.join("\n")

Ответы [ 3 ]

3 голосов
/ 24 января 2020

Давайте создадим файл.

fname = 't'

IO.write fname, <<~END
dog
cat
dog
pig
cat
END
  #=> 20

См. IO :: write . Сначала давайте предположим, что вы просто хотите прочитать уникальные строки в массив.

Если, как здесь, файл не слишком большой, вы можете написать:

arr = IO.readlines(fname, chomp: true).uniq
  #=> ["dog", "cat", "pig"]

См. IO :: readlines . chomp: true удаляет символ новой строки в конце каждой строки.

Если вы хотите sh, чтобы записать этот массив в другой файл:

fname_out = 'tt'
IO.write(fname_out, arr.join("\n") << "\n")
  #=> 12

или

File.open(fname_out, 'w') do |f|
  arr.each { |line| f.puts line }
end

Если вы хотите sh перезаписать fname, запишите новый файл, удалите существующий файл и затем переименуйте новый файл fname.

Если файл настолько большой, что он не может быть сохранен в памяти и есть много повторяющихся строк, вы можете сделать следующее.

require 'set'

st = IO.foreach(fname, chomp: true).with_object(Set.new) do |line, st|
  st.add(line)
end
  #=> #<Set: {"dog", "cat", "pig"}>

См. IO :: foreach .

Если вы просто хотите sh записать содержимое этого набора в файл, вы можете выполнить:

File.open(fname_out, 'w') do |f|
  st.each { |s| f.puts(s) }
end

Если вместо этого вы необходимо преобразовать набор в массив:

st.to_a
  #=> ["dog", "cat", "pig"]

Предполагается, что у вас достаточно памяти для хранения st и st.to_a. Если нет, вы можете написать:

st.size.times.with_object([]) do |_,a|
  s = st.first
  a << s
  st.delete(s)
end
  #=> ["dog", "cat", "pig"]

Если у вас недостаточно памяти, чтобы даже удерживать st, вам нужно будет прочитать ваш файл (построчно) в базу данных, а затем использовать операции с базой данных.

Если вы хотите sh написать файл с пропущенными дубликатами, а файл очень большой, вы можете сделать следующее, хотя и с бесконечно малым риском включения одного или нескольких дубликатов (см. комментарии).

require 'set'

line_map = IO.foreach(fname, chomp: true).with_object({}) do |line,h|
  hsh = line.hash
  h[hsh] = $. unless h.key?(hsh)
end
  #=> {3393575068349183629=>1, -4358860729541388342=>2,
  #    -176447925574512206=>4} 

$. - номер (основание 1) только что прочитанной строки. См. Строка # га sh. Поскольку число различных значений, возвращаемых этим методом, конечно, а число возможных строк бесконечно, существует вероятность, что две различные строки могут иметь одинаковое значение ha sh.

Тогда (при условии line_map не является пустым):

lines_to_keep = line_map.values 
File.open(fname_out, 'w') do |fout|
  IO.foreach(fname, chomp: true) do |line|
    if lines_to_keep.first == $.
      fout.puts(line)
      lines_to_keep.shift
    end
  end
end

Давайте посмотрим, что мы написали:

puts File.read(fname_out)
dog
cat
pig

См. Файл :: open .

Между прочим, для IO методов класса m (включая read, write, readlines и foreach), вы можете увидеть IO.m... написано File.m.... Это допустимо, потому что File является подклассом IO и поэтому наследует методы последнего. Это не относится к моему использованию File::open, так как IO :: Open - это другой метод.

1 голос
/ 24 января 2020

Set хранит только уникальные элементы, поэтому:

require 'Set'

s = Set.new
while line = gets
  s << line.strip
end
s.each { |unique_elt| puts unique_elt }

Вы можете запустить это с любым входным файлом, используя < input.txt в командной строке, вместо того, чтобы вводить имя файла в вашу программу.

Обратите внимание, что Set основан на Hash, и в документации говорится «Хэши перечисляют свои значения в порядке, в котором были вставлены соответствующие ключи», поэтому это сохранит порядок ввода.

0 голосов
/ 25 января 2020

Вы можете продолжить свою идею с помощью uniq.

uniq для сравнения результата блока и удаления дубликатов.

Например, у вас есть input.txt с этим содержанием:

1.aabba
2.abaab
3.aabba
4.aabba
puts File.readlines('input.txt', chomp: true).
  uniq { |line| line.sub(/\A\d+\./, '') }.
  join("\n")

# will print
# 1.aabba
# 2.abaab

Здесь Sring#sub, которые удаляют номера списка, но вы можете использовать другие методы, например line[2..-1].

...