Объединение внутренних массивов индексов, если они содержат одинаковое содержимое

#ruby

#ruby

Вопрос:

Я создаю массив сгруппированных индексов. Индексы — это точки внутри массива, которые соответствуют моим требованиям к группировке. Например, я группирую индексы из сетки, где объекты расположены горизонтально «близко» друг к другу. Это то, с чем я буду работать.

 [[0,1,2],[3],[4,5],[5,6],[7,8],[8,9]]
  

Я хотел бы объединить по общим индексам. Таким образом, результат должен выглядеть следующим образом.

 [[0,1,2],[3],[4,5,6],[7,8,9]]
  

Похоже, что это должно быть inject : для пар, если какие-либо внутренние элементы совпадают. Но я не вижу способа Ruby сделать это.

Комментарии:

1. Работает ли это транзитивно? Если есть [1, 2] , [2, 3] , [3, 4] , то что становится [1, 2, 3, 4] ?

2. Неясно, что означает «точки внутри массива, которые соответствуют моим требованиям к группировке».

3. Нет. Мне нужно, чтобы индексы были разделены для группировок между сопоставлением индексов по горизонтали (элементы, которые близки друг к другу)… негруппированные остаются для последующего сопоставления по вертикали.

4. » вывод , с которым я должен работать» — это не вход , насколько это касается этой проблемы?

5. Насколько это возможно, да. Я обновил вопрос.

Ответ №1:

 x.sort.inject([]) do |y, new|
  (((y.last || []) amp; new).length > 0) ? y[0..-2].push(y.last | new) : y.push(new)
end.map(amp;:sort)
  

Комментарии:

1. Это работает отлично, пока мой порядок является последовательным. Добавление .shuffle в массив нарушает это. Это хороший ответ.

2. Это правильный ответ. x.sort.inject([]) { |y, new| ((y.last || []) amp; new).length > 0 ? y[0..-2].push(y.last | new) : y.push(new) }.map{|i|i.sort} Пожалуйста, обновите свой код, чтобы он соответствовал этому, и я приму ваш ответ как правильный.

3. Как вы пожелаете! Я рад, что мы нашли функциональное решение. Возможно, вы захотите изменить свой пример, чтобы сделать требования к сортировке понятными. Например, вы можете захотеть начать с x.map(amp;:sort).sort вместо просто x.sort .

Ответ №2:

Зная Ruby, вероятно, есть более краткий способ сделать это, но это должно дать вам то, что вы хотите:

 foo = [[0,1,2],[3],[4,5],[5,6],[7,8],[8,9]]

foo.inject([]) {|result,element|
  if (result and existing = result.find_index{|a| !(element amp; [*a]).empty?})
    tmp = result[existing]
    result.delete_at(existing)
    result << (tmp | element).sort
  else
    result << element
  end
}.sort
  

Вывод:

 => [[0, 1, 2], [3], [4, 5, 6], [7, 8, 9]]
  

Логические:

Для каждого из них element в исходном массиве проверьте недавно созданный array-so-far ( result ) на наличие любой записи, которая содержит любое из тех же чисел, что и следующий элемент, используя array intersection — !(element amp; [*a]).empty?

  • если найдено, удалите указанную запись из result , объедините ее с новой element из исходного массива — (tmp | element) — затем добавьте ее обратно в result
  • если не найдено, просто объедините element из исходного массива в result

Комментарии:

1. Примечание: Небольшое редактирование, внесенное в исходный ответ, в частности, find_index логика теперь обрабатывает ввод не по порядку, например [[0,1,2],[3],[5,6],[7,8],[4,5],[8,9]] .

2. ДА. Это работает хорошо. Мне нужно будет добавить сортировку во внутренние массивы, поскольку порядок важен. Я протестировал это, включив shuffle в foo перед вводом foo.shuffle.inject , и он действительно присоединяется к правильным.

3. Разве присвоение в операторе if не считается запретным? (Поскольку это чаще всего неправильно читается.)

4. Хороший вызов re: sort — я обновил ответ, чтобы включить и это, чтобы избежать потери его в комментариях. Что касается назначения в операторе if, я не уверен — это чрезвычайно полезный шаблон, который я видел, чтобы проверять существование вещи и одним махом преобразовывать ее в переменную, а не tmp = result.find_index(...); if tmp ...

Ответ №3:

Кто-то может найти более компактный метод, но это работает…

 array = [[0,1,2],[3],[4,5],[5,6],[7,8],[8,9]]
(0...array.length).each do |a|
  (a 1...array.length).each do |b|
    unless array[a].to_a  amp; array[b].to_a == []
      array[a].push(array[b]).flatten!.uniq!.sort!
      array.delete_at(b)
      b -= 1
    end
  end
end

p array
=> [[0, 1, 2], [3], [4, 5, 6], [7, 8, 9]]
  

Комментарии:

1. Это работает даже при перетасованном состоянии массива. Хорошая работа. Внутренние массивы не отсортированы.

Ответ №4:

Решения пока кажутся мне чрезмерно сложными. Я предлагаю это (предполагая, что каждый элемент arr непустой и содержит только целые числа):

 arr = [[0, 1, 2], [3],
       [4, 5], [5, 6],
       [7, 8, 9], [9, 10], [10, 11],
       [12, 13], [13], [13, 14]]

arr.each_with_object([]) do |a,b|
  if b.any? amp;amp; b.last.last == a.first
    b[-1]  = a[1..-1]
  else
    b << a
  end
end
  #=> [[0, 1, 2], [3], [4, 5, 6], [7, 8, 9, 10, 11], [12, 13, 14]]
  

В качестве альтернативы вы могли бы сделать это, выполнив переход arr с помощью перечислителя:

 enum = arr.each
b = [enum.next]
loop do
  a = enum.next
  if b.last.last == a.first
    b[-1]  = a[1..-1]
  else
    b << a
  end
end
b