How can I simplify my nested for-loops with ReactiveCocoa? - objective-c

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];

Related

Bi-directional dictionary in objective-c

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;
}

Conditional Statement: separate custom objects with an object header in an array (Code example)

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 having trouble accessing the value I need from an NSDictionary object

I'm having trouble accessing the value I need in an NSDictionary object, a song has multiple values under the track key, but since dictionaries aren't indexable, I'm unable to access the values individually. I have tried storing the object for the track key as an array but the two entries become a single string entry and therefore the array is only of length 1 and not helpful.
Also, doing something like [response valueForKeyPath#"response.songs.tracks.foreign_id"] gives me both foreign ids, how can I just get the first one? I've been struggling with this for a while, have searched all around stackoverflow for answers but none have worked, any help is appreciated, thanks!
This is the data in the NSDictionary:
{
response = {
songs = (
{
"artist_foreign_ids" = (
{
catalog = spotify;
"foreign_id" = "spotify:artist:6vWDO969PvNqNYHIOW5v0m";
}
);
"artist_id" = AR65K7A1187FB4DAA4;
"artist_name" = "Beyonc\U00e9";
id = SOUTJIT142F256C3B5;
title = "Partition (Explicit Version)";
tracks = (
{
catalog = spotify;
"foreign_id" = "spotify:track:2vPTtiR7x7T6Lr17CE2FAE"; //I WANT TO ACCESS JUST THIS VALUE
"foreign_release_id" = "spotify:album:1hq4Vrcbua3DDBLhuWFEVQ";
id = TRLHJRD1460052F846;
},
{
catalog = spotify;
"foreign_id" = "spotify:track:6m4ZFQb3zPt3IdRDTN3tfb";
"foreign_release_id" = "spotify:album:5KPpho7rztJrZVA9yXjk8K";
id = TRYMBSK146005C3A46;
}
);
}
);
status = {
code = 0;
message = Success;
version = "4.2";
};
};
}
The tracks in your sample dictionary appear to already be an array (of dictionaries), so you should be able to do something like:
NSArray *songs = response[#"songs"];
for (NSDictionary *songDict in songs) {
NSArray *tracks = songDict[#"tracks"];
for (NSDictionary *trackDict in tracks) {
NSString *trackForeignId = trackDict[#"foreign_id"];
// Do whatever you like with trackForeignId here.
}
}

Access object of Nested NSDictionary

Is there a way to directly access an inner-Dictionary of an outer Dictionary in Objective-C? For example, I have key to an object which is part of inner dictionary, Is there any way to directly access object from that key.
GameDictionary {
Indoor_Game = { "game1" = chess; "game2" = video_Game; "game3" = poker; };
OutDoor_Game = { "game4" = cricket; "game5" = hockey; "game6" = football; };
};
I have a key "game4", but I don't know in which dictionary object of this key is present, currently I have to search in each dictionary for object, the code which I am using is:
NSString* gameName = nil;
NSString* gameKey = #"game4";
NSArray* gameKeys = [GameDictionary allKeys];
for (int index = 0; index < [gameKeys count]; index ++) {
NSDictionary* GameType = [GameDictionary objectForKey:[gameKeys objectAtIndex:index]];
if ([GameType objectForKey:gameKey]) {
gameName = [GameType objectForKey:gameKey];
break;
}
}
Is their any easy way to access directly to the inner dictionary instead of for loops.
valueForKeyPath looks like what you want.
[GameDictionary valueForKeyPath:#"OutDoor_Game"]
//would return a dictionary of the games - "game4" = cricket; "game5" = hockey; "game6" = football;
[GameDictionary valueForKeyPath:#"OutDoor_Game.game4"]
//would return cricket
https://developer.apple.com/library/ios/DOCUMENTATION/Cocoa/Conceptual/KeyValueCoding/Articles/CollectionOperators.html

Can someone make me understand how sortedArrayUsingFunction works?

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.