【问题标题】:Get the longest prefix in arrays Ruby获取数组Ruby中最长的前缀
【发布时间】:2018-05-20 09:35:50
【问题描述】:

我有一个数组数组。在每个子数组中,如果两个或多个元素共享一个长度等于或大于八的前缀,那么我想用它们的最长前缀替换这些元素。对于这个数组:

m = [
  ["A", "97455589955", "97455589920", "97455589921"],
  ["B", "2348045101518", "2348090001559"]
]

我希望得到这样的输出:

n = [
  ["A", "974555899"],
  ["B", "2348045101518", "2348090001559"]
]

对于m 中的第一个子数组,最长前缀是长度为9 的"974555899"

974555899-55
974555899-20
974555899-21

对于第二个子数组,最长的前缀是"23480",长度为 5,小于 8。在这种情况下,第二个子数组保持原样。

23480-45101518
23480-90001559

对于这个输入:

m = [
  ["A", "2491250873330", "249111222333", "2491250872214", "2491250872213"],
  ["B", "221709900000"],
  ["C", "6590247968", "6590247969", "6598540040", "65985400217"]
]

输出应该是这样的:

[
  ["A", "2491250873330", "249111222333", "249125087221"],
  ["B", "221709900000"],
  ["C", "659024796", "65985400"]
]

对于array m[0],它的四个数字之间没有足够长的前缀,但在m[0][2]m[0][3] 之间有一个长度为12 的前缀249125087221。对于array m[2],在m[2][0]m[2][1] 之间有一个长度为9 的前缀"659024796",在m[2][2]m[2][3] 之间还有一个长度为8 的前缀"65985400"

我构造了下面的代码:

m.map{|x, *y|
  [x, y.map{|z| z[0..7]}.uniq].flatten
}

使用带有第一个输入的代码,我得到了这个输出。

[
  ["A", "97455589"],
  ["B", "23480451", "23480900"]
]

我不知道如何在不设置固定长度的情况下动态获取公共前缀。

【问题讨论】:

  • 其实LCM代表Least common multiple。最大公数是什么意思?数字应该以给定的数字开头还是只包含它?
  • 我发明了首字母缩写词 LCN jeje。我的意思是第一个数字相等但大小最大的数字。在 12345 和 1234567 之间,最大的常见数字是 12345,长度 = 5。
  • 你想要的输出的最后一个元素不应该是["C","659024796","6598540040", ,"65985400217"]]吗?我想你可能数错了 8 而不是 9。
  • 嗨,卡里。不。我已经更新了我的帖子UPDATE 2,以解释第三个子数组的输出。
  • 我认为标准是> 8 而不是>= 8。我会修正我的答案。

标签: arrays ruby


【解决方案1】:

代码

def doit(arr, min_common_length)
  arr.map do |label, *values|
    [label, values.group_by { |s| s[0, min_common_length] }.
      map { |_,a| a.first[0, nbr_common_digits(a, min_common_length)] }]
  end
end

def nbr_common_digits(a, min_common_length)
  max_digits = a.map(&:size).min
  return max_digits if max_digits == min_common_length + 1
  (min_common_length..max_digits).find { |i|
    a.map { |s| s[i] }.uniq.size > 1 } || max_digits
end

示例

arr = [["A","2491250873330","249111222333","2491250872214","2491250872213"],
       ["B","221709900000"],
       ["C","6590247968","6590247969","6598540040","65985400217"]]

doit(arr, 8)
  #=> [["A", ["249125087", "249111222333"]],
  #    ["B", ["221709900000"]],
  #    ["C", ["659024796", "65985400"]]]

说明

让我们首先考虑辅助方法nbr_common_digits。假设

a = ["123467", "12345", "1234789"]
min_common_length = 2

那么步骤如下。

max_digits = a.map(&:size).min
  #=> 5 (the length of "12345")
max_digits == min_common_length + 1
  #=> 5 == 2 + 1
  #=> false, so do not return max_digits
b = (min_common_length..max_digits).find { |i| a.map { |s| s[i] }.uniq.size > 1 }
  #=> (2..5).find { |i| a.map { |s| s[i] }.uniq.size > 1 }
  #=> 4

此时我们必须考虑b 将等于nil 的可能性,这种可能性发生在所有字符串的第一个5 字符相等时。在这种情况下,我们应该返回max_digits,这就是我们需要以下内容的原因。

b || max_digits
  #=> 4

doit中的步骤如下。

min_common_length = 8

首先,我们使用Enumerable#group_by 将值按它们的第一个min_common_length 数字分组。

arr.map { |label, *values| [label,
  values.group_by { |s| s[0, min_common_length] }] }
  #=> [["A", {"24912508"=>["2491250873330", "2491250872214", "2491250872213"],
  #           "24911122"=>["249111222333"]}],
  #    ["B", {"22170990"=>["221709900000"]}],
  #    ["C", {"65902479"=>["6590247968", "6590247969"],
  #           "65985400"=>["6598540040", "65985400217"]}]]

第二步是计算最长的公共长度并根据需要替换值。

arr.map do |label, *values| [label,
  values.group_by { |s| s[0, min_common_length] }.
         map { |_,a| a.first[0, nbr_common_digits(a, min_common_length)] }]
end
  #=> [["A", ["249125087", "249111222333"]],
  #    ["B", ["221709900000"]],
  #    ["C", ["659024796", "65985400"]]]

第二个map的块中的第一个块变量(其值等于带有nbr_common_length字符的字符串--group_by的分组标准)用下划线(合法的局部变量)表示在块计算中不使用它。

【讨论】:

  • 嗨,卡里。谢谢您的帮助。我在运行您的解决方案时遇到错误。 NoMethodError: undefined method nbr_common_digits for main:Object。但是看到您的解决方案,输出对于带有标签 A 和 B 的数组是正确的,但对于带有标签 C 的数组却不是,因为对于第三个数组,只有 2 个 LCN,而不是像 UPDATE 2 的示例中的 3 个。对于第 3 个子数组,输出应为 ["C","659024796","65985400"]
  • 我发现我无意中删除了一个辅助方法 (nbr_common digits)。我会在一个小时左右把它放回去并提供解释。
  • 我把助手放回去,很快就会开始写解释。如果您在 IRB 中运行它,请先运行辅助方法。
  • 谢谢卡里。我现在可以运行您的解决方案,但即使输出与我要查找的内容相近,第三个子数组仍然不同。
  • 如果以前像魅力一样工作,现在我可以说它真的很棒!非常、非常、非常感谢 Cary 在这种情况下提供了如此大的帮助。帮助我在 MS Excel 中手动完成这项任务时节省了大量时间,并节省了大量时间来思考如何解决它。我不能给你超过一票,否则我会这样做。谢谢sss
【解决方案2】:

这是一个有趣的问题。这是我的解决方案:

def lcn(lim, *arr)
  # compute all substrings of lengths >= lim and build a lookup by length
  lookup = lcn_explode(lim, arr)

  # first pass: look for largest common number among all elements
  res, = lcn_filter(arr, lookup) { |size| size == arr.size }

  return res unless res.empty?

  # second pass: look for largest common number among some elements
  res, rem = lcn_filter(arr, lookup) { |size| size > 1 }

  # append remaining candidates with no matches
  res.concat(rem)
end

def lcn_explode(lim, arr)
  memo = Hash.new { |h, k| h[k] = Array.new }

  arr.uniq.each do |n|
    lim.upto([n.size, lim].max) do |i|
      memo[i] << [n[0, i], n]
    end
  end

  memo
end

def lcn_filter(arr, lookup)
  memo = []

  lookup.keys.sort!.reverse_each do |i|
    break if arr.empty?

    matches = Hash.new { |h, k| h[k] = Array.new }
    lookup[i].each do |m, n|
      matches[m] << n if arr.include?(n)
    end

    matches.each_pair do |m, v|
      next unless yield v.size

      memo << m

      # remove elements from input array so they won't be reused
      arr -= v
    end
  end

  return memo, arr
end

你这样使用它:

p lcn(8, "97455589955", "97455589920", "97455589921") => ["974555899"]

或者:

m.each do |key, *arr|
  p [key, *lcn(8, *arr)]
end

哪些打印:

["A", "249125087221", "2491250873330", "249111222333"]
["B", "221709900000"]
["C", "659024796", "65985400"]

【讨论】:

  • 感谢 mwp 的帮助和努力,但我没有得到运行您的解决方案的输出。你得到正确的输出了吗?
  • @GerCas 啊,我的示例中有一个关于如何使用它的错误。现在修好了。对此感到抱歉。
  • 您好 mwp,感谢您的帮助!我再次尝试了您的解决方案,但我不知道为什么我仍然得到相同的输入数组作为输出。我看到你的输出是正确的。
  • @GerCas 抱歉,我的解决方案很难实施。我将所有内容复制到 repl.it 以验证一切是否正常。这是我的 repl.it 链接:repl.it/repls/FrenchCompetitiveBots 只需点击“运行”即可查看结果。
  • 谢谢 mwp,我认为我的问题是安装在我可以访问的机器上的 Ruby 版本。您测试的站点的 Ruby 版本是 2.5.0,我的是 2.3.3。我肯定会寻找一种方法来获得早期版本并再次尝试您的解决方案,但使用示例它也可以完美运行。非常感谢您的帮助和时间。从我们看到这个论坛的每个解决方案中,学习一些新的概念和思考问题解决方案的方式,不同的方法,不同的解决方案具有相同的结果。太好了!
【解决方案3】:

您的任务可以分为两个:计算最大公数和修改原始数组。

最大公数对数组进行操作,所以应该是Array的方法。

计算 LCN 后,您可以将其长度与限制(即 8)进行比较。

class Array
  def lcn
    first.length.times do |index|
      numb = first[0..index]
      return numb unless self[1..-1].all? { |n| n.start_with?(numb) }
    end

    first
  end
end

def task(m, limit = 8)
  m.map { |i,*n| [i, n.lcn.length >= limit ? n.lcn : n].flatten }
end

task(m) # => [["A", "9745558995"], ["B", "2348045101518", "2348090001559"]]

在您的解决方案中,您实际上并没有实现 lcn 查找和过滤输出。

【讨论】:

  • 谢谢 yzalavin,我想我还不够清楚。我尝试了您的解决方案,但我更新了我的原始帖子以向您展示另一个示例。我希望你能清楚地理解。我认为更复杂。
猜你喜欢
  • 2012-02-01
  • 2014-10-13
  • 2012-07-31
  • 2017-01-23
  • 2018-12-02
  • 1970-01-01
  • 2020-12-12
  • 2011-04-27
  • 2021-09-11
相关资源
最近更新 更多