Suppose I have key-value pairs having one to one mapping.
For each key I have unique value.
Then can I make a bi-directional dictionary or something similar to it which can give me value from key and vice versa?
P.S. - I know that I can use [NSDictionary allKeysForObject: (nonnull id)] which returns an NSArray of all keys having that value.
I was just wondering if there is something like bi-directional dictionary then it will be useful.
If there is something like that then please provide solution for swift also.
Thanks.
You can extend Dictionary constraining its Value to Equatable and provide your own subscript to return the first key for value:
Xcode 11 • Swift 5.1
extension Dictionary where Value: Equatable {
subscript(firstKeyFor value: Value) -> Key? { first { $0.value == value }?.key }
func allKeys(for value: Value) -> [Key] { compactMap { $0.value == value ? $0.key : nil } }
}
let dict = ["One": 1, "Two": 2, "Three": 3, "Um": 1, "Dois": 2, "Tres": 3]
if let key = dict[firstKeyFor: 1] {
print(key) // "One"
}
let allKeys = dict.allKeys(for: 2) // ["Dois", "Two"]
if each Key has a unique(!!!) value you can add a Category to NSDictionary with
-(NSString*)keyForValue:(id)value{
NSArray* allKeys = [self allKeysForObject:value];
return [allKeys count] > 0 ? [allKeys objectAtIndex:0] : nil;
}
Related
I have an array of PFObjects. I'd like to search if the "Type" contains "Sushi". The filter alters the array. How can I preform this search without altering the array?
func startCheckOptions(objects: [AnyObject]) {
let filteredArray = objects.filter() {
if let type = ($0 as PFObject)["Type"] as String {
//if "type" contains "sushi", then do something instead of alter array
return type.rangeOfString("Sushi") != nil
} else {
return false
}
}
}
You can use the contains function:
func startCheckOptions(objects: [AnyObject]) -> Bool {
return contains(objects as [PFObject]) { (object) -> Bool in
if let type = object["Type"] as? String {
return type.rangeOfString("Sushi") != nil
}
else {
return false
}
}
}
if startCheckOptions(objects) {
println("yes")
}
else {
println("no")
}
This has the advantage of not building a new array containing the matching objects and stopping on the first match.
In this case you'd be better off protecting the objects cast (or really handling one time somewhere else) by casting it to [PFObject] ASAP. Leaving AnyObject references floating around can only lead to confusion and heartache.
filter() does not alter an array, but it returns a new array. What you have above is correct. objects will be the original array, and filteredArray will be the new array consistent of objects where "type" is "sushi".
data (
{
"name" = "Conway";
"country" = "England";
},
{
"name" = "Bale";
"country" = "Wales";
},
{
"name" = "Stephens";
"country" = "Scotland";
},
{
"name" = "Michael";
"country" = "England";
},
{
"name" = "Pedro";
"country" = "Spain";
},
{
"name" = "Patrick";
"country" = "England";
},
{
"name" = "John";
"country" = "Ireland";
},
{
"name" = "Bob";
"country" = "Ireland";
}
)
I have a JSON array I am parsing. The goal is to display this content in a picker view. However the picker view is custom, the list will appear like this:
**Ireland**
John
Bob
**England**
Conway
Michael
Patrick
etc etc.
As you can see however, the JSON to be parsed is not organised nicely and separated by headers, so I have the joy of doing it in the app instead :( but I am up for the challenge.
I have a player object.
Player.h
NSString * name;
NSString * country;
BOOL isHeader;
The block of code is below that I use to loop through the contents of the downloaded JSON. My current implementation is not ideal, and a bit confusing. But I couldn't think of any other way, I am always open to solutions to do this a quicker way.
I loop through the contents, if its the first time the object is being done, then isHeader is set to true.
The final Array is to contain all the objects from the initial array, but categorised by the country they are from, the country header is also set as a Player object, but with isHeader true. I am open to alternative ways of doing this. The end goal for each object to be separated with a header. I would preferably like to use an array of objects as well, but open to better practice suggestions.
for (int i=0; i < mArray.count; i++) {
if (i==0) {
Player * pPlayer = mArray[i];
pPlayer.isHeader=YES;
[headerArray addObject:pPlayer];
[catArray addObject:pPlayer];
}
else{
BOOL newHeader=YES;
for (int j=0; j<headerArray.count; j++) {
Player * jPlayer =mArray[i];
Player * headerPlayer = headerArray[j];
if ([headerPlayer.country isEqualToString:jPlayer.country]) {
newHeader=NO;
jPlayer.isHeader=NO;
[catArray addObject:jPlayer];
}
}
if (newHeader==YES) {
Player * hPlayer = mArray[i];
hPlayer.isHeader=YES;
[headerArray addObject:hPlayer];
[catArray addObject:hPlayer];
}
}
}
for (int k=0; k<headerArray.count; k++) {
[finalArray addObject:headerArray[k]];
for (int y=0; y<catArray.count; y++) {
Player *cPlayer = catArray[y];
Player *hPlayer = headerArray[k];
if ([hPlayer.country isEqualToString:cPlayer.country]) {
[finalArray addObject:cPlayer];
}
}
}
The current result is:
**Ireland**
**Ireland**
Bob
**England**
**England**
Michael
Patrick
So something is definitely wrong with my conditional statement.
NSMutableDictionary *players = [NSMutableDictionary dictionary];
for(Player *player in mArray) {
NSString *country = player.country;
NSMutableArray *playersArray = players[country];
if(!playersArray) {
playersArray = [NSMutableArray array];
}
[playersArray addObject:player.name];
players[country] = playersArray;
}
I think this should do it, although I'll admit I haven't tested it.
As Droppy said, if you want the headers ordered you'll have to put them in an array. You can get the headers via [players allKeys]; and order them using sortUsingComparator: -- then traverse the players dictionary getting each key in turn from the sorted array.
I'm going through Jastor's documentation:
There's an Objective-C implementation for returning arrays:
+ (Class)categories_class {
return [ProductCategory class];
}
This is my attempt at converting it to Swift, however it ends up not returning anything so I don't think it's implemented correctly:
#<_TtC4TestApp4Room: id = (null) {
resultCount = 50; // 50 is returning fine
results = ( // results is not
);
}>
NSDictionary response:
{
"resultCount" : 50,
"results" : [
{
"collectionExplicitness" : "notExplicit",
"discCount" : 1,
"artworkUrl60" : "http:\/\/a4.mzstatic.com\/us\/r30\/Features\/2a\/b7\/da\/dj.kkirmfzh.60x60-50.jpg",
"collectionCensoredName" : "Changes in Latitudes, Changes in Attitudes (Ultmate Master Disk Gold CD Reissue)"
}
]
}
Music.swift (not quite sure how to implement the results_class() method)
class Music : Jastor {
var resultCount: NSNumber = 0
var results: NSArray = []
class func results_class() -> AnyClass {
return Author.self
}
}
Author.swift
class Author {
var collectionExplicitness: NSString = ""
var discCount: NSNumber = 0
var artworkUrl60: NSString = ""
var collectionCensoredName: NSString = ""
}
I'm using the following syntax (adapted to your example):
static let results_class = Author.self
and everything works for me.
Other differences that may or may not have an effect:
I'm using Int instead of NSNumber and String instead of NSString (except for arrays).
I'm using implicitly wrapped optionals rather than assigning a default value to each field
Say I have 2 NSDictionaries that I don't know beforehand like:
NSDictionary *dictA = #{ #"key1" : #1,
#"key2" : #2 };
NSDictionary *dictB = #{ #"key1" : #"a string" };
I want to find the first match between the keys of dictB and the keys or values of dictA. Each key of dictB will either be a NSNumber or a string. If it's a number, try to find a match from the values of dictA. If it's a string, try to find a match from the keys of dictA.
Using for loops, it would looks something like this:
id match;
for (id key in dictA ) {
for (id _key in dictB {
if ( [_key is kindOfClass:NSNumber.class] && _key == dictA[key] ) {
match = _key
goto outer;
}
else if ( [_key is kindOfClass:NSString.class] && [_key isEqualToString:key] ) {
match = _key
goto outer;
}
}
};
outer:;
NSString *message = match ? #"A match was found" : #"No match was found";
NSLog(message);
How could I rewrite this with ReactiveCocoa using RACSequence and RACStream methods so it looks something like:
// shortened pseudo code:
// id match = [dictA.rac_sequence compare with dictB.rac_sequence using block and return first match];
You basically would like to create the cartesian product of the dictionaries and make a selection on it. There is no default operator in ReactiveCocoa that I know of that would do this for you. (In LINQ there are operators for this.) In RAC the simplest solution is to use the scanWithStart:combine: method to implement this operation. Once the cartesian is ready, the filter: and take:1 operations will produce the sequence of your choice.
NSDictionary *adic = #{#"aa":#"vb", #"ab": #"va"};
NSDictionary *bdic = #{#"ba": #"va", #"bb":#"vb"};;
RACSequence *aseq = adic.rac_keySequence;
RACSequence *bseq = bdic.rac_keySequence;
RACSequence *cartesian = [[aseq scanWithStart:nil combine:^id(id running, id next_a) {
return [bseq scanWithStart:nil combine:^id(id running, id next_b) {
return RACTuplePack(next_a, next_b);
}];
}] flatten];
RACSequence *filteredCartesian = [cartesian filter:^BOOL(RACTuple *value) {
RACTupleUnpack(NSString *key_a, NSString *key_b) = value;
// business logic with keys
return false;
}];
RACSequence *firstMatch = [filteredCartesian take:1];
my array has the following:
{
"4eb57e72c7e24c014f000000" : {
"_id" : {
"$id" : "4eb57e72c7e24c014f000000"
},
"author" : "tim",
"comments" : [],
"created": {
"sec" : 1320517234,
"used" : 856000
},
"picture" : "http://someurl.com",
"text" : "this is a test",
"title" : "test",
"type" : ["test"]
}
I want to sort by created (sec value)
this is what I have....I just do not know how sortedArrayUsingFunction works. I mean what am I comparing in the compare function??
jokesArray = [unSortedContentArray sortedArrayUsingFunction:Sort_Created_Comparer context:self];
NSInteger Sort_Created_Comparer(id num1, id num2, void *context)
{
int v1 = [num1 getSecFromJSONValue];
int v2 = [num2 getSecFromJSONValue];
if (v1 < v2)
return NSOrderedAscending;
else if (v1 > v2)
return NSOrderedDescending;
else
return NSOrderedSame;
}
num1 and num2 are 2 elements of your array and context is an object that you can pass in to your function to help you with the sort.
Your function will be called many times on the array and the result of the sort will be returned to you in a new array.
Is that what you are wondering?
The comparison function is called to compare two values at a time from your NSArray. This is how the sorting is done.
The comparison function is your way of telling the sort algorithm how you want your objects ordered. Without it, it has no way of knowing what the final order should be.
In your example code the argument names num1 and num2 are very misleading. They would be more accurately named object1 and object2. If object1 should come before object2 then return NSOrderedAscending. If it should come after then NSOrderedDescending otherwise return NSOrderedSame.
To illustrate, here is an example that sorts hypothetical Person objects by age (lowest to highest).
NSInteger youngest_first(id object1, id object2, void *context) {
if (object1.age < object2.age) {
return NSOrderedAscending;
}
else if (object1.age > object2.age) {
return NSOrderedDescending;
}
else {
return NSOrderedSame;
}
}
Notice that I didn't even use the context parameter as my objects themselves had sufficient information to determine the order.
If I instead want them to be sorted by descending height then I could pass the following:
NSInteger tallest_first(id object1, id object2, void *context) {
if (object1.height > object2.height) {
return NSOrderedAscending;
}
else if (object1.height < object2.height) {
return NSOrderedDescending;
}
else {
return NSOrderedSame;
}
}
One thing that is very important is that your function should return a consistent result if the arguments are passed in the other order. For example if tallest_first(adam, joe, NULL) returns NSOrderedDescending then tallest_first(joe, adam, NULL) should return NSOrderedAscending. If not your comparison function contradicts itself.