Algorithm 确定范围是否包含不区分大小写的搜索短语的算法?
假设有数千个文件按以下方式组织:首先按文件名对它们进行排序(区分大小写,使大写文件优先于小写文件),然后将它们分组到包含该文件夹中第一个和最后一个文件名的文件夹中。例如,文件夹可能看起来像:Algorithm 确定范围是否包含不区分大小写的搜索短语的算法?,algorithm,language-agnostic,range,case-insensitive,Algorithm,Language Agnostic,Range,Case Insensitive,假设有数千个文件按以下方式组织:首先按文件名对它们进行排序(区分大小写,使大写文件优先于小写文件),然后将它们分组到包含该文件夹中第一个和最后一个文件名的文件夹中。例如,文件夹可能看起来像: Abel -> Cain Camel -> Sloth Stork -> basket basking -> sleuth tiger -> zebra 现在,给定一个不区分大小写的搜索字符串s,确定哪些文件夹可以包含与s匹配的文件。您不能也不必查看文件夹内部-该文件实际上不
Abel -> Cain
Camel -> Sloth
Stork -> basket
basking -> sleuth
tiger -> zebra
现在,给定一个不区分大小写的搜索字符串s
,确定哪些文件夹可以包含与s
匹配的文件。您不能也不必查看文件夹内部-该文件实际上不必存在
一些例子:
("Abel", "Cain") matches s = "blue", since it contains "Blue"
("Stork", "basket") matches s = "arctic", since it contains "arctic"
("FA", "Fb") matches s = "foo", since it contains "FOo"
("Fa", "Fb") does NOT match s = "foo"
形式上:给定一个封闭范围[a,b]
和一个小写字符串s
,确定[a,b]
中是否有任何字符串c
,以便小写(c)=s
我的第一个直觉是对范围的边界进行不区分大小写的搜索。但从最后一个例子可以很容易看出,这是不正确的
bruce force的解决方案是生成所有可能的文件名。例如,输入字符串“abc”
将产生候选“abc”、“abc”、“abc”、“abc”、“abc”、“abc”、“abc”
。然后,您只需根据边界测试每一个。下面是暴力解决方案的一个示例。这是O(2^n)
我的问题是,是否有一种算法既快速又正确?
Clojure中的暴力解决方案:
(defn range-contains
[first last string]
(and (<= (compare first string) 0)
(>= (compare last string) 0)))
(defn generate-cases
"Generates all lowercase/uppercase combinations of a word"
[string]
(if (empty? string)
[nil]
(for [head [(java.lang.Character/toUpperCase (first string))
(java.lang.Character/toLowerCase (first string))]
tail (generate-cases (rest string))]
(cons head tail))))
(defn range-contains-insensitive
[first last string]
(let [f (fn [acc candidate] (or acc (range-contains first last (apply str candidate))))]
(reduce f false (generate-cases string))))
(fact "Range overlapping case insensitive"
(range-contains-insensitive "A" "Z" "g") => true
(range-contains-insensitive "FA" "Fa" "foo") => true
(range-contains-insensitive "b" "z" "a") => false
(range-contains-insensitive "B" "z" "a") => true)
(定义范围包含
[最后一个字符串]
(和(=(比较最后一个字符串)0)))
(defn)生成案例
“生成单词的所有小写/大写组合”
[字符串]
(如果(空?字符串)
[无]
(用于[head[(java.lang.Character/toUpperCase(第一个字符串))
(java.lang.Character/toLowerCase(第一个字符串))]
尾部(生成案例(rest字符串))]
(反对者头尾)
(defn范围包含不敏感的
[最后一个字符串]
(设[f(fn[acc候选者](或acc(范围包含最后一个(应用str候选者))))]
(减少f false(生成案例字符串)))
(事实“范围重叠不区分大小写”
(范围包含不敏感的“A”“Z”“g”)=>true
(范围包含不敏感的“FA”“FA”“foo”)=>true
(范围包含不敏感的“b”“z”“a”)=>false
(范围包含不敏感的“B”“z”“a”)=>true)
我认为不需要创建所有大小写组合,而是可以通过分别检查每个字符的大写字母,然后是小写字母来解决这个问题,将2^N更改为2N
其思路如下:
- 保留“lowdone”和“highdone”标志,它们指示s是否肯定会在下限之后,而仍然可能在上限之前,反之亦然
- 逐字符遍历字符串
- 检查当前字母的大写版本是否可以位于相应的下限字母之后,同时位于上限字母之前,然后检查该字母的小写版本,如果两个字母都不满足这两个条件,则返回false(如果“lowdone”,则不检查下限)是真的,如果“highdone”是真的,不要检查上限-在比较ABC和ACA时,一旦我们超过第二个字母,我们就不关心第三个字母)
- 如果一个案例同时满足这两个条件,请检查它是否严格地出现在下限字母之后,或者下限太短而没有相应的字母,如果是,lowdone=true
- 与highdone类似=真
公共括号(字符串l、字符串u)
{
低=l;
高=u;
}
公共布尔IsMatch(字符串s)
{
字符串su=s.ToUpper();
字符串sl=s.ToLower();
bool-lowdone=false;
bool highdone=false;
对于(int i=0;i=Low.Length | c[j]>=Low[i])&&(highdone | | i>=High.Length | c[j]=Low.Length | c[j]>Low[i])
ld=真;
如果(i>=High.Length | c[j]s.Length)
返回false;
返回true;
}
}
我认为不需要创建所有大小写组合,而是可以通过分别检查每个字符的大写字母,然后是小写字母来解决这个问题,将2^N更改为2N
其思路如下:
- 保留“lowdone”和“highdone”标志,它们指示s是否肯定会在下限之后,而仍然可能在上限之前,反之亦然
- 逐字符遍历字符串
- 检查当前字母的大写版本是否可以位于相应的下限字母之后,同时位于上限字母之前,然后检查该字母的小写版本,如果两个字母都不满足这两个条件,则返回false(如果“lowdone”,则不检查下限)是真的,如果“highdone”是真的,不要检查上限-在比较ABC和ACA时,一旦我们超过第二个字母,我们就不关心第三个字母)
- 如果一个案例同时满足这两个条件,请检查它是否严格地位于下限字母或下限字母之后
public Bracket(string l, string u) { Low = l; High = u; } public bool IsMatch(string s) { string su = s.ToUpper(); string sl = s.ToLower(); bool lowdone = false; bool highdone = false; for (int i = 0; i < s.Length; i++) { char[] c = new char[]{su[i], sl[i]}; bool possible = false; bool ld = lowdone; bool hd = highdone; for (int j = 0; j < 2; j++) { if ((lowdone || i >= Low.Length || c[j] >= Low[i]) && (highdone || i >= High.Length || c[j] <= High[i])) { if (i >= Low.Length || c[j] > Low[i]) ld = true; if (i >= High.Length || c[j] < High[i]) hd = true; possible = true; } } lowdone = ld; highdone = hd; if (!possible) return false; } if (!lowdone && Low.Length > s.Length) return false; return true; } }
public static boolean inRange(String search, String first, String last) { int len = search.length(); if (len == 0) { return true; } char low = Strings.padEnd(first, len, (char) 0).charAt(0); char high = Strings.padEnd(last, len, (char) 0).charAt(0); char capital = Character.toLowerCase(search.charAt(0)); char small = Character.toUpperCase(search.charAt(0)); if (low == high) { if (capital == low || small == low) { // All letters equal - remove first letter and restart return inRange(search.substring(1), first.substring(1), last.substring(1)); } return false; } if (containsAny(Ranges.open(low, high), capital, small)) { return true; // Definitely inside } if (!containsAny(Ranges.closed(low, high), capital, small)) { return false; // Definitely outside } // Edge case - we are on a bound and the bounds are different if (capital == low || small == low) { return Ranges.atLeast(first.substring(1)).contains(search.substring(1).toLowerCase()); } else { return Ranges.lessThan(last.substring(1)).contains(search.substring(1).toUpperCase()); } } private static <T extends Comparable<T>> boolean containsAny(Range<T> range, T value1, T value2) { return range.contains(value1) || range.contains(value2); }