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

Я использую Ruby 2.4. Пусть говорят, что у меня есть массив строк (все это просто string-i-fied (это слово?) Целые числа...

["1", "2", "5", "25", "5"]

Как написать функцию, которая сообщает мне, если все элементы в массиве встречаются не более двух раз в массиве? Например, этот массив

["1", "3", "3", "55", "3", "2"]

вернет false, потому что "3" происходит три раза, но этот массив

["20", "10", "20", "10"]

вернет true, потому что ни один из элементов не встречается более двух раз.

Ответ 1

С моей точки зрения, это может быть довольно простое решение:

def no_more_than_twice_occur?(array)
  array.none? { |el| array.count(el) > 2 }
end


no_more_than_twice_occur?(["1", "3", "3", "55", "3", "2"]) # => false
no_more_than_twice_occur?(["20", "10", "20", "10"]) # => true

Ответ 2

Вы можете определить частоту следующим образом:

frequency = array.reduce(Hash.new(0)) do |counts, value|
  counts[value] += 1
  counts
end
# => { "1" => 1, "3" => 3, "55" => 1, "2" => 1 }

И вы можете проверить, происходит ли какое-либо из них более двух раз:

frequency.values.max > 2

Если вы хотите красиво обернуть его, вы можете добавить его в Enumerable:

module Enumerable
  def frequency
    f = Hash.new(0)
    each { |v| f[v] += 1 }
    f
  end
end

И тогда ваше условие так же просто, как:

array.frequency.values.max > 2

Примечание: это происходит как часть Facets.

Ответ 3

Перечислимый # group_by сделает тяжелый подъем для этого:

def no_element_present_more_than_twice?(a)   
  a.group_by(&:itself).none? do |_key, values|
    values.count > 2
  end
end

p no_element_present_more_than_twice?(["1", "3", "3", "55", "3", "2"])
# => false
p no_element_present_more_than_twice?(["20", "10", "20", "10"])

Ответ 4

Попробуйте это

count = Hash.new(0)
array.none? { |each| (count[each] += 1) > 2 }
# => true or false

Как это работает?

  • Hash.new(0) создает хэш со значением по умолчанию 0
  • none? проверяет блок для всех элементов и возвращает ли элемент не соответствует
  • count[each] += 1 увеличивает счетчик (нет nil, поскольку значение по умолчанию 0)

Это оптимальное решение, так как оно ломается, как только будет найден первый оскорбительный элемент. Все остальные решения, размещенные здесь, либо сканируют весь массив, либо имеют еще худшую сложность.

NB, если вы хотите узнать, какие элементы отображаются более двух раз (например, для печати сообщения об ошибке) используйте find или find_all вместо none?.

Ответ 5

Я взял на себя все, чтобы сравнить все ваши варианты для вас:)

Running each test 1024 times. Test will take about 34 seconds.
_akuhn is faster than _vlasiak by 16x ± 1.0
_vlasiak is faster than _wayne by 3.5x ± 0.1
_wayne is faster than _cary by 10.0% ± 1.0%
_cary is faster than _oneneptune by 10.09% ± 1.0%
_oneneptune is similar to _coreyward
_coreyward is faster than _tadman by 10.0% ± 1.0%
_tadman is faster than _sagarpandya82 by 10.0% ± 1.0%
_sagarpandya82 is faster than _glykyo by 80.0% ± 1.0%

Как вы можете видеть, ответ @akuhn работает намного лучше, чем другие алгоритмы, потому что он выходит раньше, как только совпадение найдено.

Примечание.. Я отредактировал ответы для получения того же результата, но не изменил их для оптимизации.

Вот script, который подготовил тесты:

require 'fruity'

arr = Array.new(1000) { |seed|
  # seed is used to create the same array on each script run,
  # hence the same benchmark results will be produced
  Random.new(seed).rand(1..10).to_s
}

class Array
  def difference(other)
    h = other.each_with_object(Hash.new(0)) { |e,h| h[e] += 1 }
    reject { |e| h[e] > 0 && h[e] -= 1 }
  end
end

compare do
  _coreyward do
    arr.reduce(Hash.new(0)) { |counts, value|
      counts[value] += 1
      counts
    }.max[1] <= 2
  end

  _wayne do
    arr.group_by(&:itself).none? do |_key, values|
      values.count > 2
    end
  end

  _sagarpandya82 do
    arr.sort_by(&:to_i).each_cons(3).none? { |a,b,c| a == b && b == c }
  end

  _tadman do
    arr.sort.slice_when { |a,b| a != b }.map(&:length).max.to_i <= 2
  end

  _cary do
    arr.difference(arr.uniq*2).empty?
  end

  _akuhn do
    count = Hash.new(0)
    arr.none? { |each| (count[each] += 1) > 2 }
  end

  _oneneptune do
    arr.each_with_object(Hash.new(0)) { |element,counts|
      counts[element] += 1
    }.values.max < 3
  end

  _glykyo do
    arr.uniq.map{ |element| arr.count(element) }.max <= 2
  end

  _vlasiak do
    arr.none? { |el| arr.count(el) > 2 }
  end

end

Ответ 6

Здесь другой способ, используя метод Array#difference:

def twice_at_most?(arr)
  arr.difference(arr.uniq*2).empty?
end

где Array#difference определяется следующим образом:

class Array
  def difference(other)
    h = other.each_with_object(Hash.new(0)) { |e,h| h[e] += 1 }
    reject { |e| h[e] > 0 && h[e] -= 1 }
  end
end

После того, как я нашел много применений для Array#difference, я предложил, чтобы он был принят в качестве основного метода. В документе по этой ссылке объясняется, как работает этот метод, и приводятся примеры его использования.

Попробуйте.

twice_at_most? [1, 4, 2, 4, 1, 3, 4]
  #=> false

Здесь

arr.uniq*2
  #=> [1, 4, 2, 3, 1, 4, 2, 3] 
arr.difference(arr.uniq*2)
  #=> [4] 

Другой пример:

twice_at_most? [1, 4, 2, 4, 1, 3, 5]
  #=> true

Ответ 7

Здесь все в одном методе для вас.

def lessThanThree(arr)
  arr.each_with_object(Hash.new(0)) { |element,counts| counts[element] += 1 }.values.max < 3
end

В основном, принимает массив, выполняет итерацию, создавая хэш и подсчитывая каждое вхождение, тогда метод значений просто создает массив всех значений (значений), а затем max находит наибольшее значение. Мы проверяем, если это меньше трех, если это так, верните true, иначе верните false. Вы можете заменить true или false блоком кода.

Ответ 8

Чтобы избежать много временных накладных расходов, просто sort массива, а затем разделите его на куски похожих элементов. Затем вы можете найти самый длинный фрагмент:

def max_count(arr)
  arr.sort.slice_when { |a,b| a != b }.map(&:length).max.to_i
end

max_count(%w[ 1 3 3 55 3 2 ])
# => 3

max_count(%w[ 1 3 55 3 2 ])
# => 2

max_count([ ])
# => 0

Ответ 9

Просто для удовольствия здесь одним способом, используя each_cons и используя none?, как использовал Уэйн Конрад в своем ответе.

 arr.sort_by(&:to_i).each_cons(3).none? { |a,b,c| a == b && b == c }

Ответ 10

Для каждого уникального элемента массива подсчитывайте, сколько раз этот элемент появляется в массиве. Из этих значений проверьте, является ли max равным <= 2.

def max_occurence_at_most_2?(array)
  array.uniq.map{ |element| array.count(element) }.max <= 2
end

Не оптимизирован для скорости.