9

This is more a open question than an error-related question, so if you don't like to answer these kinds of questions, please don't flame.

I have a huge (!) list of ships in a .csv file, separated by ,

The matrix is organised like this: enter image description here
repeated with different data about 500 times.

Now, I want this to be read into objects, which can be used further to populate a UITableView Currently, I hard-code data into the object files, like this

arrayWithObjectsForTableView = [[NSMutableArray alloc] init];
if ([boatsFromOwner isEqualToString:@"Owner1"]) {
    cargoShips* ship = [[cargoShips alloc]init];
    ship.name = @"name1";
    ship.size = 1000;
    ship.owner = @"Owner1";

    [self.boatsForOwner addObject:ship];

    ship = [[cargoShips alloc]init];
    ship.name = @"Name 2";
    ship.size = 2000;
    ship.owner = @"Owner2";

And so on and on with if-else's. This is a bad method, as 1) Its boring and takes a long time 2) It takes even more time if I want to update the information. So, I figured it would be smarter to read programmatically from the matrix instead of doing it myself. Yeah, captain obvious came for a visit to my brain.

So, to the question! How can I read the .csv file that looks like this: enter image description here add the ships of, say, owner, to a NSMutableArray, in the shape of objects. (So they can be used to feed my UITableView with ships.

I would also like to have the option to sort by different stuff, like Country of build, Operator etc. How can I make code that feeds relevant ships read from the .csv into objects? I don't know much programming, so in-depth answers would be very appreciated.

bdash
  • 18,110
  • 1
  • 59
  • 91
OleB
  • 618
  • 4
  • 13
  • 4
    Well, first read a line at a time into an NSString. Then use `componentsSeparatedByString:@","` to divide the line into an NSArray. Then access the individual elements of the array by index -- a given index presumably has a given meaning. – Hot Licks Jan 21 '13 at 21:32

3 Answers3

11

The depth of your processing will determine what sort of data structure is required for this task. This is the method I would use:

1: Read the .csv file into one giant NSString object:

NSString *file = [[NSString alloc] initWithContentsOfFile:yourCSVHere];

2: Get the individual lines:

NSArray *allLines = [file componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];

3: For each line, get the individual components:

for (NSString* line in allLines) {
    NSArray *elements = [line componentsSeparatedByString:@","];
    // Elements now contains all the data from 1 csv line
    // Use data from line (see step 4)
}

4: This is where it's up to you. My first thought would be to create a class to store all your data. For example:

@interface Record : NSObject
//...
@property (nonatomic, copy) NSString *name
@property (nonatomic, copy) NSString *owner
// ... etc
@end

4a: Then, back in step 3 create a Record object for each line and then put all the Record objects into a separate NSArray (something with larger scope!).

5: Use your NSArray that contains all your Record objects as the data source for your UITableView.

The implementation of Steps 4 and 5 are up to you. That's probably how I would do it though for a medium sized .csv file.

EDIT: Here's how to generate the Records.

//
NSMutableArray *someArrayWithLargerScope = [[NSMutableArray alloc] init];
//

NSString *file = [[NSString alloc] initWithContentsOfFile:yourCSVHere];
NSArray *allLines = [file componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet];

for (NSString* line in allLines) {
    NSArray *elements = [line componentsSeparatedByString@","];
    Record *rec = [[Record alloc] init];
    rec.name = [elements objectAtIndex:0];
    rec.owner = [elements objectAtIndex:1];
    // And so on for each value in the line.
    // Note your indexes (0, 1, ...) will be determined by the
    // order of the values in the .csv file.
    // ...
    // You'll need one `Record` property for each value.

    // Store the result somewhere
    [someArrayWithLargerScope addObject:rec];
}
shawnwall
  • 4,549
  • 1
  • 27
  • 38
Ephemera
  • 8,672
  • 8
  • 44
  • 84
  • This is great, except; How do I add them to a object (The one you call `record`)The read and separate code was perfect, but this code:http://pastebin.com/Xfru063b does not work, obviously, since I used array pointers. The request is: How do you access the `line`string into an object? – Oscar Apeland Jan 26 '13 at 16:23
  • One more question; How can I make it check for duplicates? I'm adding the say, owners, to a `NSMutableArray`, but as each owner have multiple ships, they get duped in the array. Any good way to fix this? And also, add all the objects with the duplicate owner under the same owner. Sorry if this is confusing – Oscar Apeland Jan 27 '13 at 01:20
  • @OscarApeland To do that you could make an (instance) method for `Record` called `-isEqualTo:(Record)` or something similar, to detect duplicates. Or, You MAY be able to create an `NSSet` from your `NSMutableArray` and the `NSSet` will autodelete all duplicates but I'm not sure which method it uses to check if two things are equal. I suspect it is `isEqual:` in which case you will have to override it to do the check. A final way would be to manually run a for loop over your array checking duplicates. This sounds inefficient but really, that's exactly what's happening under the hood anyway. – Ephemera Jan 27 '13 at 09:30
  • What is the - newlineCharacterSet ? You didn't define it – Yossi Nov 18 '14 at 16:31
  • @Yossi It's a predefined set by Apple. According to the [docs](https://developer.apple.com/Library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSCharacterSet_Class/index.html) it contains the characters U+000A–U+000D and U+0085. – Ephemera Nov 19 '14 at 00:40
3

In terms of the CSV parsing, assuming you can spend the memory it's probably easiest to read in the whole thing to an NSString, split that on newlines and then split each line on commas, essentially as PLPiper suggests.

At that point I'd take a detour into key-value coding. Give your columns in the CSV file exactly the same name as the properties on your runtime object. Then you can just write something like:

// this method will create an object of the given type then push the values
// from valueRow to the properties named by headingRow. So, ordinarily,
// headingRow will be the first row in your CSV, valueRow will be any other
- (id)populatedObjectOfType:(Class)type withHeadingRow:(NSArray *)headingRow valueRow:(NSArray *)valueRow
{
    // we need the count of fields named in the heading row to 
    // match the count of fields given in this value row
    if([headingRow count] != [valueRow count]) return nil;

    // autorelease if you're not using ARC
    id <NSObject> newInstance = [[type alloc] init];

    // we need to enumerate two things simultaneously, so
    // we can fast enumerate one but not the other. We'll
    // use good old NSEnumerator for the other
    NSEnumerator *valueEnumerator = [valueRow objectEnumerator];
    for(NSString *propertyName in headingRow)
    {
        [newInstance setValue:[valueEnumerator nextObject] forKey:propertyName];
    }

    return newInstance;
}

... elsewhere ....
CargoShip *newShip = [self populateObjectOfType:[CargoShip class] withHeadingRow:[csvFile objectAtIndex:0] valueFor:[csvFile objectAtIndex:1]];

The main caveat is that the built-in mechanisms will convert between scalars and objects but not between objects of different types. So if you had all NSString and C integer types (short, int, NSUInteger, etc) you'd be fine, but if you had some NSStrings and, say, some NSNumbers then you would end up with strings stored in the number slots. It looks like you're using C integer types (as is quite normal) so you should be fine.

In terms of filtering, you can use NSPredicates. For example, suppose you had an array of CargoShips and wanted every one with a size of at least 500:

NSArray *bigShips = [allShips filteredArrayUsingPredicate:
    [NSPredicate predicateWithFormat:@"size > 500"]];

Similarly, for sorting you can throw some NSSortDescriptors at the problem. E.g.

NSArray *shipsSortedBySize = [allShips sortedArrayUsingDescriptors:
     @[[NSSortDescriptor sortDescriptorWithKey:@"size" ascending:YES]]];
Tommy
  • 99,986
  • 12
  • 185
  • 204
3

you can use this link,
https://github.com/davedelong/CHCSVParser
It is the .csv parser and CHCSVParser can be configured to parse other "character-seperated" file formats, such as "TSV" (tab-seperated) or comma seperated.

Banker Mittal
  • 1,918
  • 14
  • 26
  • 1
    Explain please, what is this? How do you use it? – Oscar Apeland Jan 25 '13 at 12:42
  • 1
    u can see this, http://caydenliew.com/2011/11/parsing-csv-to-objective-c-with-chcsvparser/ http://stackoverflow.com/questions/3788295/how-to-use-the-chcsvparser-class http://stackoverflow.com/questions/3788295/how-to-use-the-chcsvparser-class – Banker Mittal Jan 29 '13 at 09:54