Swift4 displaying Parser result in an NSOutlineView - swift4

I wrote a TLV parser that returns results like the following tag length value:
<e1> 53 <9f1e0831 36303231 343337ef…>
<9f1e> 8 <31363032 31343337>
<ef> 18 <df0d084d 3030302d 4d5049df 7f04312d 3232>
<df0d> 8 <4d303030 2d4d5049>
<df7f> 4 <312d3232>
<ef> 20 <df0d0b4d 3030302d 54455354 4f53df7f 03362d35>
<df0d> 11 <4d303030 2d544553 544f53>
<df7f> 3 <362d35>
I want to display this in an OutlineView, but I'm not familiar how the store object should look like and how to fill it up. Somehow it needs to be something like below:
class Node: NSObject {
var isConstructed = false
var tag = „Tag“
var length = 0
var value = „Value“
var children = [Node]()
weak var parent: Node?
override init() {
super.init()
}
init(tag: String) {
self.tag = tag
}
init(length: Int) {
self.length = length
}
init(value: String) {
self.value = value
}
init(isConstructed: Bool) {
self.isConstructed = isConstructed
}
func isLeaf() -> Bool {
return children.isEmpty
}
}
The TLV parser demo
TLVparser
Should look like this:
TLV parse result in NSOutlineView

Finally got some time to work on this question today. Putting this on an NSOutlineView is the easy part. The hard part is to define a data model for your TLV data and parse the Data into it.
TLVNode.swift
This class stores the TLV data. Your code looks like converted C and then it's not very good C either.
import Foundation
// Since we use Cocoa Bindings, all properties need to be
// dynamically dispatch hence #objCMembers declaration
// Its ability to handle erroneous data is not tested. That
// is left for the OP as an exercise.
#objcMembers class TLVNode: NSObject {
var tag: Data
var length: Int
var value: Data
var isConstructed: Bool
var children = [TLVNode]()
// Convert `tag` from Data to a string of hex for display
var displayTag: String {
// Pad the hex value with 0 so it outputs `0d` instead of just `d`
return tag.map { ("0" + String($0, radix: 16)).suffix(2) }.joined()
}
// Convert `value` from Data to string of hex
var displayValue: String {
let hexValues = value.map { ("0" + String($0, radix: 16)).suffix(2) }
var str = ""
for (index, hex) in hexValues.enumerated() {
if index > 0 && index % 4 == 0 {
str += " " + hex
} else {
str += hex
}
}
return str
}
convenience init?(dataStream: Data) {
var size = 0
self.init(dataStream: dataStream, offset: 0, size: &size)
}
static func create(from dataStream: Data) -> [TLVNode] {
var size = 0
var offset = 0
var nodes = [TLVNode]()
while let node = TLVNode(dataStream: dataStream, offset: offset, size: &size) {
nodes.append(node)
offset += size
}
return nodes
}
/// Intialize a TLVNode object from a data stream
///
/// - Parameters:
/// - dataStream: The serialized data stream, in TLV encoding
/// - offset: The location from which parsing of the data stream starts
/// - size: Upon return, the number of bytes that the node occupies
private init?(dataStream: Data, offset: Int, size: inout Int) {
// A node must have at least 3 bytes
guard offset < dataStream.count - 3 else { return nil }
// The number of bytes that `tag` occupies
let m = dataStream[offset] & 0x1F == 0x1F ?
2 + dataStream[(offset + 1)...].prefix(10).prefix(while: { $0 & 0x80 == 0x80 }).count : 1
// The number of bytes that `length` occupies
let n = dataStream[offset + m] & 0x80 == 0x80 ? Int(dataStream[offset + m] & 0x7f) : 1
guard n <= 3 else { return nil }
self.tag = Data(dataStream[offset ..< (offset + m)])
self.length = dataStream[(offset + m) ..< (offset + m + n)].map { Int($0) }.reduce(0) { result, element in result * 0x100 + element }
self.value = Data(dataStream[(offset + m + n) ..< (offset + m + n + length)])
self.isConstructed = dataStream[offset] & 0x20 == 0x20
size = m + n + length
if self.isConstructed {
var childOffset = 0
var childNodeSize = 0
while let childNode = TLVNode(dataStream: self.value, offset: childOffset, size: &childNodeSize) {
self.children.append(childNode)
childOffset += childNodeSize
}
}
}
private func generateDescription(indentation: Int) -> String {
return "\(String(repeating: " ", count: indentation))\(tag as NSData) \(length) \(value as NSData)\n"
+ children.map { $0.generateDescription(indentation: indentation + 4) }.joined()
}
// Use this property when you need to quickly dump something to the debug console
override var description: String {
return self.generateDescription(indentation: 0)
}
// A more detailed view on the properties of the current instance
// Does not include child nodes.
override var debugDescription: String {
return """
TAG = \(tag as NSData)
LENGTH = \(length)
VALUE = \(value as NSData)
CONSTRUCTED = \(isConstructed)
"""
}
}
View Controller
import Cocoa
class ViewController: NSViewController {
#objc var tlvNodes: [TLVNode]!
override func viewDidLoad() {
super.viewDidLoad()
let data = Data(bytes:
[ 0xe1,0x35,
0x9f,0x1e,0x08,0x31,0x36,0x30,0x32,0x31,0x34,0x33,0x37,
0xef,0x12,
0xdf,0x0d,0x08,0x4d,0x30,0x30,0x30,0x2d,0x4d,0x50,0x49,
0xdf,0x7f,0x04,0x31,0x2d,0x32,0x32,
0xef,0x14,
0xdf,0x0d,0x0b,0x4d,0x30,0x30,0x30,0x2d,0x54,0x45,0x53,0x54,0x4f,0x53,
0xdf,0x7f,0x03,0x36,0x2d,0x35,
// A repeat of the data above
0xe1,0x35,
0x9f,0x1e,0x08,0x31,0x36,0x30,0x32,0x31,0x34,0x33,0x37,
0xef,0x12,
0xdf,0x0d,0x08,0x4d,0x30,0x30,0x30,0x2d,0x4d,0x50,0x49,
0xdf,0x7f,0x04,0x31,0x2d,0x32,0x32,
0xef,0x14,
0xdf,0x0d,0x0b,0x4d,0x30,0x30,0x30,0x2d,0x54,0x45,0x53,0x54,0x4f,0x53,
0xdf,0x7f,0x03,0x36,0x2d,0x35
])
// The Tree Controller won't know when we assign `tlvNode` to
// an entirely new object. So we need to give it a notification
let nodes = TLVNode.create(from: data)
self.willChangeValue(forKey: "tlvNodes")
self.tlvNodes = nodes
self.didChangeValue(forKey: "tlvNodes")
}
}
Interface Builder Setup
We will use Cocoa Bindings since populating an Outline View manually can be quite tedious (see my other answer) and your example screenshot look like you are already heading in that direction. A word of caution: while Cocoa Binding is very convenient, it should be considered an advanced topic since it's rather hard to troubleshoot. On your storyboard:
From the Object library on the right, add a Tree Controller to your scene
Select the Tree Controller, in the Attributes inspector, set Children = children
Drag out an Outline View and configure it with 3 columns. We will name them Tag, Length and Value
Open the Bindings Inspector, for the 5 highlight objects, set their bindings as follow:
| IB Object | Property | Bind To | Controller Key | Model Key Path |
|-----------------|-------------------|-----------------|-----------------|--------------------------|
| Tree Controller | Controller Array | View Controller | | self.tlvNodes |
| Outline View | Content | Tree Controller | arrangedObjects | |
| Table View Cell | Value | Table Cell View | | objectValue.displayTag |
| (Tag column) | | | | |
| Table View Cell | Value | Table Cell View | | objectValue.length |
| (Length column) | | | | |
| Table View Cell | Value | Table Cell View | | objectValue.displayValue |
| (Value column) | | | | |
Result:

Related

What is the correct way in swift to wrap a segment of a UInt8 array as a String?

I have some raw data processing to do in an iPhone app. Strings always come out of an extremely large underlying byte array, so I want to be able to pull strings out of the array without triggering out of memory issues.
I can see a String(bytesNoCopy: ...) in the documentation, is this what I want, and how exactly is it supposed to be used?
Assuming an array of uint8 called data and index is a number which shows where the string is inside the array.
var myData:[UInt8] = [
4, // String 1 length
65,66,67,68,0, // String 1 data
4, // String 2 length
69,70,71,71,0 // String 2 data
]
var index = 0
let string1 = readString(&myData, &index)
let string2 = readString(&myData, &index)
print(string1, string2)
// Read a string located at a specific
// position in a byte array, and increment
// the pointer into the array into the next
// position
func readString(_ data:inout [UInt8], _ index:inout Int) -> String {
// Read string length out of data array
let l = Int(readUInt8(&data, &index))
// Read string out of data array without copy
let s = String(
bytesNoCopy: UnsafeMutableRawPointer(data + index), // <-- what goes here??
length: l,
encoding: .utf8,
freeWhenDone: false)
index = index + l
if s == nil {
return ""
}
return s!
}
// Read a byte as an integer from a
// data array, and increment the pointer into
// the data array to the next position.
func readUInt8(_ data:inout [UInt8], _ x:inout Int) -> UInt8 {
let v = data[x]
x = x + 1
return v
}
NOTE: This question is updated to include sample data, and renamed the variable x to index to make it clearer that the question was asking how to create a string from a segment of a byte array.
Here's how you can do try this -
import Foundation
func readString(_ data: inout [UInt8], _ x: inout Int) -> String {
let l = 4
var slice: ArraySlice<UInt8> = data[x..<x+l] // No copy, view into existing Array
x += l
return slice.withUnsafeBytes({ pointer in
// No copy, just making compiler happy (assumption that it is bound to UInt8 is correct
if let bytes = pointer.baseAddress?.assumingMemoryBound(to: UInt8.self) {
return String(
bytesNoCopy: UnsafeMutableRawPointer(mutating: bytes), // No copy
length: slice.count,
encoding: .utf8,
freeWhenDone: false
) ?? ""
} else {
return ""
}
})
}
Test
var a: [UInt8] = [
65, 66, 67, 68,
69, 70, 71, 72
]
var x = 0
let test1 = readString(&a, &x)
print("test1 : \(test1)")
// test1 : ABCD
let test2 = readString(&a, &x)
print("test2 : \(test2)")
// test2 : EFGH

Swift function not working inside another function

I'm new on this site but I've been struggling for several days about this issue I found. I wrote this code in order to solve a challenge of the site Codewars; the challenge consists in calculate the mean and the variance from some data about some fictional rainfalls (I attach the complete page on the bottom). In order to end this challenge I created a function to convert the data from this useless string into an array of Doubles. The weird thing is that the function if called outside the main one works properly but inside returns an empty array. I have no idea why is happening this. Thank you very much for every effort you'll put trying to explain me this.
This is the first part of the Codewars page that explain the callenge
This is the second one
//
// main.swift
// Prova
//
// Created by Lorenzo Santini on 13/06/18.
// Copyright © 2018 Lorenzo Santini. All rights reserved.
//
import Foundation
func mean(_ d: String,_ town: String) -> Double {
let arrayOfValues = obtainArrayOfMeasures(d, town)
var sum: Double = 0
for element in arrayOfValues {
sum += element
}
return sum / Double(arrayOfValues.count)
}
func variance(_ d: String,_ town: String) -> Double {
let meanValue: Double = mean(d, town)
//Here is the problem: when this function is called instead of returning the array containg all the measures for the selected city it returns an empty array
var arrayOfValues = obtainArrayOfMeasures(d, town)
var sum: Double = 0
for element in arrayOfValues {
sum += pow((element - meanValue), 2)
}
return sum / Double(arrayOfValues.count)
}
func isInt(_ char: Character) -> Bool {
switch char {
case "1","2","3","4","5","6","7","8","9":
return true
default:
return false
}
}
func obtainArrayOfMeasures(_ d: String,_ town: String) -> [Double]{
//The first array stores the Data string divided for city
var arrayOfString: [String] = []
//The second array stores the measures of rainfall of the town passed as argument for the function
var arrayOfMeasures: [Double] = []
//Split the d variable containg the data string in separated strings for each town and add it to the arrayOfString array
repeat {
let finalIndex = (data.index(of:"\n")) ?? data.endIndex
arrayOfString.append(String(data[data.startIndex..<finalIndex]))
let finalIndexToRemove = (data.endIndex == finalIndex) ? finalIndex : data.index(finalIndex, offsetBy: 1)
data.removeSubrange(data.startIndex..<finalIndexToRemove)
} while data.count != 0
//Find the string of the town passed as argument
var stringContainingTown: String? = nil
for string in arrayOfString {
if string.contains(town) {stringContainingTown = string; print("true")}
}
if stringContainingTown != nil {
var stringNumber = ""
var index = 0
//Add to arrayOfMeasures the measures of the selected town
for char in stringContainingTown! {
index += 1
if isInt(char) || char == "." {
stringNumber += String(char)
print(stringNumber)
}
if char == "," || index == stringContainingTown!.count {
arrayOfMeasures.append((stringNumber as NSString).doubleValue)
stringNumber = ""
}
}
}
return arrayOfMeasures
}
var data = "Rome:Jan 81.2,Feb 63.2,Mar 70.3,Apr 55.7,May 53.0,Jun 36.4,Jul 17.5,Aug 27.5,Sep 60.9,Oct 117.7,Nov 111.0,Dec 97.9" + "\n" +
"London:Jan 48.0,Feb 38.9,Mar 39.9,Apr 42.2,May 47.3,Jun 52.1,Jul 59.5,Aug 57.2,Sep 55.4,Oct 62.0,Nov 59.0,Dec 52.9" + "\n" +
"Paris:Jan 182.3,Feb 120.6,Mar 158.1,Apr 204.9,May 323.1,Jun 300.5,Jul 236.8,Aug 192.9,Sep 66.3,Oct 63.3,Nov 83.2,Dec 154.7" + "\n" +
"NY:Jan 108.7,Feb 101.8,Mar 131.9,Apr 93.5,May 98.8,Jun 93.6,Jul 102.2,Aug 131.8,Sep 92.0,Oct 82.3,Nov 107.8,Dec 94.2" + "\n" +
"Vancouver:Jan 145.7,Feb 121.4,Mar 102.3,Apr 69.2,May 55.8,Jun 47.1,Jul 31.3,Aug 37.0,Sep 59.6,Oct 116.3,Nov 154.6,Dec 171.5" + "\n" +
"Sydney:Jan 103.4,Feb 111.0,Mar 131.3,Apr 129.7,May 123.0,Jun 129.2,Jul 102.8,Aug 80.3,Sep 69.3,Oct 82.6,Nov 81.4,Dec 78.2" + "\n" +
"Bangkok:Jan 10.6,Feb 28.2,Mar 30.7,Apr 71.8,May 189.4,Jun 151.7,Jul 158.2,Aug 187.0,Sep 319.9,Oct 230.8,Nov 57.3,Dec 9.4" + "\n" +
"Tokyo:Jan 49.9,Feb 71.5,Mar 106.4,Apr 129.2,May 144.0,Jun 176.0,Jul 135.6,Aug 148.5,Sep 216.4,Oct 194.1,Nov 95.6,Dec 54.4" + "\n" +
"Beijing:Jan 3.9,Feb 4.7,Mar 8.2,Apr 18.4,May 33.0,Jun 78.1,Jul 224.3,Aug 170.0,Sep 58.4,Oct 18.0,Nov 9.3,Dec 2.7" + "\n" +
"Lima:Jan 1.2,Feb 0.9,Mar 0.7,Apr 0.4,May 0.6,Jun 1.8,Jul 4.4,Aug 3.1,Sep 3.3,Oct 1.7,Nov 0.5,Dec 0.7"
var prova = variance(data, "London")
The problem is that func obtainArrayOfMeasures modifies the global data
variable. When called the second time, data is an empty string.
An indicator for this problem is also that making the global data variable constant
let data = "Rome:..."
causes a compiler error at
data.removeSubrange(data.startIndex..<finalIndexToRemove)
// Cannot use mutating member on immutable value: 'data' is a 'let' constant
An immediate fix would be to operate on a local mutable copy:
func obtainArrayOfMeasures(_ d: String,_ town: String) -> [Double]{
var data = d
// ...
}
Note however that the function can be simplified to
func obtainArrayOfMeasures(_ d: String,_ town: String) -> [Double] {
let lines = d.components(separatedBy: .newlines)
guard let line = lines.first(where: { $0.hasPrefix(town)}) else {
return [] // No matching line found.
}
let entries = line.components(separatedBy: ",")
let numbers = entries.compactMap { Double($0.filter {".0123456789".contains($0) })}
return numbers
}
without mutating any values. You might also consider to return nil
or abort with fatalError() if no matching entry is found.

Finding The First Non-repeating Character algorithm Swift 4 (Looping over string only once)

I am trying to solve code fights interview practice questions, but I am stuck on how to solve this particular problem in swift. My first thought was to use a dictionary with the counts of each character, but then I would have to iterate over the string again to compare, so that doesn't work per the restrictions. Any help would be good. Thank you. Here is the problem and requirements:
Note: Write a solution that only iterates over the string once and uses O(1) additional memory, since this is what you would be asked to do during a real interview.
Given a string s, find and return the first instance of a non-repeating character in it. If there is no such character, return '_'
Here is the code I started with (borrowed from another post)
func firstNotRepeatingCharacter(s: String) -> Character {
var countHash:[Character:Int] = [:]
for character in s {
countHash[character] = (countHash[character] ?? 0) + 1
}
let nonRepeatingCharacters = s.filter({countHash[$0] == 1})
let firstNonRepeatingCharacter = nonRepeatingCharacters.first!
return firstNonRepeatingCharacter
}
firstNotRepeatingCharacter(s:"abacabad")
You can create a dictionary to store the occurrences and use first(where:) method to return the first occurrence that happens only once:
Swift 4
func firstNotRepeatingCharacter(s: String) -> Character {
var occurrences: [Character: Int] = [:]
s.forEach{ occurrences[$0, default: 0] += 1 }
return s.first{ occurrences[$0] == 1 } ?? "_"
}
Swift 3
func firstNotRepeatingCharacter(s: String) -> Character {
var occurrences: [Character:Int] = [:]
s.characters.forEach{ occurrences[$0] = (occurrences[$0] ?? 0) + 1}
return s.characters.first{ occurrences[$0] == 1 } ?? "_"
}
Another option iterating the string in reversed order and using an array of 26 elements to store the characters occurrences
func firstNotRepeatingCharacter(s: String) -> Character {
var chars = Array(repeating: 0, count: 26)
var characters: [Character] = []
var charIndex = 0
var strIndex = 0
s.characters.reversed().forEach {
let index = Int(String($0).unicodeScalars.first!.value) - 97
chars[index] += 1
if chars[index] == 1 && strIndex >= charIndex {
characters.append($0)
charIndex = strIndex
}
strIndex += 1
}
return characters.reversed().first { chars[Int(String($0).unicodeScalars.first!.value) - 97] == 1 } ?? "_"
}
Use a dictionary to store the character counts as well as where they were first encountered. Then, loop over the dictionary (which is constant in size since there are only so many unique characters in the input string, thus also takes constant time to iterate) and find the earliest occurring character with a count of 1.
func firstUniqueCharacter(in s: String) -> Character
{
var characters = [Character: (count: Int, firstIndex: Int)]()
for (i, c) in s.characters.enumerated()
{
if let t = characters[c]
{
characters[c] = (t.count + 1, t.firstIndex)
}
else
{
characters[c] = (1, i)
}
}
var firstUnique = (character: Character("_"), index: Int.max)
for (k, v) in characters
{
if v.count == 1 && v.firstIndex <= firstUnique.index
{
firstUnique = (k, v.firstIndex)
}
}
return firstUnique.character
}
Swift
Use dictionary, uniqueCharacter optional variable with unique characters array to store all uniquely present characters in the string , every time duplication of characters found should delete that character from unique characters array and same time it is the most first character then should update the dictionary with its count incremented , refer following snippet , how end of the iteration through all characters gives a FIRST NON REPEATED CHARACTER in given String. Refer following code to understand it properly
func findFirstNonRepeatingCharacter(string:String) -> Character?{
var uniqueChars:[Character] = []
var uniqueChar:Character?
var chars = string.lowercased().characters
var charWithCount:[Character:Int] = [:]
for char in chars{
if let count = charWithCount[char] { //amazon
charWithCount[char] = count+1
if char == uniqueChar{
uniqueChars.removeFirst()
uniqueChar = uniqueChars.first
}
}else{
charWithCount[char] = 1
uniqueChars.append(char)
if uniqueChar == nil{
uniqueChar = char
}
}
}
return uniqueChar
}
// Use
findFirstNonRepeatingCharacter(string: "eabcdee")

How to “draw” a Binary Tree in Console?

How can I print a binary tree in Swift so that the input 79561 prints output like this:
7
/ \
5 9
/ \
1 6
I tried to arrange this with some code using For Loops and If Statements but it didn't worked.
My code is:
import UIKit
//Variable "node" used only to arrange it in output.
var node = "0"
var space = " "
var linkLeft = "/"
var linkRight = "\\"
var str = "Hello, playground"
var height = 6
var width = height * 2 + 1
print()
//Height
for h in 1...height {
//Width
for w in 1...width {
switch h {
case 1:
if(w == width/2 + h) {
print(node, terminator: "")
} else {
print(space, terminator: "")
}
if (w == width) {
print()
}
case 2:
//print(linkLeft, terminator: "")
if(w == width/3 + h) {
print(linkLeft, terminator: "")
} else if(w == width/3 + h + 4) {
print(linkRight, terminator: "")
} else {
print(space, terminator: "")
}
if (w == width) {
print()
}
case 3:
if(w == width/5 + h) {
print(node, terminator: "")
} else if(w == width/h + h){
print(node, terminator: "")
} else {
print(space, terminator: "")
}
if (w == width) {
print()
}
break
default:
break
}
}
}
I tried to use two For Loops one for height and other one for width. But it's not working if number of nodes changes. For now I just tried to arrange places of links (/ and \), nodes and spaces, so it's not working.
Is there a possible way to do this ?
First you have to define a hierarchical tree structure (class) that allows recursive traversal of the tree nodes. How you implement it doesn't matter as long as it can provide a descriptive string and access to its left and right sub nodes.
For example (I used this for testing purposes):
class TreeNode
{
var value : Int
var left : TreeNode? = nil
var right : TreeNode? = nil
init(_ rootValue:Int)
{ value = rootValue }
#discardableResult
func addValue( _ newValue:Int) -> TreeNode
{
if newValue == value // exclude duplicate entries
{ return self }
else if newValue < value
{
if let newNode = left?.addValue(newValue)
{ return newNode }
left = TreeNode(newValue)
return left!
}
else
{
if let newNode = right?.addValue(newValue)
{ return newNode }
right = TreeNode(newValue)
return right!
}
}
}
Then you can create a recursive function to obtain the lines to print. Each line will need to be aware of lower level lines so the list of lines needs to be built from the bottom up. Recursion is an easy way to achieve this kind of interdependency.
Here's an example of a generic function that will work for any binary tree class. It expects a root node and a function (or closure) to access a node's description and left/right sub nodes :
public func treeString<T>(_ node:T, reversed:Bool=false, isTop:Bool=true, using nodeInfo:(T)->(String,T?,T?)) -> String
{
// node value string and sub nodes
let (stringValue, leftNode, rightNode) = nodeInfo(node)
let stringValueWidth = stringValue.count
// recurse to sub nodes to obtain line blocks on left and right
let leftTextBlock = leftNode == nil ? []
: treeString(leftNode!,reversed:reversed,isTop:false,using:nodeInfo)
.components(separatedBy:"\n")
let rightTextBlock = rightNode == nil ? []
: treeString(rightNode!,reversed:reversed,isTop:false,using:nodeInfo)
.components(separatedBy:"\n")
// count common and maximum number of sub node lines
let commonLines = min(leftTextBlock.count,rightTextBlock.count)
let subLevelLines = max(rightTextBlock.count,leftTextBlock.count)
// extend lines on shallower side to get same number of lines on both sides
let leftSubLines = leftTextBlock
+ Array(repeating:"", count: subLevelLines-leftTextBlock.count)
let rightSubLines = rightTextBlock
+ Array(repeating:"", count: subLevelLines-rightTextBlock.count)
// compute location of value or link bar for all left and right sub nodes
// * left node's value ends at line's width
// * right node's value starts after initial spaces
let leftLineWidths = leftSubLines.map{$0.count}
let rightLineIndents = rightSubLines.map{$0.prefix{$0==" "}.count }
// top line value locations, will be used to determine position of current node & link bars
let firstLeftWidth = leftLineWidths.first ?? 0
let firstRightIndent = rightLineIndents.first ?? 0
// width of sub node link under node value (i.e. with slashes if any)
// aims to center link bars under the value if value is wide enough
//
// ValueLine: v vv vvvvvv vvvvv
// LinkLine: / \ / \ / \ / \
//
let linkSpacing = min(stringValueWidth, 2 - stringValueWidth % 2)
let leftLinkBar = leftNode == nil ? 0 : 1
let rightLinkBar = rightNode == nil ? 0 : 1
let minLinkWidth = leftLinkBar + linkSpacing + rightLinkBar
let valueOffset = (stringValueWidth - linkSpacing) / 2
// find optimal position for right side top node
// * must allow room for link bars above and between left and right top nodes
// * must not overlap lower level nodes on any given line (allow gap of minSpacing)
// * can be offset to the left if lower subNodes of right node
// have no overlap with subNodes of left node
let minSpacing = 2
let rightNodePosition = zip(leftLineWidths,rightLineIndents[0..<commonLines])
.reduce(firstLeftWidth + minLinkWidth)
{ max($0, $1.0 + minSpacing + firstRightIndent - $1.1) }
// extend basic link bars (slashes) with underlines to reach left and right
// top nodes.
//
// vvvvv
// __/ \__
// L R
//
let linkExtraWidth = max(0, rightNodePosition - firstLeftWidth - minLinkWidth )
let rightLinkExtra = linkExtraWidth / 2
let leftLinkExtra = linkExtraWidth - rightLinkExtra
// build value line taking into account left indent and link bar extension (on left side)
let valueIndent = max(0, firstLeftWidth + leftLinkExtra + leftLinkBar - valueOffset)
let valueLine = String(repeating:" ", count:max(0,valueIndent))
+ stringValue
let slash = reversed ? "\\" : "/"
let backSlash = reversed ? "/" : "\\"
let uLine = reversed ? "¯" : "_"
// build left side of link line
let leftLink = leftNode == nil ? ""
: String(repeating: " ", count:firstLeftWidth)
+ String(repeating: uLine, count:leftLinkExtra)
+ slash
// build right side of link line (includes blank spaces under top node value)
let rightLinkOffset = linkSpacing + valueOffset * (1 - leftLinkBar)
let rightLink = rightNode == nil ? ""
: String(repeating: " ", count:rightLinkOffset)
+ backSlash
+ String(repeating: uLine, count:rightLinkExtra)
// full link line (will be empty if there are no sub nodes)
let linkLine = leftLink + rightLink
// will need to offset left side lines if right side sub nodes extend beyond left margin
// can happen if left subtree is shorter (in height) than right side subtree
let leftIndentWidth = max(0,firstRightIndent - rightNodePosition)
let leftIndent = String(repeating:" ", count:leftIndentWidth)
let indentedLeftLines = leftSubLines.map{ $0.isEmpty ? $0 : (leftIndent + $0) }
// compute distance between left and right sublines based on their value position
// can be negative if leading spaces need to be removed from right side
let mergeOffsets = indentedLeftLines
.map{$0.count}
.map{leftIndentWidth + rightNodePosition - firstRightIndent - $0 }
.enumerated()
.map{ rightSubLines[$0].isEmpty ? 0 : $1 }
// combine left and right lines using computed offsets
// * indented left sub lines
// * spaces between left and right lines
// * right sub line with extra leading blanks removed.
let mergedSubLines = zip(mergeOffsets.enumerated(),indentedLeftLines)
.map{ ( $0.0, $0.1, $1 + String(repeating:" ", count:max(0,$0.1)) ) }
.map{ $2 + String(rightSubLines[$0].dropFirst(max(0,-$1))) }
// Assemble final result combining
// * node value string
// * link line (if any)
// * merged lines from left and right sub trees (if any)
let treeLines = [leftIndent + valueLine]
+ (linkLine.isEmpty ? [] : [leftIndent + linkLine])
+ mergedSubLines
return (reversed && isTop ? treeLines.reversed(): treeLines)
.joined(separator:"\n")
}
To actually print, you'll need to supply the function with your class's node and a closure to access node descriptions and the left and right sub nodes.
extension TreeNode
{
var asString:String { return treeString(self){("\($0.value)",$0.left,$0.right)} }
}
var root = TreeNode(7)
root.addValue(9)
root.addValue(5)
root.addValue(6)
root.addValue(1)
print(root.asString)
// 7
// / \
// 5 9
// / \
// 1 6
//
root = TreeNode(80)
root.addValue(50)
root.addValue(90)
root.addValue(10)
root.addValue(60)
root.addValue(30)
root.addValue(70)
root.addValue(55)
root.addValue(5)
root.addValue(35)
root.addValue(85)
print(root.asString)
// 80
// ___/ \___
// 50 90
// __/ \__ /
// 10 60 85
// / \ / \
// 5 30 55 70
// \
// 35
//
[EDIT] Improved logic to use less space on trees with deeper right side than left. Cleaned up code and added comments to explain how it works.
//
// 12
// / \
// 10 50
// / __/ \__
// 5 30 90
// \ /
// 35 70
// / \
// 60 85
// /
// 55
//
// 12
// / \
// 10 30
// / \
// 5 90
// /
// 85
// /
// 70
// /
// 55
// /
// 48
// /
// 45
// /
// 40
// /
// 35
//
[EDIT2] made the function generic and adapted explanations.
With the generic function, the data doesn't even need to be in an actual tree structure.
For example, you could print an array containing a heap tree:
extension Array
{
func printHeapTree(reversed:Bool = false)
{
let tree = treeString( 0, reversed:reversed )
{
let left = { $0 < self.count ? $0 : nil}($0 * 2 + 1)
let right = { $0 < self.count ? $0 : nil}($0 * 2 + 2)
return ( "\(self[$0])", left, right )
}
print(tree)
}
}
let values = [7,5,9,1,6]
values.printHeapTree()
// 7
// / \
// 5 9
// / \
// 1 6
let family = [ "Me","Paul","Rosa","Vincent","Jody","John","Kate"]
family.printHeapTree()
// Me
// ___/ \___
// Paul Rosa
// / \ / \
// Vincent Jody John Kate
But for that last example, a family tree is usually presented upside down. So, I adjusted the function to allow printing a reversed tree:
family.printHeapTree(reversed:true)
// Vincent Jody John Kate
// \ / \ /
// Paul Rosa
// ¯¯¯\ /¯¯¯
// Me
[EDIT3] Added condition to exclude duplicate entries from tree in example class (TreeNode) per Emm's request
[EDIT4] changed formula for mergedSubLines so that it will compile in an actual project (was testing this in the playground).
[EDIT5] minor adjustments for Swift4, added ability to print a reversed tree, changed Array example to a heap tree.
My easy way that I found and modified a bit for swift
import UIKit
// Binary tree by class
class TreeNode
{
var value : Int
var left : TreeNode? = nil
var right : TreeNode? = nil
init(_ rootValue:Int) {
value = rootValue
}
#discardableResult
func addValue( _ newValue:Int) -> TreeNode
{
if newValue == value {
return self
}
else if newValue < value {
if let newNode = left?.addValue(newValue) {
return newNode
}
left = TreeNode(newValue)
return left!
}
else {
if let newNode = right?.addValue(newValue) {
return newNode
}
right = TreeNode(newValue)
return right!
}
}
func myPrint () {
printTree(prefix: "", n: self, isLeft: false)
}
}
func printTree(prefix: String, n: TreeNode, isLeft: Bool) {
print(prefix, (isLeft ? "|-- " : "\\-- "), n.value)
if n.left != nil {
printTree(prefix: "\(prefix) \(isLeft ? "| " : " ") ", n: n.left!, isLeft: true)
}
if n.right != nil {
printTree(prefix: "\(prefix) \(isLeft ? "| " : " ") ", n: n.right!, isLeft: false)
}
}
Input
var root = TreeNode(80)
root.addValue(50)
root.addValue(90)
root.addValue(10)
root.addValue(60)
root.addValue(30)
root.addValue(70)
root.addValue(55)
root.addValue(5)
root.addValue(35)
root.addValue(85)
root.addValue(84)
root.addValue(86)
root.addValue(92)
root.addValue(100)
root.myPrint()
Output
\-- 80
|-- 50
| |-- 10
| | |-- 5
| | \-- 30
| | \-- 35
| \-- 60
| |-- 55
| \-- 70
\-- 90
|-- 85
| |-- 84
| \-- 86
\-- 92
\-- 100

How to increment String in Swift

I need to save files in an alphabetical order.
Now my code is saving files in numeric order
1.png
2.png
3.png ...
The problem is when i read this files again I read this files as described here
So I was thinking of changing the code and to save the files not in a numeric order but in an alphabetical order as:
a.png b.png c.png ... z.png aa.png ab.png ...
But in Swift it's difficult to increment even Character type.
How can I start from:
var s: String = "a"
and increment s in that way?
You can keep it numeric, just use the right option when sorting:
let arr = ["1.png", "19.png", "2.png", "10.png"]
let result = arr.sort {
$0.compare($1, options: .NumericSearch) == .OrderedAscending
}
// result: ["1.png", "2.png", "10.png", "19.png"]
If you'd really like to make them alphabetical, try this code to increment the names:
/// Increments a single `UInt32` scalar value
func incrementScalarValue(_ scalarValue: UInt32) -> String {
return String(Character(UnicodeScalar(scalarValue + 1)))
}
/// Recursive function that increments a name
func incrementName(_ name: String) -> String {
var previousName = name
if let lastScalar = previousName.unicodeScalars.last {
let lastChar = previousName.remove(at: previousName.index(before: previousName.endIndex))
if lastChar == "z" {
let newName = incrementName(previousName) + "a"
return newName
} else {
let incrementedChar = incrementScalarValue(lastScalar.value)
return previousName + incrementedChar
}
} else {
return "a"
}
}
var fileNames = ["a.png"]
for _ in 1...77 {
// Strip off ".png" from the file name
let previousFileName = fileNames.last!.components(separatedBy: ".png")[0]
// Increment the name
let incremented = incrementName(previousFileName)
// Append it to the array with ".png" added again
fileNames.append(incremented + ".png")
}
print(fileNames)
// Prints `["a.png", "b.png", "c.png", "d.png", "e.png", "f.png", "g.png", "h.png", "i.png", "j.png", "k.png", "l.png", "m.png", "n.png", "o.png", "p.png", "q.png", "r.png", "s.png", "t.png", "u.png", "v.png", "w.png", "x.png", "y.png", "z.png", "aa.png", "ab.png", "ac.png", "ad.png", "ae.png", "af.png", "ag.png", "ah.png", "ai.png", "aj.png", "ak.png", "al.png", "am.png", "an.png", "ao.png", "ap.png", "aq.png", "ar.png", "as.png", "at.png", "au.png", "av.png", "aw.png", "ax.png", "ay.png", "az.png", "ba.png", "bb.png", "bc.png", "bd.png", "be.png", "bf.png", "bg.png", "bh.png", "bi.png", "bj.png", "bk.png", "bl.png", "bm.png", "bn.png", "bo.png", "bp.png", "bq.png", "br.png", "bs.png", "bt.png", "bu.png", "bv.png", "bw.png", "bx.png", "by.png", "bz.png"]`
You will eventually end up with
a.png
b.png
c.png
...
z.png
aa.png
ab.png
...
zz.png
aaa.png
aab.png
...
Paste this code in the playground and check result. n numbers supported means you can enter any high number such as 99999999999999 enjoy!
you can uncomment for loop code to check code is working fine or not
but don't forget to assign a lesser value to counter variable otherwise Xcode will freeze.
var fileName:String = ""
var counter = 0.0
var alphabets = ["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z"]
let totalAlphaBets = Double(alphabets.count)
let numFiles = 9999
func getCharacter(counter c:Double) -> String {
var chars:String
var divisionResult = Int(c / totalAlphaBets)
let modResult = Int(c.truncatingRemainder(dividingBy: totalAlphaBets))
chars = getCharFromArr(index: modResult)
if(divisionResult != 0){
divisionResult -= 1
if(divisionResult > alphabets.count-1){
chars = getCharacter(counter: Double(divisionResult)) + chars
}else{
chars = getCharFromArr(index: divisionResult) + chars
}
}
return chars
}
func getCharFromArr(index i:Int) -> String {
if(i < alphabets.count){
return alphabets[i]
}else{
print("wrong index")
return ""
}
}
for _ in 0...numFiles {
fileName = getCharacter(counter: counter)+".png"
print(fileName)
counter += 1
}
fileName = getCharacter(counter: Double(numFiles))+".png"
print(fileName)