-16

I am writing an iOS App in Swift 4.2

Response from server is a string with values separated by pipe character "|". It contains many rows of values. I want to split it into an array of subarrays.

Response example:

"001|apple|red|002|banana|yellow|003|grapes|purple"

For this example, the output should be an array containing 3 arrays of above fruits. If I use response.componentsSeparatedByString("|") it will give me an array with 9 elements, which I don't want. If I take the above example into consideration, what I need is an array of 3 arrays further having 3 elements.

Expected Output:

[[001, "apple", "red"], [002, "banana", "yellow"], [003, "grapes", "purple"]]
17
  • 1
    Add your expectation output what you need. Commented Mar 28, 2019 at 11:13
  • can you show some code or OP Commented Mar 28, 2019 at 11:13
  • 2
    There is a method in String that separate all by a string (like the pipe characters). Afterwards, it up to you to trasnform it into "sub array", but we don't know what it should look like. Does it needs to keep the numbers, etc. Commented Mar 28, 2019 at 11:14
  • Explain what you have tried and why it doesn't work and are you using swift or objective-c? Please remove irrelevant tags Commented Mar 28, 2019 at 11:15
  • 1
    Can you ask the API to give you back some nice JSON instead? :) Commented Mar 28, 2019 at 11:18

6 Answers 6

3

If I got correct what you want to receive as a result, then this code would make what you want:

extension Array {
    func chunked(into size: Int) -> [[Element]] {
        return stride(from: 0, to: self.count, by: size).map {
            Array(self[$0 ..< Swift.min($0 + size, self.count)])
        }
    }
}

let src = "001|apple|red|002|banana|yellow|003|grapes|purple"
let result = src.split(separator: "|").map(String.init).chunked(into: 3)
// result = [["001", "apple", "red"], ["002", "banana", "yellow"], ["003", "grapes", "purple"]]

This will work if you know the expected size of resulting subarrays

You can also remove .map(String.init) from the last line if it's ok for you that array elements are of type String.SubSequence

Sign up to request clarification or add additional context in comments.

7 Comments

Unfortunately your solution doesn't work with dynamic number of items in one subgroup.
Thanks a lot, what is Element type?
@ManWithBear There's no such requirement in question. Also no info that every subarray will start with number, so I've just made an assumption
@AntonFilimonov fair point
|
2
  1. Split string by | character
  2. Then append elements that not a number in last subarray of resulted array
  3. If element is number, then add new subarray to resulted array
  4. Repeat 2-3 until you done
let input = "001|apple|red|002|banana|yellow|003|grapes|purple"
let result: [[String]] = input
    .split(separator: "|")
    .reduce(into: []) { result, string in
        guard let _ = Int(string) else {
            result[result.count - 1].append(String(string))
            return
        }
        result.append([])
    }
/// result: [["apple", "red"], ["banana", "yellow"], ["grapes", "purple"]]

If you want persist 001 as well, then change result.append([]) to result.append([String(string)]):

[["001", "apple", "red"], ["002", "banana", "yellow"], ["003", "grapes", "purple"]]

Important

This solution expect that your string will start with number or crash otherwise.
If you can't guaranteed that your string starts with number, that you need to manually check that array not empty in guard block.

4 Comments

Where are you taking chuncking size in this solution?
@SheikhAtif this solution doesn't take chunk size at all. It always split by numbers, so you can have dynamic number of elements in one subarray
Actually the real response has 100+ elements in one row including . number types many where
If you will always have same number of items in one row, then @AntonFilimov solution will be easier and enough
1

With regex you could do something like this with thanks to OOPer's extension

Add a string extension that splits the string based on a regex pattern.

extension String {
    func split(usingRegex pattern: String) -> [String] {
        //### Crashes when you pass invalid `pattern`
        let regex = try! NSRegularExpression(pattern: pattern)
        let matches = regex.matches(in: self, range: NSRange(0..<utf16.count))
        let ranges = [startIndex..<startIndex] + matches.map{Range($0.range, in: self)!} + [endIndex..<endIndex]
        return (0...matches.count).map { String(self[ranges[$0].lowerBound..<ranges[$0+1].lowerBound]) }
    }
}

Then split your string based on the pattern [0-9]{3} 3 numbers in a row.

let str = "001|apple|red|002|banana|yellow|003|grapes|purple|004|this|is|a|test|one|005|so|is|this"
let pattern = "[0-9]{3}"
let result = str.split(usingRegex: pattern)

var all:[[String]] = []
for row in result {
    let split = row.split(separator: "|").map({ (substring) in
        return String(substring)
    })
    if split.count != 0 {
        all.append(split)
    }
}

dump(all)

I tested this in a playground and got the following result:

▿ 5 elements
  ▿ 3 elements
    - "001"
    - "apple"
    - "red"
  ▿ 3 elements
    - "002"
    - "banana"
    - "yellow"
  ▿ 3 elements
    - "003"
    - "grapes"
    - "purple"
  ▿ 6 elements
    - "004"
    - "this"
    - "is"
    - "a"
    - "test"
    - "one"
  ▿ 4 elements
    - "005"
    - "so"
    - "is"
    - "this"

If you decide you want to exclude the ID from the resulting arrays you can modify the extension return to the following:

return (0...matches.count).map { String(self[ranges[$0].upperBound..<ranges[$0+1].lowerBound]) }

This will switch the return range to use the upperBound instead of lowerBound

1 Comment

OP wants to have numbers inside one row and looks like have static number of items in one row as well
0

You can use String method enumerateSubstrings in range using byWords options, check if the string is an integer, if so append a new array with that string to the result otherwise append the word to the last result array:

let string = "001|apple|red|002|banana|yellow|003|grapes|purple"
var result: [[Substring]] = []
string.enumerateSubstrings(in: string.startIndex..., options: .byWords) { _, range, _, _ in
    let word = string[range]
    if let _ = Int(word) {
        result.append([word])
        // or just a new empty array
        // result.append([])
    } else {
        result[result.index(before: result.endIndex)].append(word)
    }
}

print(result)   // "[["001", "apple", "red"], ["002", "banana", "yellow"], ["003", "grapes", "purple"]]\n"

Comments

0

Basic way

let str = "001|apple|red|002|banana|yellow|003|grapes|purple"
let components = str.components(separatedBy: "|")
let chunkSize = 3
let loopCount = components.count/chunkSize

var packages: [Array<String>] = []
for index in  0..<loopCount {
    /// Starting index
    let numIndex = index * chunkSize

    /// Get the subarray of range
    let package = Array(components[numIndex..<(numIndex+chunkSize)])
    packages.append(package)
}

print(packages)

Output:

[["001", "apple", "red"], ["002", "banana", "yellow"], ["003", "grapes", "purple"]]

6 Comments

Instead use stride
Answer is so simple! Thanks
Actualy the real reponse has 100+ elements in one row, in example i just showed number,fruit,color.
Then replace 3 with 100 :)
I need to make 100+ variables according to this solution.
|
0

Okay You can do it recursively

let input = "001|apple|red|002|banana|yellow|003|grapes|purple"

let array = input.components(separatedBy: "|")


// Get Chunks from array
 extension Array {
      func getFirstElements(upTo position: Int) -> Array<Element> {
         let arraySlice = self[0 ..< position]
         return Array(arraySlice)
      }
  }

func recersivelyGetArray (array:[String], slice:inout [[String]]) {

  guard !array.isEmpty else{
    return
  }

  var copyArray = array

  var chunkSize = 3
  if  array.count >= 3 {
    chunkSize = 3
  } else {
    chunkSize = array.count
  }
  let threeElements =  copyArray.getFirstElements(upTo: chunkSize)
  slice.append(threeElements)

  copyArray.removeFirst(chunkSize)

  recersivelyGetArray(array: copyArray, slice: &slice)


}

var inoutResult = [[String]]()

recersivelyGetArray(array: array, slice: &inoutResult)

print(inoutResult) 

Output

Case 1 For Input

let input = "001|apple|red|002|banana|yellow|003|grapes|purple"

[["001", "apple", "red"], ["002", "banana", "yellow"], ["003", "grapes", "purple"]]

Case 2 For Input

let input = "001|apple|red|002|banana|yellow|003|grapes"

[["001", "apple", "red"], ["002", "banana", "yellow"], ["003", "grapes"]]

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.