获取数组Ruby中最长的前缀

时间:2018-05-20 09:35:50

标签: arrays ruby

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

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

我期待这样的输出:

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

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

974555899-55
974555899-20
974555899-21

对于第二个子阵列,最长的前缀是长度为5的"23480",并且短于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],其四个数字之间没有足够长的前缀,但在249125087221m[0][2]之间有一个长度为12的前缀m[0][3]。对于array m[2]"659024796"m[2][0]之间的长度为9的前缀m[2][1]"65985400"之间还有另一个长度为8的前缀m[2][2]m[2][3]

我构建了以下代码:

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

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

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

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

3 个答案:

答案 0 :(得分: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的分组标准)由下划线表示(a合法的局部变量)表示它不用于块计算。

答案 1 :(得分:1)

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

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"]

答案 2 :(得分:0)

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

最大公共号对数组进行操作,因此它应该是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查找和过滤输出。