以数学方式查找数字子字符串,无需字符串比较
Posted
技术标签:
【中文标题】以数学方式查找数字子字符串,无需字符串比较【英文标题】:Finding numerical substrings mathematically, without string comparison 【发布时间】:2010-09-18 21:59:45 【问题描述】:这原本是我在工作中遇到的一个问题,但现在我只是为了自己的好奇心而尝试解决的问题。
我想找出 int 'a' 是否以最有效的方式包含 int 'b'。我写了一些代码,但似乎不管我写什么,把它解析成一个字符串然后使用 indexOf 是数学上的两倍。
内存不是问题(在合理范围内),只是处理速度。
这是我编写的数学代码:
private static int[] exponents = 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 ;
private static boolean findMatch(int a, int b)
if (b > a) return false;
if (a == b) return true;
int needleLength = getLength(b);
int exponent = exponents[needleLength];
int subNum;
while (a >= 1)
subNum = a % exponent;
if (subNum == b)
return true;
a /= 10;
return false;
private static int getLength(int b)
int len = 0;
while (b >= 1)
len++;
b /= 10;
return len;
这是我正在使用的字符串方法,它似乎胜过上面的数学方法:
private static boolean findStringMatch(int a, int b)
return String.valueOf(a).indexOf(String.valueOf(b)) != -1;
因此,尽管这对于我完成我的工作并不是真正需要的,但我只是想知道是否有人可以想出任何方法来进一步优化我的数学方法,或者完全是一种全新的方法。再次,记忆是没有问题的,我只是为了速度而拍摄。
我真的很想看到或听到任何人对此提出的任何建议。
编辑:当我说包含时,我的意思是可以在任何地方,例如,findMatch(1234, 23) == true
编辑: 对于每个人都说这个废话是不可读和不必要的:你没有抓住重点。关键是要解决一个有趣的问题,而不是想出要在生产代码中使用的答案。
【问题讨论】:
正如所写,您的问题无法回答。把它精简到最基本的东西 有趣的是字符串版本更快,因为数字的 toString 不是必须执行类似的 shift/mod/div 操作才能将数字转换为数字吗? 【参考方案1】:嗯,我可能完全误解了这个问题,但是.....
// Check if A is inside B lol
bool Contains (int a, int b)
return (a <= b);
除非你想知道一个特定的数字序列是否在另一个数字序列中。
在这种情况下,将其转换为字符串将比进行数学计算更快。
【讨论】:
【参考方案2】:无论如何,这绝不会回答您的问题,但无论如何都是建议:-)
方法名称findMatch
描述性不是很好。在这种情况下,我有一个静态方法ContainerBuilder.number(int)
,它返回一个ContainerBuilder
,它上面有方法contains
。这样你的代码就变成了:
boolean b = number(12345).contains(234);
只是一些长期的建议!
哦,是的,我也想说,你应该定义你所说的“包含”是什么意思
【讨论】:
是的,这段代码并没有投入生产,只是我快速完成的东西 别忘了 ContainerBuilderFactory 和 IBuiltContainer @Jonathan:我正要写同样的东西 :) 是的,干得好。哈哈!仅仅因为,在我的日常工作中,我无休止地遇到了大量的方法,其中充满了诸如findMatch
之类的无证代码,我完全没有资格发布一些(友好的)一般性建议。哦,谢谢你也给我一些建议!
Nalandial - 不,这看起来像是家庭作业。因此,对未来的一般建议。【参考方案3】:
我能想到的唯一优化是自己转换为字符串,并在转换时比较数字(从右到左)。首先转换 b 的所有数字,然后从 a 的右侧开始转换,直到在 b 的第一个数字上找到匹配项(从右侧开始)。比较直到所有 b 匹配或您遇到不匹配。如果您遇到不匹配的情况,请回溯到您开始匹配 b 的第一个数字的位置,前进 a 并重新开始。
IndexOf 必须执行基本相同的回溯算法,除了从左侧开始。根据实际数字,这可能会更快。我认为如果数字是随机的,应该是因为应该有很多次不必转换所有a。
【讨论】:
我实际上是在寻找一种方法,使数学方法比字符串比较更快,如果存在这种方法的话。正如我所说,这已成为我个人的挑战,并且已经脱离了项目的实际需求。 我喜欢这个主意,它可以节省一些时间,尤其是在可能命中的时候。另外,虽然公认的解决方案将 a 和 b 限制为 16 位,但这个解决方案仅以这种方式限制较小的 b。【参考方案4】:它应该是更快的字符串方式,因为你的问题是文本的,而不是数学的。请注意,您的“包含”关系没有说明数字,它只说明了它们的十进制表示。
还要注意,您要编写的函数将是不可读的 - 另一个开发人员永远不会理解您在做什么。 (看看你在这里遇到了什么麻烦。)另一方面,字符串版本非常清楚。
【讨论】:
那么我只是出于好奇而提出的挑战:让数学变得更快! :D 哦,我很清楚它完全不可读。正如我所提到的,这只是出于好奇,不会出现在任何生产代码中。只是让一些人有机会出去玩:P 我明白了,我只指出了一个方面,没有回答主要问题。至于主要问题,tvanfosson 指出了我看到的获得额外速度的唯一希望:仅在需要时将 a 转换为十进制。如果匹配存在,则可以节省一些时间。【参考方案5】:有没有办法用二进制计算?显然,包含另一个字符的二进制整数的整数的二进制值并不意味着十进制也一样。但是,是否可以使用某种二进制技巧?也许将像 12345 这样的数字转换为 0001 0010 0011 0100 0101,然后进行一些位移以确定其中是否包含 23 (0010 0011)。因为您的字符集只有 10 个字符,您可以通过在单个字节中存储 2 个字符值来缩短计算时间。
编辑
稍微扩展一下这个想法。如果您有 2 个整数 A 和 B,并且想知道 A 是否包含 B,则首先检查 2 件事。如果 A 小于 B,则 A 不能包含 B。如果 A = B,则 A 包含 B。此时您可以将它们转换为字符串*。如果 A 包含与 B 相同数量的字符数,则 A 不包含 B,除非它们相等,但是如果它们相等,我们就不会在这里,所以如果两个字符串的长度相同,则 a 不包含 b .此时,A 的长度将比 B 长。因此,现在您可以将字符串转换为其打包的二进制值,正如我在本文第一部分中所指出的那样。将这些值存储在整数数组中。现在对数组中的整数值进行按位与运算,如果结果为 A,则 A 包含 B。现在将 B 的整数数组向左移动 4 位,然后再次进行比较。这样做直到你开始从 B 的左侧弹出位。
*上一段中的 * 表示您可以跳过此步骤。可能有一种方法可以完全不使用字符串。可能有一些花哨的二进制技巧可以用来获得我在第一段中讨论的打包二进制表示。应该有一些可以使用的二进制技巧,或者一些快速的数学运算,可以将整数转换为我之前讨论过的十进制值。
【讨论】:
我在想这个,但老实说我想不出办法。 这不是“转换为字符串”计划所做的几乎完全一样的事情,尽管由于您使用 BCD 字符串而不是 ASCII/UTF-8/UTF-16/任何字符串而节省了一小部分内存?【参考方案6】:看起来你的功能实际上做得很好,但有一个小改进:
private static boolean findMatch(int a, int b)
if (b > a) return false;
if (a == b) return true;
int needleLength = getLength(b);
int exponent = exponents[needleLength];
int subNum;
while (a > b)
subNum = a % exponent;
if (subNum == b)
return true;
a /= 10;
return false;
仅仅因为一旦a小于b,就不值得一直寻找,不是吗? 如果您找到解决方案,祝您好运并发布!
【讨论】:
这实际上是一个非常好的观点,并且确实对其进行了相当多的优化。不错的收获! 运行更多测试,这对于 b 相当大的情况非常有用。当 b 很大时,字符串方法花费的时间相同,而数学方法则以压倒性优势击败它!【参考方案7】:仅供参考
http://refactormycode.com/
可以为你工作。
【讨论】:
呃!我个人的猜测是,消极点是针对垃圾邮件的?无论如何,我只推荐了另一个通常可以工作的工具(如 ***),这很糟糕吗? 因为它看起来像垃圾邮件。您没有提供有关链接是什么的足够信息。 “Hi [link] can work”形式的 cmets 对几乎所有人来说都是垃圾邮件。【参考方案8】:这是一个有趣的问题。 String.class 的许多函数实际上是原生的,这使得击败 String 成为一个困难的命题。但这里有一些帮手:
提示1:不同的简单整数运算有不同的速度。
通过示例程序中的快速计算显示:
% ~ T
* ~ 4T
/ ~ 7T
因此,您希望使用尽可能少的除法来支持乘法或取模。未显示减法、加法和比较运算符,因为它们会将所有这些都从水中吹走。此外,尽可能使用“final”允许 JVM 进行某些优化。加速你的“getLength”功能:
private static int getLength(final int b)
int len = 0;
while (b > exponents[len])
len++;
return len + 1
这使功能提高了大约 7 倍。如果 b > 指数中的最大值,则会出现 indexOutOfBounds 异常。为了解决这个问题,您可以:
private static int getLength(final int b)
int len = 0;
final int maxLen = exponents.length;
while (len < maxLen && b > exponents[len])
len++;
return len + 1;
如果 b 太大,这会稍微慢一些,并且给你一个不正确的长度,但它不会引发异常。
提示 2:不必要的对象/基元创建和方法调用会增加运行时间。
我猜测“getLength”不会在其他任何地方调用,因此虽然拥有一个单独的函数可能会很好,但从优化的角度来看,它是一个不必要的方法调用和对象“len”的创建。我们可以将代码放在我们使用它的地方。
private static boolean findMatch(int a, final int b)
if (b > a) return false;
if (a == b) return true;
int needleLength = 0;
while (b > exponents[len])
needleLength ++;
needleLength++;
final int exponent = exponents[needleLength];
int subNum;
while (a >= 1 && a <= b)
subNum = a % exponent;
if (subNum == b)
return true;
a /= 10;
return false;
另外,请注意,我将底部的 while 循环也更改为包含“a
【讨论】:
【参考方案9】:这是 Kibbee 的路线,但在他发布并解决此问题之前,我对此有点感兴趣:
long mask ( long n )
long m = n % 10;
long n_d = n;
long div = 10;
int shl = 0;
while ( n_d >= 10 )
n_d /= 10;
long t = n_d % 10;
m |= ( t << ( shl += 4 ));
return m;
boolean findMatch( int a, int b )
if ( b < a ) return false;
if ( a == b ) return true;
long m_a = mask( a ); // set up mask O(n)
long m_b = mask( b ); // set up mask O(m)
while ( m_a < m_b )
if (( m_a & m_b ) == m_a ) return true;
m_a <<= 4; // shift - fast!
if ( m_a == m_b ) return true;
// O(p)
return false;
void testContains( int a, int b )
print( "findMatch( " + a + ", " + b + " )=" + findMatch( a, b ));
testContains( 12, 120 );
testContains( 12, 125 );
testContains( 123, 551241238 );
testContains( 131, 1214124 );
testContains( 131, 1314124 );
由于 300 个字符太少,无法进行论证,因此我正在编辑这篇主要帖子以回应 Pyrolistical。
与 OP 不同,本机编译的 indexOf 比带有原语的 Java 代码更快,我并不感到惊讶。因此,我的目标不是在整个 Java 代码中找到我认为比称为无数次的本机方法更快的东西。
OP 明确表示这不是生产问题,更像是一种无所事事的好奇心,所以我的回答解决了这种好奇心。我的猜测是,当他试图在生产中解决它时,速度是一个问题,但作为一种空闲的好奇心,“这种方法将被调用数百万次”不再适用。正如他必须向一位发帖人解释的那样,它不再作为生产代码来追求,因此复杂性不再重要。
此外,它提供了页面上唯一能够在“551241238”中找到“123”的实现,因此除非正确性是无关紧要的问题,否则它提供了这一点。此外,“一种使用 Java 原语以数学方式解决问题但优于优化的本机代码的算法”的解决方案空间可能是 EMPTY。
另外,从您的评论中不清楚您是否将苹果与苹果进行了比较。功能规范是 f( int, int )-> boolean,而不是 f( String, String )-> boolean (这是indexOf
的一种域)。所以除非你测试了这样的东西(它仍然可以击败我的,我不会感到非常惊讶。)额外的开销可能会消耗掉多余的 40%。
boolean findMatch( int a, int b )
String s_a = "" + a;
String s_b = "" + b;
return s_a.indexOf( s_b ) > -1;
它执行相同的基本步骤。 log10( a ) encoding + log10( b ) encoding + 实际找到匹配,这也是 O(n) where n 是最大的对数。
【讨论】:
当改成整数时,这与字符串函数相当,我认为这和它会得到的一样好。 是的,我后来想到了这个方法,但没有花时间真正把它全部编程出来。 你分析了吗?我的快速测试表明,这种方法比 string.contains 方法慢了大约 40%。 Pyrolistical,它符合“以数学方式查找数字子字符串,无需字符串比较”,它是否比本机代码更快完全是另一回事。 如果您阅读问题和 Nalandial 的 cmets,您会发现他正在寻找更快的替代者。所以是的,正确的答案需要更快,而不仅仅是正确。【参考方案10】:我能问一下你在代码中的什么地方使用了这个函数吗?也许还有另一种方法可以解决它目前正在解决的问题,这种方法会更快。这可能就像当我的朋友要求我完全重新调音他的吉他时,我做到了,然后才意识到我可以将底弦降低一整步并获得相同的结果。
【讨论】:
以上是关于以数学方式查找数字子字符串,无需字符串比较的主要内容,如果未能解决你的问题,请参考以下文章
如何对oracle sql中的数字字符串进行数学比较[重复]