【问题标题】:How do I sort an integer array while also keeping identical elements apart from each other?如何对整数数组进行排序,同时保持相同的元素彼此分开?
【发布时间】:2015-01-24 14:49:59
【问题描述】:

我不确定这种类型是否有名称,所以我很难在其他地方找到任何相关的答案。

我希望元素尽可能地旋转。

给定这个数组:

[38, 38, 40, 40, 40, 41, 41, 41, 41, 60]

我如何把它分类成这个?

[38, 40, 41, 60, 38, 40, 41, 40, 41, 41]

我研究了其他线程并没有找到答案。

提前致谢!

【问题讨论】:

  • 试试这个 [1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4].uniq.sort*4
  • 您的问题尚未完全明确。例如,你会如何排序:[1,2,3,3,3,3,3,3,3]?没有办法不让多个 3 并排放置而仍然保留所有原始元素。还是您只是对数字进行排序?
  • 我正在尝试尽可能均匀地旋转它们。这是一个(希望)更清晰的示例:鉴于此:[38,38,40,40,40,41,41,41,41,60],我想要这个:[38,40,41,60,38,40, 41、40、41、41]
  • 我已更新问题以使其更清晰。

标签: ruby arrays sorting


【解决方案1】:

这本身并不是真正的“排序”。有一个排序步骤,但最终你真正想要做的是称为matrix transposition

下面有两种实现方式。

更详细的

第一种方法更长,并且为了清晰起见(希望如此)分为更多步骤。

首先,我们将对相关的数组元素进行排序和分组:

v = arr.sort.group_by { |e| e }.values
# => [[38, 38], [40, 40, 40], [41, 41, 41, 41], [60]]

让我们为结果创建一个新数组:

r = []

现在我们将得到元素数量最多的那个:

max = arr.map { |e| arr.count(e) }.max

然后我们将遍历数组多次,

max.times { ... }

每次从每个子数组中取出一个元素,然后将其放入结果数组中:

max.times { v.each { |a| r << a.shift } }; r.compact!

我们有我们的答案:

# => [38, 40, 41, 60, 38, 40, 41, 40, 41, 41]

更简洁

既然您已经看到了很长的路要走,这里有一个更简洁的方法。这不需要太多的迭代(或输出数组!)。

首先,我们将子数组填充到max的大小:

arr.sort.
  group_by { |e| e }.values.
  map { |a| a.fill(nil, a.size..max-1) }
# ...

现在这是一个大小为max 乘以max 元素的方阵,因此我们可以将其视为方阵。这意味着我们可以转置元素,使“行”变成“列”,反之亦然。

所以我们将transpose,然后flatten 得到一个数组,然后compact 去掉nil 元素:

# ...
  (...).transpose.flatten.compact

我们得到了想要的结果:

# => [38, 40, 41, 60, 38, 40, 41, 40, 41, 41]

【讨论】:

  • 感谢您为此付出了这么多努力!它真的帮助了我。我确实想提一下,在我掉到下一行并使用 r = r.compact() 之前,我仍然有 nils 使用详细的方式。我猜紧凑返回一个没有 nils 的副本。再次感谢!
  • @SteveArnold:哎呀,我忘了!;那应该是r.compact!
【解决方案2】:

这是解决此问题的另一种方法。看起来约翰已经给出了一个很好的答案,所以我不会冗长地解释这个。

要了解其工作原理,我建议放入一些 puts 语句以查看中间值。您可以使用watch ruby sorter_test.rb 运行它以不断获得反馈。 (见watch docs)。

这里使用的大部分方法都来自Enumerable module

require 'minitest/autorun'

class Sorter < Struct.new(:list)
  def call
    sorted_and_grouped = list.sort.group_by { |i| i }.values
    max_len = sorted_and_grouped.max_by(&:count).count
    padded = sorted_and_grouped.map { |xs| pad(xs, max_len) }
    padded.reduce(&:zip).flatten.compact
  end

  private

  def pad(xs, len)
    xs.fill(nil, xs.count...len)
  end
end

ORIGINAL  = [38, 38, 40, 40, 40, 41, 41, 41, 41, 60].freeze
SORTED    = [38, 40, 41, 60, 38, 40, 41, 40, 41, 41].freeze
class TestSorter < Minitest::Test
  def test_sorts_as_expected
    actual = Sorter.new(ORIGINAL).call
    assert_equal SORTED, actual
  end
end

【讨论】:

  • 感谢您提供这个!
【解决方案3】:

这是另一种方式。这取决于您的数组被排序(增加或减少),就像在您的示例中一样。此外,如果没有这个假设,问题就无法很好地定义。

首先,我一直希望有一个帮手:

class Array
  def %(arr)
    arr.each_with_object(dup) do |e,a|
      i = a.index(e)  
      a.delete_at(i) if i
    end
  end
end

例如:

arr = [38, 38, 40, 40, 40, 41, 41, 41, 41, 60]
arr % [41, 60, 40, 38, 40, 41] 
  #=> [38, 40, 41, 41]

如果ab 是两个数组,a%b 类似于a-b,除了删除a 中包含在b 中的所有元素之外,它会删除@987654329 中的一个字符@(具有最小索引的那个)对于b 中该字符的每个实例。现在这不是一种方便的内置方法吗?

有了这个帮助器,就可以轻松地以所需的方式对数组进行排序:

  1. 复制数组a 并创建一个空数组result
  2. ua 的唯一元素)添加到result
  3. 使用帮助程序从a 中删除u
  4. 如果a为空,返回result;否则重复第 2 步。

代码如下:

def steves_sort(arr)
  a = arr.dup
  result = []
  while a.any?
    u = a.uniq
    result.concat(u)
    a = a % u
  end
  result
end

steves_sort([38, 38, 40, 40, 40, 41, 41, 41, 41, 60])
  #=> [38, 40, 41, 60, 38, 40, 41, 40, 41, 41] 

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-30
    • 1970-01-01
    • 2020-03-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多