Ruby: Как добавить уникальные значения к дубликатам?Например (значение, значение_2, значение_3) - PullRequest
0 голосов
/ 17 апреля 2019

Я использую программное обеспечение, которое не допускает дублирования значений для поля «Node_ID», узлы представляют собой набор объектов с несколькими полями (например, координата X, координата Y, высота).Я пытаюсь переименовать Node_Ids для всех узлов в новый формат, но изо всех сил пытаюсь добавить уникальный номер для любых обнаруженных дубликатов.

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

array.include?(node)

duplicates = array.select{|element| array.count(element) > 1}

Я нахожусь в точке, где код, кажется, распознает дубликат и добавляет «_1» к дублирующему узлу, однако, если есть еще один дубликат, он также добавляет «_1»...

$array = Array.new    # this is a temporary array I have been using to store the written Node_IDs, to check against for duplicates.
xy_nodes = ('_nodes').each do |xy|
    x1 = xy.x.to_i
    y1 = xy.y.to_i
    x = x1.to_s.rjust(7, "0")
    y = y1.to_s.rjust(7, "0")
    node = x+y
        # the above was just getting it to the correct format, no issues there
    if $array.include?(node)
        i=0
        node = node + "_#{i+1}"    # this is where things need sorted, clearly as it stands this would only result in _1 being added for the duplicates, however I'm struggling to iterate and check against duplicates after the first to continue adding _1, _2, _3 as suffixes.
        $array << node
        $checkmsg << node    # this array is used later
        xy.user_text = node    # used for writing to as this field can accept duplicates
        xy.write
    else
        puts "...iteration found no duplicates"
        $array << node
        xy.user_text = node
        xy.write        
    end
    puts "************ END OF ITERATION **************"
    puts ""
end

Это приводит к чему-то похожему ниже (в поле "user_text"):

05555550333333
04444440222222
05555550333333_1
05555550333333_1

Просто интересно, как лучше получить код для оценки нового дубликата исосчитайте вверх для каждого найденного и получите:

05555550333333
04444440222222
05555550333333_1
05555550333333_2

Спасибо.

ОБНОВЛЕНИЕ: В ответ на ответы

У меня возникли проблемы с сжатием вИнформация к комментариям, поэтому я думал, что напишу здесь.Прежде всего, спасибо за ответ.Кажется, что все три метода работают хорошо, чтобы $ массив выглядел так, как я надеюсь, для готовой статьи Node_ID (или user_text во время тестирования).

Например, используя эти методы, я могуполучить вывод массива:

02511160678961
02735510688965
02966900697649
03216480682699
02735510688965_1
02735510688965_2
02735510688965_3
03355411149097

Тем не менее, массив $ в моем коде изначально использовался как нечто, в которое я бросал «узел», чтобы проверить, использовался ли он ранее.Вместо того, чтобы быть массивом, я хотел сам отформатировать / записать.

Каждый узел - это объект в таблице ('_nodes').И я надеюсь, что итеративно напишу новый user_text (Node_ID), проверяющий дубликаты каждой итерации, а не в конце завершенного массива.

Моя цель состояла в том, чтобы обновить user_text каждого объекта (в конце концов Node_ID когда-то был решен)и добавьте его в массив $ для проверки дубликатов, когда будет записан текст user_text следующего объекта.Не уверен, есть ли очевидный способ применить предложения к каждому объекту перед записью, а не к законченному массиву, как я справился с вашей помощью.

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

Ответы [ 3 ]

1 голос
/ 17 апреля 2019

Различные повороты на одну и ту же концепцию, опубликованные @ iGian

names = ['a', 'b', 'c', 'a', 'a', 'c', 'b', 'e']

names.group_by(&:itself).flat_map do |k,v| 
  v.size.times.map {|n| n.zero? ? k : "#{k}_#{n}"}
end

Порядок сортировки будет меняться в зависимости от наличия уникальных элементов в списке

ИЛИ

names.sort.chunk_while {|a,b| a == b }.flat_map do |a| 
  a.map.with_index {|b,idx| idx.zero? ? b : "#{b}_#{idx}"}
end

Порядок сортировки будет меняться в зависимости от естественной сортировки списка элементов.

0 голосов
/ 17 апреля 2019

Этот подход делает один проход через массив и поддерживает порядок.

names = ['21_1', '34', '49_2', '21_1', '21_1', '49_2', '14_2']

nxt_hash = {}
names.map do |name|
  next name unless name.include?('_')
  prefix, _, suffix = name.partition('_')
  nxt = nxt_hash[name] || suffix.to_i
  nxt_hash[name] = nxt + 1
  "%s_%d" % [prefix, nxt]
end
  #=> ["21_1", "34", "49_2", "21_2", "21_3", "49_3", "14_2"]

Когда закончите,

nxt_hash
  #=> {"21_1"=>4, "49_2"=>4, "14_2"=>3}

Этот подход будет слегка изменен, если вы обрабатываете строки по одному, как если бы вы читали из файла или базы данных, или они возвращались методом по отдельности.

Давайте определим перечислитель, который будет генерировать имена по одному.

enum = names.to_enum
  #=> #<Enumerator: ["21_1", "34", "49_2", "21_1", "21_1", "49_2", "14_2"]:each>

Тогда

enum.next  #=> "21_1" 
enum.next  #=> "34" 
enum.next  #=> "49_2" 
enum.next  #=> "21_1" 
enum.next  #=> "21_1" 
enum.next  #=> "49_2" 
enum.next  #=> "14_2" 
enum.next  #=> StopIteration (iteration reached an end)

и так далее. Теперь мы можем построить нужный массив (arr) следующим образом.

enum = names.to_enum
arr = []
nxt_hash = {}
loop do
  name = enum.next
  unless name.include?('_')
    arr << name
    next
  end
  prefix, _, suffix = name.partition('_')
  nxt = nxt_hash[name] || suffix.to_i
  nxt_hash[name] = nxt + 1
  arr << "%s_%d" % [prefix, nxt]
end
arr
  #=> ["21_1", "34", "49_2", "21_2", "21_3", "49_3", "14_2"] 

Здесь enum.next (см. Enumerator # next ) возвращает исключение StopIteration, когда у него больше нет элементов для генерации. Kernel # loop обрабатывает это выражение, прерывая цикл. Если каждый name предоставляется методом или образует строку, которая читается из файла или базы данных, у вас будут другие способы выхода из цикла, но мое использование перечислителя имитирует все способы генерации name s. по одному.

Этот подход может быть изменен для решения более общей проблемы.

names = ['a', 'b', 'c', 'a', 'a', 'c', 'b', 'e']

nxt_hash = {}
names.map do |name|
  nxt = nxt_hash[name]
  nxt_hash[name] = nxt.to_i + 1
  nxt.nil? ? name : "%s_%s" % [name, nxt]
end
  #=> ["a", "b", "c", "a_1", "a_2", "c_1", "b_1", "e"]  

Когда закончите,

nxt_hash
  #=> {"a"=>3, "b"=>2, "c"=>2, "e"=>1}

Примечание: nil.to_i #=> 0.

0 голосов
/ 17 апреля 2019

Это просто идея, может быть, может помочь.

Допустим, это ваш массив names:

names = ['a', 'b', 'c', 'a', 'a', 'c', 'b', 'e']

Это один из вариантов переименования элементов:

tmp = names.each_with_object(Hash.new(0)) { |e, h| h[e] += 1 }
#=> {"a"=>3, "b"=>2, "c"=>2, "e"=>1}

Таким образом, в основном он считает элемент и генерирует хэш выше.Затем сопоставьте хеш для перестройки массива с добавлением варианта:

tmp.each_with_object([]) { |(k, v), a| a << ([k]*v).map.with_index { |e, i| e + "_#{i}" }  }
#=> [["a_0", "a_1", "a_2"], ["b_0", "b_1"], ["c_0", "c_1"], ["e_0"]]

Конечно, вы можете пропустить добавление индекса, если v == 1:

{ |e, i| v == 1 ? e : e + "_#{i}" }

Используемые методы описаны здесь: Enumerable , Hash , Array


Вторая часть может быть изменена на:
tmp.flat_map { |k, v| v.times.map { |i| v == 1 ? k : k + "_#{i}" } }
#=> ["a_0", "a_1", "a_2", "b_0", "b_1", "c_0", "c_1", "e"]

И один вкладыш, простодля удовольствия:

names.each_with_object(Hash.new(0)) { |e, h| h[e] += 1 }.flat_map { |k, v| v.times.map { |i| v == 1 ? k : k + "_#{i}" } }
...