• Swift算法小程序

    2017-10-22 19:15:27
    Swift算法小程序 编写一个程序,能交换两个变量的数值 例如: 变量a值为20,变量b值为30,调用函数后,a的值变为30,b 的值变为20 答案: -func swap(a: inout Int , b:inout Int){ let temp = a a = b b = ...

    (1) 编写一个程序,能交换两个变量的数值
    例如: 变量a值为20,变量b值为30,调用函数后,a的值变为30,b 的值变为20

    答案:   -func swap(a: inout Int , b:inout Int){
                let temp = a
                 a = b
                b = temp
            }
            var x = 20 , y = 30
            swap(a:&x , b:&y)
            print(x,y)
    

    (2) 编写一个程序,求1! + 2! + 3! + 4!的和
    要求:使用嵌套定义函数实现

    答案: func getSum(number: Int) -> Int {
        //求某个数阶乘的结果
        func getFactorIal(num: Int) -> Int {
            var sum = 1
            for _ in 1...num {
                sum += 1
            }
            return sum
        }
        var total = 0
        for item in 1...number {
            total += getFactorIal(num: item)
        }
        return total
    }
    print(getSum(number: 3))
    

    (3) 古典问题:有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子长到第三个月后每个月又生一对兔子,假如兔子都不死,问每个月的兔子总数为多少?

    答案:func getFinboNum(num: Int) ->Int{
                if num == 1 || num == 2 {
                 return 1
                }
            return (getFinboNum(num: num - 1) + getFinboNum(num: num - 2))
    }
    for month in 1...10{
        print("\(month):\(getFinboNum(num: month))")
    }

    (4) 打印出所有的“水仙花数”,所谓“水仙花数”是指一个三位数,其各位数字立方和等于该数本身。例如:153是一个“水仙花数”,因为153=1的三次方+5的三次方+3的三次方

    答案 :for number in 100...999{
        var hunder = number / 100
        var ten = number / 10 % 10
        var num = number % 10
       let sum = pow(Decimal(hunder), 3) + pow(Decimal(ten), 3) +  pow(Decimal(num), 3)
        if sum == Decimal(number){
            print(number)
        }
    }   

    (5) 编写一个程序,要求可以把一个字符串中的每一个字符,如果是大
    写字母变小写字母,如果是小写字母变大写,如果是其他字符不变
    例如: 字符串China23beiJing 经过程序转换为cHINA23BEIjING

    答案:
    /*
     函数,将大写字母变小写,小写字母变大写,其他不变
     */
    func changeCharcter (chNum:Character) -> Character {
        //将字符转成整数
    
        var chStr = String(chNum)   //将字符转成字符串
        var num:UInt32 = 0  //用于接受字符整数值的变量
        for item in chStr.unicodeScalars {
            num = item.value    //循环只执行一次,获取字符的整数的值
    
        }
        /*
         如果是大小写字母,转换数值
         */
        //如果是大写字母
        if num >= 65 && num <= 90 {
            num += 32
        }
            //如果是小写字母
        else if num >= 97 && num <= 122 {
            num -= 32
        }
        /*
         将整数转换为字符
         */
        let newChNum = Character(UnicodeScalar(num)!)
        return newChNum
    
    }
    var str = "China23beiJing "
    var i = 0   //表示偏移量(循环变量初始值)
    while i < str.characters.count {    //循环条件,包含循环变量的终止值
        var str1 = str[str.index(str.startIndex, offsetBy: i)]
    //    str1 = changeCharcter(chNum: str1)
    
        str.replaceSubrange(str.index(str.startIndex, offsetBy:
            i)...str.index(str.startIndex, offsetBy: i),
                               with: String(changeCharcter(chNum: str1)))
        i+=1;   //循环变量值变化
    }
    print(str)
    

    (6) 编写一个程序,要求接收一个数字,程序会将这个数字以二进制方
    式打印,例如:数字10 , 以1010的方式打印出来

    答案 :
        func binaryPrintIntNumber(num : Int) {
    
        var remainderArr:[Int] = []  //int数组,存储余数
        var quotient:Int = num  //表示商的变量,初始值是num
        while quotient > 0 {   //商的值是0
            let remainderNum = quotient % 2 //获取余数的值
            remainderArr.insert(remainderNum, at: 0)    //插入数组
            quotient /= 2   //商变换
        }
        for item in remainderArr {
            print(item, terminator: "")
        }
        print("")
    }
    
    var a = 10
    binaryPrintIntNumber(num: a)
    print(a)
    

    (7) 编写一个程序,判断101-200之间有多少个素数,并输出所有素数

    答案:
        var isPreimNum = true   //判断是否是素数,是就是true不是就是false
    var sum = 0
    for item in 101...200 {     //遍历101到200中的任意数
        for j in 2..<item {     //判断item是不是素数
            if item % j == 0 {
                isPreimNum = false
                break
            }
        }
        if isPreimNum {     //如果是素数
            print(item)     //打印这个素数
            sum += 1
        }
        isPreimNum = true
    
    }
    print(sum)}
    
    

    (8) 编写一个程序,查看1、2、3、4四个数字,能组成多少个互不相同且无重复数字的三位数?都是多少?

    var sum1 = 0  //定义变量用于记录个数
    
    for hudder in 1...4 {   //百位
        for ten in 1...4 {  //十位
            for num in 1...4 {  //个位
                if hudder != ten && hudder != num && ten != num {
                    print(hudder*100 + ten*10 + num)
                    sum1 += 1   //计算个数
                }
            }
        }
    }
    
    print(sum1)
    展开全文
  • 本文讲的是实现二叉树以及二叉树遍历数据结构, Swift 算法俱乐部是一个致力于使用 Swift 来实现数据结构和算法的一个开源项目。 每个月,我和 Chris Pilcher 会在俱乐部网站上开建一个教程,来实现一个炫酷的...
    本文讲的是实现二叉树以及二叉树遍历数据结构,

    Swift 算法俱乐部 是一个致力于使用 Swift 来实现数据结构和算法的一个开源项目。

    每个月,我和 Chris Pilcher 会在俱乐部网站上开建一个教程,来实现一个炫酷的数据结构或者算法。如果你想要去学习更多关于算法和数据结构的知识,请跟随我们的脚步吧。

    在这个教程里面,你将学习到关于二叉树和二叉搜索树的知识。二叉树的实现首先是由 Matthijs Hollemans 实现的,而二叉搜索树是由 Nico Ameghino 实现的。

    提示: 你是 Swift 算法俱乐部的新成员吗?如果是的话,来看看我们的 指引文章 吧。

    开始

    在计算机科学中,二叉树 是一种最普遍的数据结构。更先进的像 红黑树 和 AVL 树 都是从二叉树中演进过来的。

    二叉树自身则是从最通用的树演变过来的。如果你不知道那是什么,来看一下上个月关于 Swift 树的数据结构 的文章吧。

    让我们来看一下这是如何工作的。

    二叉树数据结构

    二叉树是一颗每个结点都有 0,1 或者 2 个子树的树。最重要的一点是子树的数量最多为 2 - 这也是为什么它是二叉树的原因。

    这里我们来看一下二叉树是什么样子的:

    BinaryTree

    术语

    在我们深入研究代码之前,首先去了解一些重要的术语也是很重要的。

    在上面提到的通用树的基础上,二叉树增加了左右子树的概念。

    左子树

     子树从左边开始延伸:

    BinaryTree-2

    右子树

    令人惊讶的是,右边是  子树:

    BinaryTree-2

    叶结点

    如果一个结点没有任何子树,就被称为叶结点:

    BinaryTree-2

     是一棵树的最顶端的结点(程序员喜欢倒立的树):

    BinaryTree-2

    用 Swift 实现的二叉树

    就像其它树一样,一颗二叉树由结点组成。代表一个结点的方法就是使用一个类(暂时不要进入 Playground,这只是一个例子):

    class Node<T> {
      var value: T
      var leftChild: Node?
      var rightChild: Node?
    
      init(value: T) {
        self.value = value
      }
    }
    

    在一颗二叉树里面,每个结点存储着一些数据(),而且左右边都有子树(左子树 和 右子树)。 在这种实现方式里,左子树 和 右子树 是可选的,意味着它们可以为 nil

    那是一种传统的构建树的方式,然而,作为一个寻求刺激的人,你应该觉得开心了,因为我们今天将要尝试一些新的东西!:]

    语义值

    Swift 的一个核心创意就是直接使用类型值(例如 struct(结构) 和 enum(枚举))而不是在合适的地方使用引用类型(例如 class(类))。好吧,创建一棵树就是一个完美的使用类型值的例子 - 所以在这个教程里面,你将实现二叉树作为枚举类型。

    创建一个新的 Swift playground(这个教程使用 Xcode 8 beta 5)然后加上下面的枚举声明:

    enum BinaryTree<T> {
    
    }
    

    你已经声明了一个名为 BinaryTree(二叉树) 的枚举。`` 语法声明了这是一个 通用 的且允许推断调用站点类型信息的枚举。

    声明

    枚举的声明是很严格的,所以只能是唯一声明。幸运的是,这非常符合二叉树的概念。二叉树是一个有限结点的集合,这些结点或者为空,或者是由一个值和一个指向其他结点的指针所构成。

    相应的更新你的枚举:

    enum BinaryTree<T> {
      case empty
      case node(BinaryTree, T, BinaryTree)
    }
    

    如果你有其他编程语言的编程经验,这个 node(结点)的例子可能相比起来有点不同。Swift 的枚举允许 associated values(相关的值),这是一个比较奇特的术语,意味着你可以和一个已存储的属性相互绑定。

    在 node(BinaryTree, T, BinaryTree) 里,括号内的参数分别对应着左子树,值,右子树。

    这是一种紧凑的二叉树构建方式。然而,你马上就会看到一个编译器提出的错误:

    Recursive enum 'BinaryTree' is not marked 'indirect'
    

    Xcode 应该提供了一种解决这个错误的方法。根据报错信息来修正错误,然后你的枚举应该看起来像这样:

    indirect enum BinaryTree<T> {
      case empty
      case node(BinaryTree, T, BinaryTree)
    }
    

    间接

    Swift 中的枚举是一种类型值。当 Swift 试图去为类型值分配内存的时候,它需要去确切的知道所需要被分配的内存大小。

    你所定义的枚举是一种 recursive (递归)枚举。那是一种有着一个指向自身的相关值(associated value)的一种枚举。递归类型的类型值内存大小无法被确定。

    Screen Shot 2016-08-01 at 1.27.40 AM

    所以在这里你有一个问题。Swift 希望能准确的知道枚举的大小,然而你所创建的递归类型的枚举却没有暴露这个消息。

    这就是 indirect(间接)这个关键字的由来。indirect(间接)实现了一个两个类型值之间的 indirection(间接层)。这引出了语义与类型值之间的一层中间层。

    这个枚举现在引用的是它的关联值而不是自身的值。引用值有着一个确切的大小,所以就不再存在之前的问题。

    代码现在可以通过编译了,但是你能够更加的简洁。将 BinaryTree(二叉树)更新到下面的样子:

    enum BinaryTree<T> {
      case empty
      indirect case node(BinaryTree, T, BinaryTree)
    }
    

    因为只有 node(结点)是递归的,所以你只需要在结点处应用 indirect(间接)即可。

    例子:算数操作

    检验这一点的有一个有趣的例子是使用一棵二叉树来进行一系列的计算。我们来进行下面这个例子的运算:(5 * (a - 10)) + (-4 * (3 / b))

    Operations

    在你的 playground 文件的最后写下下面的语句:

    // leaf nodes
    let node5 = BinaryTree.node(.empty, "5", .empty)
    let nodeA = BinaryTree.node(.empty, "a", .empty)
    let node10 = BinaryTree.node(.empty, "10", .empty)
    let node4 = BinaryTree.node(.empty, "4", .empty)
    let node3 = BinaryTree.node(.empty, "3", .empty)
    let nodeB = BinaryTree.node(.empty, "b", .empty)
    
    // intermediate nodes on the left
    let Aminus10 = BinaryTree.node(nodeA, "-", node10)
    let timesLeft = BinaryTree.node(node5, "*", Aminus10)
    
    // intermediate nodes on the right
    let minus4 = BinaryTree.node(.empty, "-", node4)
    let divide3andB = BinaryTree.node(node3, "/", nodeB)
    let timesRight = BinaryTree.node(minus4, "*", divide3andB)
    
    // root node
    let tree = BinaryTree.node(timesLeft, "+", timesRight)
    

    你需要通过从叶结点开始一直到树的顶部来反向构建这棵树。

    CustomStringConvertible 协议

    如果没有控制台输出很难去验证一棵树的结构。Swift 有一个名为 CustomStringConvertible 的协议可以允许自定义一个print 输出声明。在你的 BinaryTree 枚举下加上下面的语句:

    extension BinaryTree: CustomStringConvertible {
      var description: String {
        switch self {
        case let .node(left, value, right):
          return "value: \(value), left = [" + left.description + "], right = [" + right.description + "]"
        case .empty:
          return ""
        }
      }
    }
    

    通过在文件的最后编写下面的语句来打印这棵树:

    tree.count
    

    你应该可以看到类似下面的语句:

    value: +, left = [value: *, left = [value: 5, left = [], right = []], right = [value: -, left = [value: a, left = [], right = []], right = [value: 10, left = [], right = []]]], right = [value: *, left = [value: -, left = [], right = [value: 4, left = [], right = []]], right = [value: /, left = [value: 3, left = [], right = []], right = [value: b, left = [], right = []]]]
    

    配合一些联想,你可以看到这棵树的结构。 ;-) 缩进一下可以帮助你的理解:

    value: +, 
        left = [value: *, 
            left = [value: 5, left = [], right = []], 
            right = [value: -, 
                left = [value: a, left = [], right = []], 
                right = [value: 10, left = [], right = []]]], 
        right = [value: *, 
            left = [value: -, 
                left = [], 
                right = [value: 4, left = [], right = []]], 
            right = [value: /, 
                left = [value: 3, left = [], right = []], 
                right = [value: b, left = [], right = []]]]
    

    得到数值

    另一个有用的特性就是可以得到树的结点。在你的 BinaryTree 枚举里面加上下面的语句:

    var count: Int {
      switch self {
      case let .node(left, _, right):
        return left.count + 1 + right.count
      case .empty:
        return 0
      }
    }
    

    通过在你的 playground 程序的最后加上下面的语句来进行测试:

    tree.count
    

    你应该可以看到侧边栏有数字 12,因为这棵树有 12 个结点。

    已经完成到这里了,非常棒。现在你已经有了关于二叉树的良好基础,是时候去了解目前为止最受欢迎的二叉树了 - Binary Search Tree(二叉搜索树)!

    二叉搜索树

    二叉搜索树是一种特殊的二叉树(普通的二叉树每个结点最多有 2 个子树),这种特殊的二叉树可以执行插入和删除操作,使得这棵树总是按序排列。

    “总是按序排列” 的属性

    这里是一个关于一棵有效二叉搜索树的例子:

    Tree1

    可以注意到每个左子树的数值小于它的父结点的数值,每个右子树的数值大于父结点的数值。这就是二叉搜索树的主要特性。

    举个例子,2 比 7 小,所以放在左边,5 比 2 大,所以放在右边。

    插入

    当执行一个插入操作的时候,将根结点当成当前结点:

    • 如果当前结点为空 ,你在这里插入一个新的结点。
    • 如果新的值更小 ,你沿着左边的分支向下。
    • 如果新的值更大 ,你沿着右边的分支向下。

    你向下遍历这棵树,直到你找到一个空的地方可以插入新值。

    例如,假如你想要插入一个值为 9 的数到上面的树中:

    1. 从树的根结点开始(根结点数值为 7),并与新的值 9 进行比较。
    2. 9 大于 7,所以你沿着右边向下。
    3. 比较 9 和 10,因为 9 小于 10,所以你沿着左边向下。
    4. 这个左分支是空的,因此你要插入一个新的结点然后放置这个 9 的数值。

    这棵新的树现在看起来像这样:

    Tree2

    这里有另一个例子。假如你想插入一个值为 3 的数到上面的树中:

    1. 从树的根结点开始(根结点数值为 7),并与新的值 3 进行比较。
    2. 3 小于 7,所以你沿着左边向下。
    3. 比较 3 和 2,因为 3 大于 2,所以你沿着右边向下。
    4. 这个左分支是空的,因此你要插入一个新的结点然后放置这个 3 的数值。

    这棵新的树现在看起来像这样:

    added

    最后这棵树上,总是只有一个可能插入新元素的地方。找到这个可以插入新元素的地方总是比较快的。这个过程会花费 O(h) 的时间,而 h 是树的高度。

    注意: 如果你对于树的高度不熟悉,来看一下之前发的 Swift Trees(Swift 的树) 这篇文章吧。

    挑战:实现插入

    现在你已经知道了应该在哪里插入数值了,是时候来实现这个过程了。在你的 BinaryTree(二叉树)枚举中加入下面的方法:

    // 1. 
    mutating func naiveInsert(newValue: T) {
      // 2.
      guard case .node(var left, let value, var right) = self else {
        // 3. 
        self = .node(.empty, newValue, .empty)
        return 
      }
    
      // 4. TODO: Implement rest of algorithm!
    
    }
    

    让我们一段一段的来复习一下:

    1. 类型值默认是不变的。如果你创建了尝试在类型值中改变什么东西的方法的话,你会需要通过显式使用 mutating(可变)关键词来标记你的方法。
    2. 你应该在当前结点中使用 guard 声明语句来暴露你的左子树,当前值和右子树。而如果这个结点是 empty(空)的,那guard 就会失败然后跳入它的 else block 语句中去。
    3. 在这个 block 中,self(自身对象) 是 empty(空)的。你将会在这里插入一个新的值。
    4. 这就是你进来的地方 - 稍等一下。

    基于之前所提到的算法知识,一会你将尝试着去实现上面的四个段落中的内容。这对于你是一个很好的锻炼,不单单是理解二叉搜索树,还包括磨练你的递归技能。

    但在你开始做之前,你需要对 BinaryTree 的签名做一点修改。在第四个段落处,你需要对比新值和旧值,但在目前的二叉树实现机制中你无法做到这一点。为了修复这一个问题,把你的 BinaryTree(二叉树)枚举更新成下面的样子:

    enum BinaryTree<T: Comparable> {}
      // stuff inside unchanged
    }
    

    Comparable(可对比的)协议确保你所构建的二叉树可以使用比较运算符进行值的对比,就像使用 operator(操作)一样。

    现在,根据之前所提到的算法知道,继续尝试实现第四个段落的内容。下面的内容可以作为参考:

    • 如果当前结点为空 ,你在这里插入一个新的结点,搞定。
    • 如果新的值更小 ,你沿着左边的分支向下,你需要这样做。
    • 如果新的值更大 ,你沿着右边的分支向下,你需要这样做。

    如果你陷入了困境,你可以查看一下下面提供的解决方案。

    // 4. TODO: Implement naive algorithm!
    if newValue < value {
      left.naiveInsert(newValue: newValue)
    } else {
      right.naiveInsert(newValue: newValue)
    }
    

    写时拷贝

    虽然这是一个很好的实现,但它不起作用。在你的 playground 程序中写入下面的语句来测试这个功能:

    var binaryTree: BinaryTree = .empty
    binaryTree.naiveInsert(newValue: 5) // binaryTree now has a node value with 5
    binaryTree.naiveInsert(newValue: 7) // binaryTree is unchanged
    binaryTree.naiveInsert(newValue: 9) // binaryTree is unchanged
    

    Screen Shot 2016-08-10 at 8.55.46 PM

    写时拷贝技术就是这里的罪魁祸首。每次你尝试着去修改这棵树的时候,一个新的子树的拷贝就会被创建。这个新的拷贝是不会链接到你的旧的拷贝的,所以你的最开始的二叉树是永远不会被新的值所修改的。

    这里需要一种不同的方式来实现一些事情。在你的 BinaryTree(二叉树)枚举中写入下面的语句:

    private func newTreeWithInsertedValue(newValue: T) -> BinaryTree {
      switch self {
      // 1
      case .empty:
        return .node(.empty, newValue, .empty)
      // 2 
      case let .node(left, value, right):
        if newValue < value {
          return .node(left.newTreeWithInsertedValue(newValue: newValue), value, right)
        } else {
          return .node(left, value, right.newTreeWithInsertedValue(newValue: newValue))
        }
      }
    }
    

    这是一个会根据所插入新元素返回一个新的树的方法。代码是相对简单的:

    1. 如果这棵树是空的,你想要去插入一个新值。
    2. 如果这棵树不是空的,你将会需要去决定把新值插入到左子树或者右子树。

    在你的 BinaryTree(二叉树)枚举中写入下面的语句:

    mutating func insert(newValue: T) {
      self = newTreeWithInsertedValue(newValue: newValue)
    }
    

    通过更换你的 playground 程序最底下的测试语句来进行测试:

    binaryTree.insert(newValue: 5) 
    binaryTree.insert(newValue: 7) 
    binaryTree.insert(newValue: 9)
    

    你应该可以得到下面的树结构:

    value: 5, 
        left = [], 
        right = [value: 7, 
            left = [], 
            right = [value: 9, 
                left = [], 
                right = []]]
    

    恭喜 - 现在你已经可以进行插入工作了。

    插入时间复杂度

    就像在剧透过的章节里面说到的,每次进行一个新的插入操作的时候,你都需要创建一份树的拷贝。创建一份拷贝需要遍历之前的所有结点。这会为这个插入方法增加 O(n) 的时间复杂度。

    提示: 一颗使用传统类实现的二叉搜索树的平均时间复杂度是 O(log n),这是相当快的。使用类(引用语义)是不会有写时拷贝的行为的,所以你将不去做树的复杂拷贝也能实现插入操作。

    遍历算法

    遍历算法是树的相关操作的基础。一个遍历算法会经历一棵树的所有结点。下面是三种遍历一颗树的主要方式:

    中序遍历

    中序遍历是按照升序来遍历一颗二叉搜索树的。下面是一个中序遍历看起来的样子:

    Traversing

    从顶部开始,沿着左边尽可能的向下。当你到达左边的底部,你将会看到当前的值,这个时候你尝试着遍历到右边。这个过程将会持续下去直到你遍历完整棵树。

    在你的 BinaryTree(二叉树)枚举中写入下面的语句:

    func traverseInOrder(process: @noescape (T) -> ()) {
      switch self {
      // 1
      case .empty:
        return 
      // 2
      case let .node(left, value, right):
        left.traverseInOrder(process: process)
        process(value)
        right.traverseInOrder(process: process)
      }
    }
    

    这段代码是相当简单的:

    1. 如果这个结点是空的,就没有方法继续前进下去。这里只要返回就好了。
    2. 如果这个结点不为空,那你将可以前进的更深一点。中序遍历的定义是首先走左子树,然后是结点,最后是右子树。

    看到这里,你将会创建上面提到的二叉树。删除你的 playground 程序最底下所有的测试代码并更换成下面的语句:

    var tree: BinaryTree<Int> = .empty
    
    tree.insert(newValue: 7)
    tree.insert(newValue: 10)
    tree.insert(newValue: 2)
    tree.insert(newValue: 1)
    tree.insert(newValue: 5)
    tree.insert(newValue: 9)
    
    tree.traverseInOrder { print($0) }
    

    你已经创建了一棵可以使用你的插入方法的二叉搜索树。traverseInOrder 将会在按照升序遍历你的结点后,传递每个结点的值给结尾闭包。

    在这个结尾闭包里,你将打印通过你的遍历方法传递过来的值。$0 是一种对于传递到闭包的元素进行引用到一种缩写语法。

    你将会看到在你的控制台会有这样的输出:

    1
    2
    5
    7
    9
    10
    

    先序遍历

    二叉搜索树的先序遍历是一种在遍历过程中首先遍历节点的遍历方法。这里的关键是在遍历子树之前首先调用 process 方法。在你的 BinaryTree(二叉树)枚举中写入下面的语句:

    func traversePreOrder( process: @noescape (T) -> ()) {
      switch self {
      case .empty:
        return
      case let .node(left, value, right):
        process(value)
        left.traversePreOrder(process: process)
        right.traversePreOrder(process: process)
      }
    }
    

    后序遍历

    二叉搜索树的后序遍历是一种在遍历过程中首先遍历左子树和右子树的遍历方法。在你的 BinaryTree(二叉树)枚举中写入下面的语句:

    func traversePostOrder( process: @noescape (T) -> ()) {
      switch self {
      case .empty:
        return
      case let .node(left, value, right):
        left.traversePostOrder(process: process)
        right.traversePostOrder(process: process)
        process(value) 
      }
    }
    

    这三种遍历方法是很多复杂的编程问题的基础。理解它们被证明在很多情况下都是有用的,包括你的下一个编程面试。

    小小的挑战

    遍历算法的时间复杂度是什么?

    时间复杂度是 O(n) ,这里的 n 指的是树的结点数。

    这应该是很明显的,因为这个遍历的想法就是要遍历一棵树的所有结点。

    搜索

    就像二叉搜索树的名字提示我们的一样,一棵二叉搜索树是已知的最好的高效搜索方式。一棵合格的二叉搜索树的所有的左子树的数目会小于它的父结点的数目,而它的所有右结点的数目会大于或等于它的父结点的数目。

    利用这个前提,你就可以知道决定选择哪条路线 - 左边或者右边 - 去知道你所要的值是否存在于这棵树上。在你的BinaryTree(二叉树)枚举中写入下面的语句:

    func search(searchValue: T) -> BinaryTree? {
      switch self {
      case .empty:
        return nil
      case let .node(left, value, right):
        // 1
        if searchValue == value {
          return self
        }
    
        // 2
        if searchValue < value {
          return left.search(searchValue: searchValue)
        } else {
          return right.search(searchValue: searchValue)
        }
      }
    }
    

    很像遍历算法,搜索包括着遍历二叉树:

    1. 如果你的当前值与你想要搜索的值相同,停止搜索。返回当前子树。
    2. 如果你继续执行到这个点,说明你还没有找到你的值。你将会需要去决定往左子树的方向前进或者往右子树的方向前进。你会使用二叉搜索树的规则来决定。

    与遍历算法不同,搜索算法在每一个递归步骤只会遍历其中一边。平均而言,这会导致时间复杂度为 O(log n) ,速度远远快于O(n) 时间复杂度的遍历操作。

    在你的 playground 文件里写下下面的语句来进行测试:

    tree.search(searchValue: 5)
    

    下一站是哪里?

    我希望你喜欢这个构建 Swift 二叉树数据结构的教程!

    这里是一个关于上述代码的 Swift playground 文件。你也可以在 Swift 算法俱乐部的 Binary Search Tree 章节里面找到关于二叉树可替代的实现方式和进行进一步的讨论。

    这只是 Swift 算法俱乐部所关注的其中一个算法实现。如果你感兴趣,请查看 repo

    这是你的最好的了解算法和数据结构的机会 - 它们解决很多现实问题,和经常被问及的面试问题。而且很有趣!

    所以后面请持续关注来自 Swift 算法俱乐部的教程。如果你对于在 Swift 中实现二叉树有任何问题,请加入下面的论坛进行讨论。





    原文发布时间为:2016年09月12日

    本文来自云栖社区合作伙伴掘金,了解相关信息可以关注掘金网站。
    展开全文
  • 转发出处:Swift循环遍历集合方法总结 概要 2016年2月份我总结过OC循环遍历,文章在此:iOS开发遍历集合(NSArray,NSDictionary、NSSet)方法总结。时隔一年,随着Swift的逐渐完善,自己使用Swift开发的...

    转发出处:Swift循环遍历集合方法总结


    概要

    2016年2月份我总结过OC循环遍历,文章在此:iOS开发遍历集合(NSArray,NSDictionary、NSSet)方法总结。时隔一年,随着Swift的逐渐完善,自己使用Swift开发的项目经验和知识逐渐积累,是时候总结一下Swift的循环遍历了。
    相信Swift一定会给你一些不一样的东西,甚至是惊喜。


    Swit-for.jpeg

    第一种方式:for-in循环

    OC延续了C语言的for循环,在Swift中被彻底改造,我们无法再使用传统形式的for循环了

    遍历数组和字典:

        //遍历数组
        let iosArray = ["L", "O", "V", "E", "I", "O", "S"]
    
        for index in 0...6 {
            print(iosArray[index])
        }
    
        for index in 0..<6 {
            print(iosArray[index])
        }
    
        for element in iosArray {
            print(element)
        }
    
        //遍历字典
        let iosDict = ["1": "one", "2": "two", "3": "three", "4": "four"]
        for (key, value) in iosDict {
            print("\(key): \(value)")
        }
    
        //单独遍历字典的key和value
        let keys = iosDict.keys
        for k in keys {
            print(k)
        }
    
        let values = iosDict.values
        for v in values {
            print(v)
        }

    如上遍历数组使用了2种方式

    1、
    第一种方式是Swift中普通的for循环语法,在索引index和遍历范围0...6之间用关键字in,这里要注意0...6的表示的范围是:0<= index <= 6,而0..<6表示的是:0<= index < 6,这里要注意的是没有:0<..6的形式。只要熟悉了Swift语法,以上这些并不难理解。
    拓展1:0...6的形式还可以取出制定范围的数组中的元素,代码如下:

        let sectionArray = iosArray[1...4]
        print(sectionArray)
        输出:
        ▿ 4 elements
            - 0 : "O"
        - 1 : "V"
        - 2 : "E"
        - 3 : "I"

    拓展2:0...6的形式还可以用来初始化创建数组,代码如下:

        let numbers = Array(1...7)
        print(numbers)
        输出:
        ▿ 7 elements
        - 0 : 1
        - 1 : 2
        - 2 : 3
        - 3 : 4
        - 4 : 5
        - 5 : 6
        - 6 : 7

    也就是说以后遇到涉及范围的情况都可以尝试0...6这种形式,看看是否可以迅速获取指定范围内的元素,可用的地方还有很多,小伙伴自己掘金吧。

    2、
    第二种方式类似于OC中的快速遍历,不需要索引直接就可以访问到数组中的元素,也很好理解。


    字典的遍历可分为同时或者分别遍历key和value

    1、
    同时遍历key和value时利用了Swift的元组,元组可以把不同类型的值组合成一个复合的值,使用起来非常方便,这样就可以同时拿到字典的key和value了。

    2、
    单独遍历字典的key个value时,需要注意的是,keys和values并不是Array,因此无法直接使用keys[0]的形式访问,他们实际的类型是LazyMapCollection<[Key : Value], Key>,显然不是一个数组。当然我们可以将他们转换成数组,如下:

        //将字典的kyes转换成数组
        let keys = Array(iosDict.keys)
        print(keys[0])

    由于字典是无序的,所有这么做的意义并不大。

    第二种方式:Swift为for循环带来的惊喜

    将以下内容单拿出来作为第二种方式不太合适,其实这部分还是属于Swift的for-in循环,单独拿出来是出于对这种方式的喜爱,也让大家在看的时候更加醒目。

    反向遍历

        //倒序遍历数组
        for index in (0...6).reversed() {
            print(iosArray[index])
        }
    
        for element in iosArray.reversed() {
            print(element)
        }
    
        //倒序遍历字典
        for (key, value) in iosDict.reversed() {
            print("\(key): \(value)")
        }

    1、
    如上无论是0...6这种索引方式还是快速遍历,都可直接调用reversed()函数轻松实现反向遍历。

    2、
    对于字典的反向遍历,有些小伙伴可能会有些疑问,字典是无序的,反向和正向遍历有区别吗,似乎意义不大。这里需要说明的是,字典的无序是说不保证顺序,但是在内存中是按照顺序排列的,只是这种顺序不一定按照我们存入或者编码的顺序排列,因此字典的反向遍历也是有意义的。

    3、
    看过我去年总结的OC循环遍历的小伙伴一定还记得,当我们需要在遍历集合时改变集合中的元素时,正向遍历会偶尔出现崩溃的问题,尤其是数据量较大时几乎每次都会崩溃,当我们使用反向遍历时就没有崩溃的问题了,在Swift中为了保证程序的稳定,也建议在遍历集合需要修改集合元素时采用反向遍历。

    拓展:reversed()函数实际上是返回给我们一个顺序完全颠倒的集合,那么我们就可以利用这个函数得到一个倒序的集合,非常方便,代码如下:

        //获取倒序数组
        let reversedArray = Array(iosArray.reversed())
        print(reversedArray)

    forEach遍历

    如果还有小伙伴认为for-in遍历繁琐,Swift还提供了一种更加简洁的遍历方式forEach,代码如下:

        //使用forEach正向遍历
        iosArray.forEach { (word) in
            print(word)
        }
    
        //使用forEach的反向遍历
        iosArray.reversed().forEach { (word) in
            print(word)
        }

    注意:1、不能使用“break”或者“continue”退出遍历;2、使用“return”结束当前循环遍历,这种方式只是结束了当前闭包内的循环遍历,并不会跳过后续代码的调用。


    stride遍历

    stride遍历分为
    stride<T : Strideable>(from start: T, to end: T, by stride: T.Stride)

    stride<T : Strideable>(from start: T, through end: T, by stride: T.Stride)
    两种遍历方式,代码如下:

        //stride正向遍历
        for index in stride(from: 1, to: 6, by: 1) {
            print(index)
            print(iosArray[index])
        }
    
        //stride正向跳跃遍历
        for index in stride(from: 0, to: 6, by: 2) {
            print(index)
            print(iosArray[index])
        }
    
        //stride反向遍历
        for index in stride(from: 6, to: 1, by: -1) {
            print(index)
            print(iosArray[index])
        }
    
        //stride through正向遍历
        for index in stride(from: 0, through: 6, by: 1) {
            print(index)
            print(iosArray[index])
        }

    1、
    正如stride单词的含义“大步跨过”,使用这种方式遍历的好处自然是可以灵活的根据自己的需求遍历,比如我们有时需要遍历索引为偶数或者基数的元素,或者每隔3个元素遍历一次等等类似的需求都可以轻松实现;

    2、
    stride遍历同样可以实现正向和反向的遍历,在by后面添加正数表示递增的正向遍历,添加负数表示递减的反向遍历;

    3、
    to和through两种遍历方式的不同在于to不包含后面的索引,而through包含后面的索引,以to: 6through: 6为例,to:<6或者>6through:<=6或者>=6,至于是<还是>取决于是正向遍历还是反向遍历。

    第三种方式:基于块的遍历

    OC拥有一套很优雅基于快的遍历,Swift保持了这套优秀的接口,下面来看看Swift是如何使用的。

    正向遍历

        //遍历数组
        for (n, c) in iosArray.enumerated() {
            print("\(n): \(c)")
        }
    
        //遍历字典
        for (n, c) in iosDict.enumerated() {
            print("\(n): \(c)")
        }

    注意:1、(n, c)中n表示元素的输入顺序,c表示集合中的每一个元素;2、由于数组是有序的,所以在数组中n自然也可以表示每一个元素在数组中索引,而字典是无序的,但是n依然会按照0、1、2...的顺序输入,因此不可以代表在字典中的索引。


    反向遍历

        //反向遍历数组
        for (n, c) in iosArray.enumerated().reversed() {
            print("\(n): \(c)")
        }
    
        //反向遍历字典
        for (n, c) in iosDict.enumerated().reversed() {
            print("\(n): \(c)")
        }

    反向遍历就是直接在enumerated()函数后调用reversed()函数。

    总结

    在总结OC循环遍历时,笔者极力推崇基于块的循环遍历,因为相比较其他的遍历方式,基于块的循环遍历实在是集优雅与实用于一体的尤物,但是在Swift中情况发生了一些变化。

    1、
    以上列举了3大种遍历方式,其实这种分类方式并不严谨,他们只是展示的形式不一样,本质上都属于for-in循环的变种,都是基于枚举的循环遍历,所以大家不必太较真他们的本质区别,在形式上区分开就可以,不影响我们的使用;

    2、
    在OC中除了少数情况我们需要使用for (int i = 0; i < n; i++) 的方式,我们都推荐使用基于枚举的快速遍历。在Swift中舍弃了for (int i = 0; i < n; i++)的形式,带给我们for index in 0...6,这并不只是语法格式的变化,从本质上已经完全不一样,使用起来更加方便,也拥有更多的接口提供便利的功能。并且相比较其他的遍历方式也有很多优点,因此在Swift中我们无法一边倒的选择一种方式,应该根据情况选择合适的方法。

    3、
    在OC中基于块的遍历还有一种情况是并发遍历,我在Swift中没有找到相应的方法,看了几千行代码也没有发现踪迹,有找到的小伙伴还请告知,感激不尽。当然并发遍历用的并不多,我们很多时候我们都希望集合的元素按序出现,在时间和效率上也没有区别。

    尾巴

    抛开我们的使用习惯,Swift在很多时候都要比OC甚至其他编程语言更加简洁实用,虽然现在Swift编程很多还是依赖于OC,甚至Swift这种编译时的语言的动态性依然是基于OC的运行时,还有大量的类似于UIKit的库也都是基于OC。Swift集其他语言优点于一身又不失个性,从for循环等一系列小的地方的改变就可以看出其独特之处。完全取代OC还需时日,可此时投身Swift确是最好时机,你还在等什么?

    展开全文
  • 随着Swift的逐渐完善,自己使用Swift开发的项目经验和知识逐渐积累,是时候总结一下Swift的循环遍历了。相信Swift一定会给你一些不一样的东西,甚至是惊喜,感兴趣的朋友们下面来一起看看吧。 第一种方式:for-in...

     前言

    之前分享总结过OC循环遍历,文章点击这里:iOS遍历集合(NSArray,NSDictionary、NSSet)方法总结。随着Swift的逐渐完善,自己使用Swift开发的项目经验和知识逐渐积累,是时候总结一下Swift的循环遍历了。相信Swift一定会给你一些不一样的东西,甚至是惊喜,感兴趣的朋友们下面来一起看看吧。

    第一种方式:for-in循环

    OC延续了C语言的for循环,在Swift中被彻底改造,我们无法再使用传统形式的for循环了

    遍历数组和字典:

     //遍历数组
     let iosArray = ["L", "O", "V", "E", "I", "O", "S"]
    
     for index in 0...6 {
     print(iosArray[index])
     }
    
     for index in 0..<6 {
     print(iosArray[index])
     }
    
     for element in iosArray {
     print(element)
     }
    
     //遍历字典
     let iosDict = ["1": "one", "2": "two", "3": "three", "4": "four"]
     for (key, value) in iosDict {
     print("\(key): \(value)")
     }
    
     //单独遍历字典的key和value
     let keys = iosDict.keys
     for k in keys {
     print(k)
     }
    
     let values = iosDict.values
     for v in values {
     print(v)
     }

    如上遍历数组使用了2种方式

    1、第一种方式是Swift中普通的for循环语法,在索引index和遍历范围0...6之间用关键字in,这里要注意0...6的表示的范围是:0<= index <= 6,而0..<6表示的是:0<= index < 6,这里要注意的是没有:0<..6的形式。只要熟悉了Swift语法,以上这些并不难理解。

    拓展1:0...6的形式还可以取出制定范围的数组中的元素,代码如下:

     let sectionArray = iosArray[1...4]
     print(sectionArray)
     输出:
     ▿ 4 elements
     - 0 : "O"
     - 1 : "V"
     - 2 : "E"
     - 3 : "I"

    拓展2:0...6的形式还可以用来初始化创建数组,代码如下:

     let numbers = Array(1...7)
     print(numbers)
     输出:
     ▿ 7 elements
     - 0 : 1
     - 1 : 2
     - 2 : 3
     - 3 : 4
     - 4 : 5
     - 5 : 6
     - 6 : 7

    也就是说以后遇到涉及范围的情况都可以尝试0...6这种形式,看看是否可以迅速获取指定范围内的元素,可用的地方还有很多,小伙伴自己掘金吧。

    2、第二种方式类似于OC中的快速遍历,不需要索引直接就可以访问到数组中的元素,也很好理解。

    字典的遍历可分为同时或者分别遍历key和value

    1、同时遍历key和value时利用了Swift的元组,元组可以把不同类型的值组合成一个复合的值,使用起来非常方便,这样就可以同时拿到字典的key和value了。

    2、单独遍历字典的key个value时,需要注意的是,keys和values并不是Array,因此无法直接使用keys[0]的形式访问,他们实际的类型是LazyMapCollection<[Key : Value], Key> ,显然不是一个数组。

    当然我们可以将他们转换成数组,如下:

     //将字典的kyes转换成数组
     let keys = Array(iosDict.keys)
     print(keys[0])

    由于字典是无序的,所有这么做的意义并不大。

    第二种方式:Swift为for循环带来的惊喜

    将以下内容单拿出来作为第二种方式不太合适,其实这部分还是属于Swift的for-in循环,单独拿出来是出于对这种方式的喜爱,也让大家在看的时候更加醒目。

    反向遍历

     //倒序遍历数组
     for index in (0...6).reversed() {
     print(iosArray[index])
     }
    
     for element in iosArray.reversed() {
     print(element)
     }
    
     //倒序遍历字典
     for (key, value) in iosDict.reversed() {
     print("\(key): \(value)")
     }

    1、如上无论是0...6这种索引方式还是快速遍历,都可直接调用reversed()函数轻松实现反向遍历。

    2、对于字典的反向遍历,有些小伙伴可能会有些疑问,字典是无序的,反向和正向遍历有区别吗,似乎意义不大。这里需要说明的是,字典的无序是说不保证顺序,但是在内存中是按照顺序排列的,只是这种顺序不一定按照我们存入或者编码的顺序排列,因此字典的反向遍历也是有意义的。

    3、看过我去年总结的OC循环遍历的小伙伴一定还记得,当我们需要在遍历集合时改变集合中的元素时,正向遍历会偶尔出现崩溃的问题,尤其是数据量较大时几乎每次都会崩溃,当我们使用反向遍历时就没有崩溃的问题了,在Swift中为了保证程序的稳定,也建议在遍历集合需要修改集合元素时采用反向遍历。

    拓展:reversed()函数实际上是返回给我们一个顺序完全颠倒的集合,那么我们就可以利用这个函数得到一个倒序的集合,非常方便,代码如下:

     //获取倒序数组
     let reversedArray = Array(iosArray.reversed())
     print(reversedArray)

    forEach遍历

    如果还有小伙伴认为for-in遍历繁琐,Swift还提供了一种更加简洁的遍历方式forEach,代码如下:

     //使用forEach正向遍历
     iosArray.forEach { (word) in
     print(word)
     }
    
     //使用forEach的反向遍历
     iosArray.reversed().forEach { (word) in
     print(word)
     }

    注意:

    1、不能使用“break”或者“continue”退出遍历;

    2、使用“return”结束当前循环遍历,这种方式只是结束了当前闭包内的循环遍历,并不会跳过后续代码的调用。

    stride遍历

    stride遍历分为

    stride<T : Strideable>(from start: T, to end: T, by stride: T.Stride)

    stride<T : Strideable>(from start: T, through end: T, by stride: T.Stride)

    两种遍历方式,代码如下:

     //stride正向遍历
     for index in stride(from: 1, to: 6, by: 1) {
     print(index)
     print(iosArray[index])
     }
    
     //stride正向跳跃遍历
     for index in stride(from: 0, to: 6, by: 2) {
     print(index)
     print(iosArray[index])
     }
    
     //stride反向遍历
     for index in stride(from: 6, to: 1, by: -1) {
     print(index)
     print(iosArray[index])
     }
    
     //stride through正向遍历
     for index in stride(from: 0, through: 6, by: 1) {
     print(index)
     print(iosArray[index])
     }

    1、正如stride单词的含义“大步跨过”,使用这种方式遍历的好处自然是可以灵活的根据自己的需求遍历,比如我们有时需要遍历索引为偶数或者基数的元素,或者每隔3个元素遍历一次等等类似的需求都可以轻松实现;

    2、stride遍历同样可以实现正向和反向的遍历,在by后面添加正数表示递增的正向遍历,添加负数表示递减的反向遍历;

    3、to和through两种遍历方式的不同在于to不包含后面的索引,而through包含后面的索引,以to: 6和through: 6为例,to:<6或者>6,through:<=6或者>=6,至于是<还是>取决于是正向遍历还是反向遍历。

    第三种方式:基于块的遍历

    OC拥有一套很优雅基于快的遍历,Swift保持了这套优秀的接口,下面来看看Swift是如何使用的。

    正向遍历

     //遍历数组
     for (n, c) in iosArray.enumerated() {
     print("\(n): \(c)")
     }
    
     //遍历字典
     for (n, c) in iosDict.enumerated() {
     print("\(n): \(c)")
     }

    注意:

    1、(n, c)中n表示元素的输入顺序,c表示集合中的每一个元素;

    2、由于数组是有序的,所以在数组中n自然也可以表示每一个元素在数组中索引,而字典是无序的,但是n依然会按照0、1、2...的顺序输入,因此不可以代表在字典中的索引。

    反向遍历

     //反向遍历数组
     for (n, c) in iosArray.enumerated().reversed() {
     print("\(n): \(c)")
     }
    
     //反向遍历字典
     for (n, c) in iosDict.enumerated().reversed() {
     print("\(n): \(c)")
     }

    反向遍历就是直接在enumerated()函数后调用reversed()函数。

    总结

    在总结OC循环遍历时,笔者极力推崇基于块的循环遍历,因为相比较其他的遍历方式,基于块的循环遍历实在是集优雅与实用于一体的尤物,但是在Swift中情况发生了一些变化。

    1、以上列举了3大种遍历方式,其实这种分类方式并不严谨,他们只是展示的形式不一样,本质上都属于for-in循环的变种,都是基于枚举的循环遍历,所以大家不必太较真他们的本质区别,在形式上区分开就可以,不影响我们的使用;

    2、在OC中除了少数情况我们需要使用for (int i = 0; i < n; i++) 的方式,我们都推荐使用基于枚举的快速遍历。在Swift中舍弃了for (int i = 0; i < n; i++)的形式,带给我们for index in 0...6,这并不只是语法格式的变化,从本质上已经完全不一样,使用起来更加方便,也拥有更多的接口提供便利的功能。并且相比较其他的遍历方式也有很多优点,因此在Swift中我们无法一边倒的选择一种方式,应该根据情况选择合适的方法。

    3、在OC中基于块的遍历还有一种情况是并发遍历,我在Swift中没有找到相应的方法,看了几千行代码也没有发现踪迹,有找到的小伙伴还请告知,感激不尽。当然并发遍历用的并不多,我们很多时候我们都希望集合的元素按序出现,在时间和效率上也没有区别。

    结束语

    抛开我们的使用习惯,Swift在很多时候都要比OC甚至其他编程语言更加简洁实用,虽然现在Swift编程很多还是依赖于OC,甚至Swift这种编译时的语言的动态性依然是基于OC的运行时,还有大量的类似于UIKit的库也都是基于OC。Swift集其他语言优点于一身又不失个性,从for循环等一系列小的地方的改变就可以看出其独特之处。完全取代OC还需时日,可此时投身Swift确是最好时机,你还在等什么?

    以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对脚本之家的支持。

    转载于:https://www.cnblogs.com/yujidewu/p/7464167.html

    展开全文
  • OC延续了C语言的for循环,在Swift中被彻底改造,我们无法再使用传统形式的for循环了 遍历数组和字典: 1 //遍历数组 2 let iosArray = ["L", "O", "V", "E", "I", "O", "S"] 3 4 for index in 0...6 {...
     

    第一种方式:for-in循环

    OC延续了C语言的for循环,在Swift中被彻底改造,我们无法再使用传统形式的for循环了

    遍历数组和字典:

     1  //遍历数组
     2     let iosArray = ["L", "O", "V", "E", "I", "O", "S"]
     3 
     4     for index in 0...6 {
     5         print(iosArray[index])
     6     }
     7 
     8     for index in 0..<6 {
     9         print(iosArray[index])
    10     }
    11 
    12     for element in iosArray {
    13         print(element)
    14     }
    15 
    16     //遍历字典
    17     let iosDict = ["1": "one", "2": "two", "3": "three", "4": "four"]
    18     for (key, value) in iosDict {
    19         print("\(key): \(value)")
    20     }
    21 
    22     //单独遍历字典的key和value
    23     let keys = iosDict.keys
    24     for k in keys {
    25         print(k)
    26     }
    27 
    28     let values = iosDict.values
    29     for v in values {
    30         print(v)
    31     }

     


    如上遍历数组使用了2种方式

    1、
    第一种方式是Swift中普通的for循环语法,在索引index和遍历范围0...6之间用关键字in,这里要注意0...6的表示的范围是:0<= index <= 6,而0..<6表示的是:0<= index < 6,这里要注意的是没有:0<..6的形式。只要熟悉了Swift语法,以上这些并不难理解。
    拓展1:0...6的形式还可以取出制定范围的数组中的元素,代码如下:

    1   let sectionArray = iosArray[1...4]
    2     print(sectionArray)
    3     输出:
    44 elements
    5         - 0 : "O"
    6     - 1 : "V"
    7     - 2 : "E"
    8     - 3 : "I"

    拓展2:0...6的形式还可以用来初始化创建数组,代码如下:
     1  let numbers = Array(1...7)
     2     print(numbers)
     3     输出:
     47 elements
     5     - 0 : 1
     6     - 1 : 2
     7     - 2 : 3
     8     - 3 : 4
     9     - 4 : 5
    10     - 5 : 6
    11     - 6 : 7

    也就是说以后遇到涉及范围的情况都可以尝试0...6这种形式,看看是否可以迅速获取指定范围内的元素,可用的地方还有很多,小伙伴自己掘金吧。

    2、
    第二种方式类似于OC中的快速遍历,不需要索引直接就可以访问到数组中的元素,也很好理解。


    字典的遍历可分为同时或者分别遍历key和value

    1、
    同时遍历key和value时利用了Swift的元组,元组可以把不同类型的值组合成一个复合的值,使用起来非常方便,这样就可以同时拿到字典的key和value了。

    2、
    单独遍历字典的key个value时,需要注意的是,keys和values并不是Array,因此无法直接使用keys[0]的形式访问,他们实际的类型是LazyMapCollection<[Key : Value], Key>,显然不是一个数组。当然我们可以将他们转换成数组,如下:

        1 //将字典的kyes转换成数组 2  let keys = Array(iosDict.keys) 3 print(keys[0]) 

    由于字典是无序的,所有这么做的意义并不大。

    第二种方式:Swift为for循环带来的惊喜

    将以下内容单拿出来作为第二种方式不太合适,其实这部分还是属于Swift的for-in循环,单独拿出来是出于对这种方式的喜爱,也让大家在看的时候更加醒目。

    反向遍历

     1     //倒序遍历数组
     2     for index in (0...6).reversed() {
     3         print(iosArray[index])
     4     }
     5 
     6     for element in iosArray.reversed() {
     7         print(element)
     8     }
     9 
    10     //倒序遍历字典
    11     for (key, value) in iosDict.reversed() {
    12         print("\(key): \(value)")
    13     }
    1、如上无论是0...6这种索引方式还是快速遍历,都可直接调用reversed()函数轻松实现反向遍历。

    2、
    对于字典的反向遍历,有些小伙伴可能会有些疑问,字典是无序的,反向和正向遍历有区别吗,似乎意义不大。这里需要说明的是,字典的无序是说不保证顺序,但是在内存中是按照顺序排列的,只是这种顺序不一定按照我们存入或者编码的顺序排列,因此字典的反向遍历也是有意义的。

    3、
    看过我去年总结的OC循环遍历的小伙伴一定还记得,当我们需要在遍历集合时改变集合中的元素时,正向遍历会偶尔出现崩溃的问题,尤其是数据量较大时几乎每次都会崩溃,当我们使用反向遍历时就没有崩溃的问题了,在Swift中为了保证程序的稳定,也建议在遍历集合需要修改集合元素时采用反向遍历。

    拓展:reversed()函数实际上是返回给我们一个顺序完全颠倒的集合,那么我们就可以利用这个函数得到一个倒序的集合,非常方便,代码如下:

       1 //获取倒序数组 2  let reversedArray = Array(iosArray.reversed()) 3 print(reversedArray) 

    forEach遍历

    如果还有小伙伴认为for-in遍历繁琐,Swift还提供了一种更加简洁的遍历方式forEach,代码如下:

    1   //使用forEach正向遍历
    2     iosArray.forEach { (word) in
    3         print(word)
    4     }
    5 
    6     //使用forEach的反向遍历
    7     iosArray.reversed().forEach { (word) in
    8         print(word)
    9     }

     

    注意: 1、不能使用“break”或者“continue”退出遍历; 2、使用“return”结束当前循环遍历,这种方式只是结束了当前闭包内的循环遍历,并不会跳过后续代码的调用。


    stride遍历

    • stride遍历分为
    •  stride(from: <#T##Strideable#>, to: <#T##Strideable#>, by: <#T##Comparable & SignedNumeric#>)
    •  stride(from: <#T##Strideable#>, through: <#T##Strideable#>, by: <#T##Comparable & SignedNumeric#>)

    两种遍历方式,代码如下:

      
     1   //stride正向遍历
     2     for index in stride(from: 1, to: 6, by: 1) {
     3         print(index)
     4         print(iosArray[index])
     5     }
     6 
     7     //stride正向跳跃遍历
     8     for index in stride(from: 0, to: 6, by: 2) {
     9         print(index)
    10         print(iosArray[index])
    11     }
    12 
    13     //stride反向遍历
    14     for index in stride(from: 6, to: 1, by: -1) {
    15         print(index)
    16         print(iosArray[index])
    17     }
    18 
    19     //stride through正向遍历
    20     for index in stride(from: 0, through: 6, by: 1) {
    21         print(index)
    22         print(iosArray[index])
    23     }
    1、

    正如stride单词的含义“大步跨过”,使用这种方式遍历的好处自然是可以灵活的根据自己的需求遍历,比如我们有时需要遍历索引为偶数或者基数的元素,或者每隔3个元素遍历一次等等类似的需求都可以轻松实现;

    2、
    stride遍历同样可以实现正向和反向的遍历,在by后面添加正数表示递增的正向遍历,添加负数表示递减的反向遍历;

    3、
    to和through两种遍历方式的不同在于to不包含后面的索引,而through包含后面的索引,以to: 6through: 6为例,to:<6或者>6through:<=6或者>=6,至于是<还是>取决于是正向遍历还是反向遍历。

    第三种方式:基于块的遍历

    OC拥有一套很优雅基于快的遍历,Swift保持了这套优秀的接口,下面来看看Swift是如何使用的。

    正向遍历

    1     //遍历数组
    2     for (n, c) in iosArray.enumerated() {
    3         print("\(n): \(c)")
    4     }
    5 
    6     //遍历字典
    7     for (n, c) in iosDict.enumerated() {
    8         print("\(n): \(c)")
    9     }

     

    注意: 1、(n, c)中n表示元素的输入顺序,c表示集合中的每一个元素; 2、由于数组是有序的,所以在数组中n自然也可以表示每一个元素在数组中索引,而字典是无序的,但是n依然会按照0、1、2...的顺序输入,因此不可以代表在字典中的索引。


    反向遍历

    1  //反向遍历数组
    2     for (n, c) in iosArray.enumerated().reversed() {
    3         print("\(n): \(c)")
    4     }
    5 
    6     //反向遍历字典
    7     for (n, c) in iosDict.enumerated().reversed() {
    8         print("\(n): \(c)")
    9     }

     

    反向遍历就是直接在enumerated()函数后调用reversed()函数。

     

    转载于:https://www.cnblogs.com/lurenq/p/7477246.html

    展开全文
  • title: "Swift 中枚举高级用法及实践" date: 2015-11-20 tags: [APPVENTURE] categories: [Swift 进阶] permalink: advanced-practical-enum-examples 原文链接=...
  • Swift正迅速成为数据科学中最强大、最有效的语言之一 Swift与Python非常相似,所以你会发现2种语言的转换非常平滑 我们将介绍Swift的基础知识,并学习如何使用该语言构建你的第一个数据科学模型 介绍 Python被...
  • 正好,也想复习一下数据结构的知识,就来写了一个Demo。居然有新发现(本文中的第三种方式)。 我们在学习数据结构的时候,肯定可以很轻松地编写对二叉树的三种遍历过程。分别是前序、中序和后序遍历。 这里要说...
  • 确定用于表示给定值集合的数据结构通常比看起来更棘手。由于每种数据结构都针对一定数量的用例进行了优化,因此找到每组数据的正确匹配通常会对我们的代码最终变得有效率产生重大影响。 雨燕标准库附带了三个主要...
  • swift 3. 基本数据类型

    2016-09-03 18:06:13
    前言: 这一节是蛮重要的基础部分,主要讲解swift的基本shu
  • 本文旨在帮助开发者快速从OC开发过渡到Swift开发,挑选了一些比较浅显的但是比较常用的Swift语法特性,在介绍的过程中,通常会拿OC中的语言特性作比较,让大家更好的注意到Swift的不同。 另外需要说明的是,笔者也...
  • Swift是苹果于2014年WWDC(苹果开发者大会)发布的新开发语言,可与Objective-C共同运行于MAC OS和iOS平台,用于搭建基于苹果平台的应用程序。 2、举例说明Swift里面有哪些是 Objective-C中没有的? Swift...
  • 假设你有一个需要处理许多数据的应用。你会把收据放在哪儿?你怎么样高效地组织并处理数据呢?如果你的项目只处理一个数字,你把它存在一个变量中。如果有两个数字你就用两个变量。 如果有1000个数字,10,000个...
  • UserDefault基本用法
  • 区间运算符Range Operator也是Swift的一个比较突出的特点。可以用来表示一段数据的区域。区间运算符主要可以分为以下两类: Closed Range Operator :闭区间[a,b] a...b :注意:a和b之间是三个点 Half-Closed ...
  • 三、Swift基础介绍 本章将对Swift做一个简单说明,内容...首先swift全局作用域中的代码会被自动当做程序的入口点,所以你也不需要main函数。你同样不需要在每个语句结尾写上分号。而且提供了playground可以在编辑代码实
  • Swift之集合

    2017-05-06 11:01:55
    前言集合(Collection)是建立在序列(sequence)上层的类型,它添加了可重复遍历元素和根据下标访问元素的功能。为了具体说明Swift中的集合实现原理。我们会实现一个自己的集合。可能Swift标准库中没有实现的最有用的...
  • 前提知识:在Swift中,如果想要使用某一个类(cocoapods导入的三方库除外),是不用导头文件的,因为Swift新增了一个OC中没有的概念,叫“命名空间”。只要在同一个命名空间中的资源都是共享的,而且默认情况下,...
  • Swift 数组会强制检测元素的类型,如果类型不同则会报错,Swift 数组应该遵循像Array这样的形式,其中Element是这个数组中唯一允许存在的数据类型。 如果创建一个数组,并赋值给一个变量,则创建的集合就是可以修改...
  • Swift语言提供Array、Set和Dictionary三种基本的集合类型用来存储集合数据。数组是有序的数据集;集合是无序无重复的数据集;而字典是无序的键值对数组集。 Swift的Array、Set和Dictionary类型被实现为泛型集合。...
1 2 3 4 5 ... 20
收藏数 1,922
精华内容 768