NSDictionary不区分大小写地查找对象。

9

NSDictionary类中有objectForKey方法,但是它对键名区分大小写。没有可用的函数可以像这样进行不区分大小写的查找。

- (id)objectForKey:(id)aKey options:(id) options;

在选项中,您可以传递“NSCaseInsensitiveSearch”参数。

要从大小写不敏感的NSDictionary中获取密钥,可以使用下面编写的以下代码。


欢迎通过适当的理由进行踩票。 - NNikN
这是一个自问自答并分享的问题。 - NNikN
但是,当我提出问题时,我看到了一个“回答自己的问题”的选项。所以,这就是我所做的。顺便说一句,你对于获得声望的评论相当讽刺。我看到很多编码风格,你也可以像@Ramy那样尝试回答。请指定您的博客,这样我就可以在那里提出这样的问题。 - NNikN
@Jennis:可能是,也可能不是。如果你有一个好问题并找到了一个好答案,那就是 Stack Overflow 的用处所在。 - Chuck
@Jennis,我想分享我的答案。我找到了“回答自己的问题-以问答形式分享你的知识”。所以我写了这一句:“要从不区分大小写的NSDictionary中获取键,可以使用下面的代码。”很奇怪你对这个网站的回复方式感到满意。请看看其他人已经回答自己问题来帮助他人的许多问题。无论如何,谢谢你的批评。 - NNikN
6个回答

12
您需要添加NSDictionary类别,具有此功能。
- (id)objectForCaseInsensitiveKey:(NSString *)key {
    NSArray *allKeys = [self allKeys];
    for (NSString *str in allKeys) {
        if ([key caseInsensitiveCompare:str] == NSOrderedSame) {
            return [self objectForKey:str];
        }
    }
    return nil;
}

这是个好主意,创建一个类别。但是,为什么需要编写for循环呢?可以使用现成的函数,它使用NSPredicate。 - NNikN
9
...而你刚刚将搜索复杂度从 O(log(N)) 提高到了 O(N) - ivanzoid
@ivanzoid 实际上,你只是将它从 O(1) 提升到了 O(n)NSDictionary 是一个哈希表,而不是一棵树。 - al45tair

11

这里有几个原因不包括在内:

  1. NSDictionary使用哈希相等,而对于任何良好的哈希算法,源字符串中的任何变化都会导致不同的哈希。

  2. 更重要的是NSDictionary的键不是字符串。符合NSCopying协议的任何对象都可以作为字典键,其中包括比字符串多得多的对象。NSNumber和NSBezierPath之间的大小写不敏感的比较会是什么样子?

这里的许多答案提供的解决方案都相当于将字典转换成数组并迭代它。那可行,如果你只需要一次性的话这没关系。但是该解决方案非常丑陋且具有糟糕的性能特征。如果我经常需要这个(例如足够创建一个NSDictionary类别),我会想要在数据结构级别上正确地解决它。

你需要的是一个类,它包装了一个NSDictionary,只允许使用字符串作为键,并在给定键时自动将其小写(如果需要双向映射,还可能记住原始键)。这将相当简单实现,并且是一个更清晰的设计。如果只需要一次性,那么这太麻烦了,但如果你经常这样做,我认为值得整洁地处理它。


非常好,这是一个非常好的答案。我同意你的观点,objectForKey并不总是NSString类型,但它是最常用的类型。我认为当调用objectForKey时,系统会计算传递给函数的键的哈希值,然后执行一个循环来匹配该哈希值与可用哈希值,并在找到匹配时返回相应的值。如果我说错了,请纠正我。 - NNikN
@andyPaul:这比那个复杂一些。该算法需要线性时间来查找键的值(即字典中每个额外项都会增加一定量的开销,从而平均匹配键和值所需的时间更长),但即使有数十万个对象,NSDictionary仍然保持快速。但核心思想,即通过比较哈希值来实现,是完全正确的。(顺便说一句,我并不是故意含糊其辞。NSDictionary不是一个类,而是许多不同的类,具有不同的实现,但从外部看起来都是相同的。) - Chuck
小写化不是正确的转换方式 - 你需要进行大小写折叠。 - al45tair

2
正确的答案是应该使用大小写折叠后的键作为字典键。这与将它们转换为大写或小写不同,而且不会破坏 O(1) 平均情况下的搜索/插入复杂度。
不幸的是,Cocoa 似乎没有适当的 NSString 方法来折叠字符串的大小写,但 Core Foundation 有 CFStringFold() 可以用于此目的。让我们编写一个简短的函数来完成必要的工作:
NSString *foldedString(NSString *s, NSLocale *locale)
{
  CFMutableStringRef ret = CFStringCreateMutableCopy(kCFAllocatorDefault, 0,
                                                     (__bridge CFStringRef)s);
  CFStringNormalize(ret, kCFStringNormalizationFormD);
  CFStringFold(ret, kCFCompareCaseInsensitive, (__bridge CFLocaleRef)locale);
  return (__bridge_transfer NSString *)ret;
}

请注意,locale参数很重要。如果您指定NULL,则会获得当前系统语言环境。在大多数情况下,这是可以接受的,但是对于土耳其用户来说,他们可能会惊讶地发现“I”与“ı”相匹配,而不是“I”。因此,您可能需要传递[NSLocale currentLocale],如果您要保存结果,还可以保存区域标识符并从中创建区域设置。
因此,在向字典添加内容时,您现在需要执行以下操作:
[dict setObject:obj forKey:foldedString(myKey, locale)];

并且再次查找

[dict objectForKey:foldedString(myKey, locale)];

最后一点观察是您可能希望将大小写折叠的键与原始值一起存储,这样您就不必在每次访问字典时都进行折叠。


1
在下面的代码中,我搜索输入键的实际键。因此,如果输入键为@"naMe",则实际键为@"name"。
NSDictionary *dic=[NSDictionary dictionaryWithObjectsAndKeys:@"John",@"Name",@"123456",@"empId", nil];


NSString *key=@"naMe";
NSString *name=[dic objectForKey:key];

if(name==nil){
    NSPredicate *searchPred=[NSPredicate predicateWithFormat:@"self LIKE[cd] %@",key];
    NSArray *searchedKeys=[[dic allKeys] filteredArrayUsingPredicate:searchPred];

    if(searchedKeys.count>0){
        name=[dic objectForKey:[searchedKeys objectAtIndex:0]];

    }
}

NSLog(@"Name = %@",name);

你也可以这样做。https://dev59.com/jVfUa4cB1Zd3GeqPJZeW - Paramasivan Samuttiram
这很复杂且速度较慢;它首先构建一个包含字典中所有键的数组 O(n),然后必须解析 NSPredicate 并过滤结果数组。 - al45tair

0

很多答案都是正确的,但这里有一个更好的例子:

    NSDictionary* dict= @{ @"hello" : @"Hey" };
    NSArray* keys= [dict allKeys];
    NSUInteger index=[keys indexOfObjectPassingTest:  ^BOOL (id obj, NSUInteger index, BOOL* stop)
     {
         if( [obj caseInsensitiveCompare: @"Hello"]==NSOrderedSame)
         {
             *stop= YES;
             return YES;
         }
         else
         {
             return NO;
         }
     }];

就个人而言,我认为这种方式更容易,但每个人都有自己的编程风格。

编辑

一种不太易读但更短的解决方案:

    NSDictionary* dict= @{ @"hello" : @"Hey" };
    NSArray* keys= [dict allKeys];
    NSUInteger index=[keys indexOfObjectPassingTest:  ^BOOL (id obj, NSUInteger index, BOOL* stop)
     {
         return *stop= [obj caseInsensitiveCompare: @"Hello"]==NSOrderedSame ;

     }];

查看你编写的代码占据了多少行。但是,你可以将整个块写在一行中,但很难理解。 - NNikN
有很多行代码,但实际的代码量非常小。我不会仅仅因为在返回值之前必须将*stop设置为YES而返回[obj caseInsensitiveCompare:@"Hello"]==NSOrderedSame,因此这一行代码就像你所说的那样,几乎无法阅读。 - Ramy Al Zuhouri
这是另一个O(n)的答案。NSDictionary访问是O(1)的。至少我想它没有使用NSPredicate - al45tair

0

如果您只在一个地方(也许是两个或三个地方)将数据存储到NSDictionary中并从中检索数据,那么您可以在这两个地方使用

[myString lowercaseString]

更严谨的答案对于字典对象在代码中的多处使用非常有用。


这实际上是比上面大部分更长的答案要好得多。唯一的错误是在所有情况下小写化不会按预期工作(您真的应该对字符串进行大小写折叠)。 - al45tair

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接