我想知道Ruby中是否有一种优雅的方式来提出一些整数的所有排列(重复),其要求是1)引入的整数必须按从左到右的升序排列2)零是免除的从这条规则。
下面,我有三位数的输出子集和整数0,1,2,3,4,5,6,7,8,9。这只是总答案的一个子集,特别是它以5开头的子集。我已经在其中包含了几个注释
500 - Zero is used twice
505 - 5 is used twice. Note that 504 is not included because 5 was introduced on the left and 4 < 5
506
507
508
509
550
555
556
557
558
559
560
565 - Though 5 < 6, 5 can be used twice because 5 was introduced to the left of 6.
566
567
568
569
570
575
577
578
579
580
585
588
589
590
595
599
我需要能够以任意长的输出长度(不仅仅是3,如本例),并且我需要能够为特定的整数集做到这一点。但是,零将始终是排序规则不适用的整数。
答案 0 :(得分:1)
这样可行:
class Foo
include Comparable
attr :digits
def initialize(digits)
@digits = digits.dup
end
def increment(i)
if i == -1 # [9,9] => [1,0,0]
@digits.unshift 1
else
succ = @digits[i] + 1
if succ == 10 # [8,9] => [9,0]
@digits[i] = 0
increment(i-1)
else
@digits[i] = @digits[0,i].sort.detect { |e| e >= succ } || succ
end
end
self
end
def succ
Foo.new(@digits).increment(@digits.length-1)
end
def <=>(other)
@digits <=> other.digits
end
def to_s
digits.join
end
def inspect
to_s
end
end
range = Foo.new([5,0,0])..Foo.new([5,9,9])
range.to_a
#=> [500, 505, 506, 507, 508, 509, 550, 555, 556, 557, 558, 559, 560, 565, 566, 567, 568, 569, 570, 575, 577, 578, 579, 580, 585, 588, 589, 590, 595, 599]
增加数字的主要规则是:
@digits[i] = @digits[0,i].sort.detect { |e| e >= succ } || succ
这会将剩余的数字排序到当前数字(“引入左侧”),并检测第一个等于或大于后继数的元素。如果找不到,则使用后继本身。
答案 1 :(得分:1)
如果你需要它作为可执行文件:
#!/usr/bin/env ruby -w
def output(start, stop)
(start..stop).select do |num|
digits = num.to_s.split('').to_a
digits.map! { |d| d.to_i }
checks = []
while digit = digits.shift
next if digit == 0
next if checks.find { |d| break true if digit == d }
break false if checks.find { |d| break true if digit < d }
checks << digit
end != false
end
end
p output(*$*[0..1].map { |a| a.to_i })
$ ./test.rb 560 570
[560, 565, 566, 567, 568, 569, 570]
答案 2 :(得分:0)
注意:显示了三种解决方案;寻找分裂。
描述有效的数字,然后(1..INFINITE).select{|n| valid(n)}.take(1)
那么什么是有效的?好吧,让我们在这里取一些优势:
class Fixnum
def to_a
to_s.split('').collect{|d| d.to_i}
end
end
123.to_a == [1,2,3]
好吧,现在:每个数字可以是已经存在的数字或零,或者是大于先前值的数字,并且第一个数字始终有效。
PS - 我使用i
而不是i-1
,因为循环的索引比set
小1,因为我关闭了第一个元素。
def valid num
#Ignore zeros:
set = num.to_a.select{|d| d != 0 }
#First digit is always valid:
set[1..-1].each_with_index{ |d, i|
if d > set[i]
# puts "Increasing digit"
elsif set[0..i].include? d
# puts "Repeat digit"
else
# puts "Digit does not pass"
return false
end
}
return true
end
然后,为懒惰而欢呼:
(1..Float::INFINITY).lazy.select{|n| valid n}.take(100).force
#=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 22, 23, 24,
# 25, 26, 27, 28, 29, 30, 33, 34, 35, 36, 37, 38, 39, 40, 44, 45, 46, 47, 48, 49, 50, 55,
# 56, 57, 58, 59, 60, 66, 67, 68, 69, 70, 77, 78, 79, 80, 88, 89, 90, 99, 100, 101, 102,
# 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120,
# 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 133, 134, 135, 136]
现在我们拥有它,让它简洁明了:
def valid2 num
set = num.to_a.select{|d| d != 0 }
set[1..-1].each_with_index{ |d, i|
return false unless (d > set[i]) || (set[0..(i)].include? d)
}
return true
end
检查:
(1..Float::INFINITY).lazy.select{|n| valid n}.take(100).force - (1..Float::INFINITY).lazy.select{|n| valid2 n}.take(100).force
#=> []
现在一起:
def valid num
set = num.to_s.split('').collect{|d| d.to_i}.select{|d| d != 0 }
set[1..-1].each_with_index{ |d, i|
return false unless (d > set[i]) || (set[0..(i)].include? d)
}
return true
end
编辑: 如果您想要该集合的特定子集,只需更改范围即可。你原来的是:
(500..1000).select{|n| valid n}
Edit2:生成给定位数n
的范围:
((Array.new(n-1, 0).unshift(1).join('').to_i)..(Array.new(n, 0).unshift(1).join('').to_i))
Edit3:有趣的替代方法 - 在数字生效时递归删除。
def _rvalid set
return true if set.size < 2
return false if set[1] < set[0]
return _rvalid set.select{|d| d != set[0]}
end
def rvalid num
return _rvalid num.to_s.split('').collect{|d| d.to_i}.select{|d| d != 0 }
end
(1..Float::INFINITY).lazy.select{|n| rvalid n}.take(100).force
编辑4:正生成方法
def _rgen set, target
return set if set.size == target
((set.max..9).to_a + set.uniq).collect{ |d|
_rgen((set + [d]), target)
}
end
def rgen target
sets = (0..9).collect{|d|
_rgen [d], target
}
# This method has an array problem that I'm not going to figure out right now
while sets.first.is_a? Array
sets = sets.flatten
end
sets.each_slice(target).to_a.collect{|set| set.join('').to_i}
end
答案 3 :(得分:0)
这是一些C#/伪代码。它绝对不会编译。实现不是线性的,但我注意到您可以在哪里添加一个简单的优化,以提高效率。算法非常简单,但它看起来非常合理(它相对于输出是线性的。我猜测输出呈指数增长...所以这个算法也是指数级的。但是有一个紧密的常数)。
// Note: I've never used BigInteger before. I don't even know what the
// APIs are. Basically you can use strings but hopefully the arbitrary
// precision arithmetic class/struct would be more efficient. You
// mentioned that you intend to add more than just 10 digits. In
// that case you pretty much have to use a string without rolling out
// your own special class. Perhaps C# has an arbitrary precision arithmetic
// which handles arbitrary base as well?
// Note: We assume that possibleDigits is sorted in increasing order. But you
// could easily sort. Also we assume that it doesn't contain 0. Again easy fix.
public List<BigInteger> GenSequences(int numDigits, List<int> possibleDigits)
{
// We have special cases to get rid of things like 000050000...
// hard to explain, but should be obvious if you look at it
// carefully
if (numDigits <= 0)
{
return new List<BigInteger>();
}
// Starts with all of the valid 1 digit (except 0)
var sequences = new Queue<BigInteger>(possibleDigits);
// Special case if numDigits == 1
if (numDigits == 1)
{
sequences.Enqueue(new BigInteger(0));
return sequences;
}
// Now the general case. We have all valid sequences of length 1
// (except 0 because no valid sequence of length greater than 1
// will start with 0)
for (int length = 1; length <= numDigits; length++)
{
// Naming is a bit weird. A 'sequence' is just a BigInteger
var sequence = sequences.Dequeue();
while (sequence.Length == length)
{
// 0 always works
var temp = sequence * 10;
sequences.Enqueue(temp);
// Now do all of the other possible last digits
var largestDigitIndex = FindLargestDigitIndex(sequence, possibleDigits);
for (int lastDigitIndex = largestDigitIndex;
lastDigitIndex < possibleDigits.Length;
lastDigitIndex++)
{
temp = sequence * 10 + possibleDigits[lastDigitIndex];
sequences.Enqueue(temp);
}
sequence = sequences.Dequeue();
}
}
}
// TODO: This is the slow part of the algorithm. Instead, keep track of
// the max digit of a given sequence Meaning 5705 => 7. Keep a 1-to-1
// mapping from sequences to largestDigitsInSequences. That's linear
// overhead in memory and reduces time complexity to linear _with respect to the
// output_. So if the output is like `O(k^n)` where `k` is the number of possible
// digits and `n` is the number of digits in the output sequences, then it's
// exponential
private int FindLargestDigitIndex(BigInteger number,
List<int> possibleDigits)
{
// Just iterate over the digits of number and find the maximum
// digit. Then return the index of that digit in the
// possibleDigits list
}
我证明了为什么算法在上面的评论中起作用(至少大多数情况下)。这是一个归纳论点。对于一般n > 1
,您可以采取任何可能的顺序。第一个n-1
数字(从左开始)必须形成一个有效的序列(通过矛盾)。使用归纳然后检查最内层循环中的逻辑,我们可以看到将输出我们想要的序列。这个具体的实现你还需要一些关于终止等的证明。例如,Queue
的要点是我们要处理长度n
的序列,同时我们将长度n+1
的序列添加到相同的 Queue
。 Queue
的排序允许最内层的while
循环终止(因为在我们到达n
序列之前,我们将遍历所有长度n+1
的序列。)< / p>
答案 4 :(得分:0)
这似乎并不太复杂。写一个基本N增量的细化,改变当一个数字从零增加时,它会直接变为其左边的最大数字。
更新我误读了规范,我对此的初步看法并没有完全发挥作用。根据实际数据集,uniq.sort
可能过于昂贵,但当序列中的项只有几位数时,它就可以了。 正确的方式是维护数字的第二个排序副本,但我会这样离开,直到我知道这个效率太低。
注意这里的0..N值旨在用作每个数字可以采用的实际值的排序列表的索引。对map
的调用将生成序列的真实元素。
此程序转储序列中与您自己显示的序列相同的部分(一切从5开始)。
def inc!(seq, limit)
(seq.length-1).downto(0) do |i|
if seq[i] == limit
seq[i] = 0
else
valid = seq.first(i).uniq.sort
valid += ((valid.last || 0).next .. limit).to_a
seq[i] = valid.find { |v| v > seq[i] }
break
end
end
end
seq = Array.new(3,0)
loop do
puts seq.join if seq[0] == 5
inc!(seq, 9)
break if seq == [0,0,0]
end
<强>输出强>
500
505
506
507
508
509
550
555
556
557
558
559
560
565
566
567
568
569
570
575
577
578
579
580
585
588
589
590
595
599