0%

mapflatMap是Swift数组中的两个高阶函数,他们能很方便的对数组内的所有元素进行操作,然后返回一个新的数组。本文将探索这两个函数的实际使用以及其内部实现。

map

在objective-c中,如果我们要对一个数组里的每个元素进行操作,我们都是通过写for循环来遍历数组,然后在再对元素进行操作。比如我们要对一个数组中的所以元素做平方,并返回新的数组,我们可以这样写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

NSArray *array = @[@(1), @(3), @(5), @(6)];
NSMutableArray *sauared = [NSMutableArray array];
for (NSNumber *number in array) {
NSInteger num = number.integerValue;
[sauared addObject:@(num * num)];
}

NSLog(@"%@", sauared);

// 最后的输出为:
2016-07-31 15:20:52.497 Array[9945:5153603] (
1,
9,
25,
36
)

在Swift中,为数组提供了一个map方法来做这件事,因此上面的代码在Swift中可以这样写:

1
2
3
let array = [1, 2, 3]
let sauared = x.map{$0 * $0}
// sauared = [1, 4, 9]

使用map方法的优势很明显:

  • 代码会变得非常简洁,一句代码就能搞定,代码量变少了,也意味着出错的概率变小了。
  • 语义更加明确,也就是代码可读性更好。通过map这个方法名,我们一眼就知道这个方法要做什么,而如果是通过for循环,我们必须看完整个上下文的代码的才知道在做什么。
  • 在Swift中使用map还有一个优点就是能使用let把新的数组定义为不可变量。如果是用for循环,因为要在循环中逐个添加新的数组元素,因此新的数组必须要用var来定义。这样也提高了代码的安全性。

map 的内部实现

map函数的内部实现原理也非常简单,就是把for循环中的代码用一个泛型函数封装起来了。源码地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public func map<T>(
_ transform: @noescape (Iterator.Element) throws -> T
) rethrows -> [T] {
// TODO: swift-3-indexing-model - review the following
let count: Int = numericCast(self.count)
if count == 0 {
return []
}

var result = ContiguousArray<T>()
result.reserveCapacity(count)

var i = self.startIndex

for _ in 0..<count {
result.append(try transform(self[i]))
formIndex(after: &i)
}

_expectEnd(i, self)
return Array(result)
}

我们也可以这样简单地实现:

1
2
3
4
5
6
7
8
9
10
11
extension Array {
func my_map<U>(transform: Element -> U) -> [U] {
var result: [U] = []
result.reserveCapacity(self.count)
for x in self {
result.append(transform(x))
}
return result
}
}

flatMap

flatMapmap很像,都是对数组中每个元素进行操作转换,但flatMap会做两件额外的事,一是将结果数组展平,如果结果数组中的元素也是一个数组,就会将这个数组展平;二是会把数组中的nil过滤,返回一个不包含nil的数组。这两个额外的操作,其实是分别对应flatMap的两个方法, 因此在一个flatMap方法中这两个额外操作是不能同时执行的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
extension SequenceType {
/// Returns an `Array` containing the concatenated results of mapping
/// `transform` over `self`.
///
/// s.flatMap(transform)
///
/// is equivalent to
///
/// Array(s.map(transform).flatten())
///
/// - Complexity: O(*M* + *N*), where *M* is the length of `self`
/// and *N* is the length of the result.
@warn_unused_result
public func flatMap<S : SequenceType>(transform: (Self.Generator.Element) throws -> S) rethrows -> [S.Generator.Element]
}

extension SequenceType {
/// Returns an `Array` containing the non-nil results of mapping
/// `transform` over `self`.
///
/// - Complexity: O(*M* + *N*), where *M* is the length of `self`
/// and *N* is the length of the result.
@warn_unused_result
public func flatMap<T>(@noescape transform: (Self.Generator.Element) throws -> T?) rethrows -> [T]
}

从文档中我们可以看到第一个flatMap的调用者中的元素其实是一个数组(SequenceType),返回的是一个完全展开的一次数组,通过实际代码,我们可以看出其中的区别:

1
2
3
4
5
let array = [[1, 2, 3, 4], [5, 6, 7]]
let mapArray = array.map{$0.map{$0 * $0}}
// mapArray = [[1,4,9,16],[25,36,49]]
let flatMapArray = array.flatMap{$0.map{$0 * $0}}
// flatMapArray = [1,4,9,16,25,36,49]

文档的解释是第一个flatMap函数相当于Array(s.map(transform).flatten())

第二个flatMap方法则会返回一个不带nil的数组,示例如下:

1
2
3
4
5
let nilArray: [Any?] = [1, 2, nil, 3]
let mapArray = nilArray.map{$0}
// mapArray = [1,2,nil,3]
let flatMapArray = nilArray.flatMap{$0}
// flatMapArray = [1,2,3]

小思考:下面的代码各会返回什么结果

1
2
3
4
5
6
7
8
9
let sArray: [[Int?]] = [[1, 2, 3, nil, 4]]
sArray.flatMap{$0}

let array = [[1, 2, 3], 4, [5, 6]]
array.flatMap{$0}

let array: [Any?] = [[1, 2, 3], 4, [5, 6], nil]
array.flatMap{$0}

flatMap 的内部实现

第一个flatMap源码地址

1
2
3
4
5
6
7
8
9
10
11

public func flatMap<SegmentOfResult : Sequence>(
_ transform: @noescape (${GElement}) throws -> SegmentOfResult
) rethrows -> [SegmentOfResult.${GElement}] {
var result: [SegmentOfResult.${GElement}] = []
for element in self {
result.append(contentsOf: try transform(element))
}
return result
}

从源码中我们可以发现,flatMap方法之所以能将数组展开,关键是在添加结果是使用的是appendContentsOf方法,这个方法能把数组中的元素取出,放入一个新数组中。

第二个flatMap源码地址

1
2
3
4
5
6
7
8
9
10
11
12
13

public func flatMap<ElementOfResult>(
_ transform: @noescape (${GElement}) throws -> ElementOfResult?
) rethrows -> [ElementOfResult] {
var result: [ElementOfResult] = []
for element in self {
if let newElement = try transform(element) {
result.append(newElement)
}
}
return result
}

第二个flatMap方法内部则使用了一个if let判断来过滤nil元素。

Swift Transforming Arrays

——by《Advanced Swift》

In Swift, Array object has a number of Transforming methods, there are map, filter, reduce, flatMap, etc.

Map

Every programmer has writtern similar code hundreds of times: create a new array, loop over all elements in an existing array, perform an operation on an element, and append the result of that operation to the new array. For example, the follwing code squares an array of integers:

1
2
3
4
5
6
let fibs = [1, 2, 3, 4]
var sauared: [Int] = []
for fib in fibs {
sauared.append(fib * fib)
}

Swift arrays have a map method, adopted from the world of functional programming. This method can perform a transformation on every value in the array. So, the above code can write like this:

1
2
let sauared = fibs.map { fib in fib * fib }

Use map method has three main advantages:

  • It’s shorter, and more importantly, it’s clearer. The map acts a signal, you know immediately what is happening when you see it: a function is going to be applied to every element, returning a new array of the transformed elements.
  • We can declare squared with let, because we aren’t mutating it any longer when use map method.
  • map isn’t hard to write – it’s just need to wrapping up the boilerplate parts of the for loop into a generic function. Here’s one possible implementaion:
1
2
3
4
5
6
7
8
9
10
11
extension Array {
func my_map<U>(transform: Element -> U) -> [U] {
var result: [U] = []
result.reserveCapacity(self.count)
for x in self {
result.append(transform(x))
}
return result
}
}

Parameterizing Behavior with Functions

map manages to separate out the boilerplate functionality – which doesn’t vary from call to call – from the funcionality that always varies: the logic of how exactly to fransform each element.
This pattern of parameterizing behavior is found throughout the standard library. There are 13 separate functions that take a closure that allows the caller to customize the key step:

  • map and flatMap – how to tranform an element
  • filter – should an element be included?
  • reduce – how fo fold an element into an aggregate value
  • sort and lexicographicCompare – in what order should two elements come?
  • indexOf and contains – does this elements match
  • minElement and maxElement – which is the min/max of two elements?
  • elementsEqual and startsWith – are two elements equivalent?
  • split – is this element a separator?

how to learn ruby

the best way to learn how to code is to actually code. so please go ahead and copy the examples from the github repository that will be provided and follow along the example. Experiment in the editor by typing, by modifying the examples and executing the examples and seeing what happens. That’s the only way to learn how to code.

Ruby basics

Ruby is an Object-Oriented Language

Ruby is a genuine object-oriented language, and pretty much everything in Ruby is an object.

for example the number 5 in ruby is an object of the class called Fixnum

1
2
3
4
5
irb(main):011:0* 5
=> 5
irb(main):012:0> 5.class
=> Fixnum

flow of control in Ruby

tips:

false and nil objects are false
everthing else is true include 0

triple equal ==> === is called the case equality operator because it is used in precisely this case!
most of the time triple equals just delegates to a double equals, so it’s almost like a super set of a double equals.

3 flavors

  • regular expression
  • double equals
  • comparing the value class

Functions and Methods

  • Technically, a function is defined outside of a class and a method is defined inside a class.
  • In Ruby, every function/method has at least one class it blongs to. Not always written inside a class.

**Methods: **

Methods are invoked by sending a message to an object.

  • Parentheses(“()”) are optional both when defining and calling a method
  • No need to declare type of parameters
  • Can return whatever you want
  • return keyword is optional(last executed line returned)
  • Method names can end with:
    • ‘?’-predicate methods
    • ‘!’-dangerous side-effects
  • Methods can have default arguments

Blocks

  • Chunksof code
    • Enclosed between either curly braces({}) when block content is single line or the keywords do and end when block content spans multiple lines.
    • Passed to methods as last “parameter”
  • Coding with blocks
    • two ways to configure a block in your own method
      • Implicit
        • use `block_given?’ to see if block was passed in
        • use ‘yield’ to “call” the block
      • Explicit
        • use & in front of the last “parameter”
        • use call method to call the block

files

  • read file:
1
2
3
4
5
6
irb(main):001:0> File.foreach('test.txt') do |line|
irb(main):002:1* puts line
irb(main):003:1> p line
irb(main):004:1> p line.chomp
irb(main):005:1> p line.split
irb(main):006:1> end
  • write file

    1
    2
    3
    4
    5
    # override the file
    irb(main):007:0> File.open("test.txt", "w") do |file|
    irb(main):008:1* file.puts "one line"
    irb(main):009:1> file.puts "second line"
    irb(main):010:1> end

当我有了一定的编程经验后,熟悉了一门语言,然后再去学习另一门语言,会发现要找教程都比较麻烦,因为基本上所有语言的基础教程,都会不厌其烦的讲各个语言都差不多的一些编程基础内容。
我比较认同《七周七语言》书中的观点,若要领会一门语言的精髓,应该从以下5个方面入手:

  • 语言的类型模型是什么?强类型或弱类型,静态类型或动态类型。
  • 语言的编程范式是什么?是面向对象、函数式、过程式、还是其它的,或者是各种范式的综合体。
  • 怎样和语言交互?编译型还是解释型。
  • 语言的判断结构和核心数据结构是什么。
  • 哪些核心特性让这门语言与众不同。

Ruby简介

  • Ruby是由日本人松本行弘在1993年发明的
  • Ruby是一门脚本语言、解释型、面向对象、动态类型的语言

Ruby基础语法特点

Ruby是一门解释型语言,不需要编译,能直接运行源代码。

在mac的终端上执行irb命令,就能进入Ruby的执行环境。
我们可以先输入一些简单的代码进去看看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
irb(main):001:0> 5
=> 5
irb(main):002:0> 5 + 5
=> 10
irb(main):004:0> 'lijun'
=> "lijun"
irb(main):005:0> 'lijun' + 'hello'
=> "lijunhello"
irb(main):006:0> a = 'Ruby'
=> "Ruby"
irb(main):007:0> puts a
Ruby
=> nil
irb(main):008:0> puts "hello, #{a}"
hello, Ruby
=> nil
irb(main):009:0> puts 'hello, #{a}'
hello, #{a}
=> nil
irb(main):010:0>

从上面这段代码中,我们可以看出几点:

  • Ruby是一门解释执行,不需要编译 
  • 不用声明变量
  • 每条 ruby 代码都会返回某个值
  • 单引号表示直接解释
  • 双引号包含的字符串会进行字符串替换

纯面向对象,数字、函数都是对象,函数作为参数传递

Ruby 是一门纯面向对象的语言

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
irb(main):011:0* 5
=> 5
irb(main):012:0> 5.class
=> Fixnum
irb(main):013:0> 5.class.class
=> Class
irb(main):015:0> 5.class.class.superclass
=> Module
irb(main):016:0> Module
=> Module
irb(main):017:0> Module.superclass
=> Object
irb(main):018:0> Object.class
=> Class
irb(main):020:0> Object.superclass
=> BasicObject
irb(main):021:0> Object.superclass.superclass
=> nil
irb(main):022:0> BasicObject
=> BasicObject
irb(main):023:0> BasicObject.methods
=> [:allocate, :new, :superclass, :freeze, :===, :==, :<=>, :<, :<=, :>, :>=, :to_s, :inspect, :included_modules, :include?, :name, :ancestors, :instance_methods, :public_instance_methods, :protected_instance_methods, :private_instance_methods, :constants, :const_get, :const_set, :const_defined?, :const_missing, :class_variables, :remove_class_variable, :class_variable_get, :class_variable_set, :class_variable_defined?, :public_constant, :private_constant, :singleton_class?, :include, :prepend, :module_exec, :class_exec, :module_eval, :class_eval, :method_defined?, :public_method_defined?, :private_method_defined?, :protected_method_defined?, :public_class_method, :private_class_method, :autoload, :autoload?, :instance_method, :public_instance_method, :nil?, :=~, :!~, :eql?, :hash, :class, :singleton_class, :clone, :dup, :itself, :taint, :tainted?, :untaint, :untrust, :untrusted?, :trust, :frozen?, :methods, :singleton_methods, :protected_methods, :private_methods, :public_methods, :instance_variables, :instance_variable_get, :instance_variable_set, :instance_variable_defined?, :remove_instance_variable, :instance_of?, :kind_of?, :is_a?, :tap, :send, :public_send, :respond_to?, :extend, :display, :method, :public_method, :singleton_method, :define_singleton_method, :object_id, :to_enum, :enum_for, :equal?, :!, :!=, :instance_eval, :instance_exec, :__send__, :__id__]

在Ruby中一切皆对象,包括数字,也包括函数。

在ruby中是通过def ... end来定义一个函数

1
2
3
4
5
6
7
8
9

irb(main):045:0* def say_hello(name)
irb(main):046:1> puts "hello, #{name}"
irb(main):047:1> end
=> :say_hello
irb(main):048:0> say_hello('lijun')
hello, lijun
=> nil

ruby的函数默认是将最后一行的代码的运行结果返回,当然也可以写return,指定返回的内容。

1
2
3
4
5
6
7
8
9
10
11
12

irb(main):060:0* def is_equal(a,b)
irb(main):061:1> a == b
irb(main):062:1> end
=> :is_equal
irb(main):063:0> bool = is_equal(3,4)
=> false
irb(main):064:0> bool
=> false
irb(main):065:0> bool = is_equal(5,5)
=> true

Ruby中也有类似oc中block的代码块,实际上就是一个匿名函数。

1
2
3
4
5
6
7
irb(main):070:0* 5.times { puts 'hello, ruby' }
hello, ruby
hello, ruby
hello, ruby
hello, ruby
hello, ruby
=> 5

动态类型、鸭子类型,强类型

判断结构和核心数据

Ruby核心特性

module and mixin

Ruby元编程

Ruby gem,rake,task

**未完待续**

pushups

2年半的时间,5万个俯卧撑,这是我昨天在PushUps软件里达成的数字。

昨天看到这个数字的时候,我自己也惊了一下,没想到会有这么多。因为我并没有刻意去追求要做多少个,也并不是很有计划性,基本上就是按照内置的计划,想做的时候就做一下,而且这两年半的时间里,还有大半年几乎没有做俯卧撑。同时开始使用的SquatsSitUps分别记录的深蹲和仰卧起坐个数只有1万左右,只有俯卧撑的1/5。

任何事情坚持下去,总会有所成就的。5万个俯卧撑,给我带来的直接变化,就是有胸肌了。尽管我现在依然很瘦,但能够很明显的看出胸肌。这可能是我断断续续坚持做的最久的一件事吧,比跑步还久。

me

到目前为止,我能坚持做的事,基本都是运动方面的,比如最早的骑行、后来跑步、做俯卧撑,还没有将坚持扩展到其它方面,比如学习方面。比较成功的就属去年学习编程了,日复一日坚持了几个月,但工作后就基本停止了。

对我而言,做一些无需动脑地简单重复动作更容易坚持,而一旦涉及脑力活动,就比较困难了。至今没有找到比较好的解决方法。

或许方法早已找到,只是我一直没有运用到脑力活动上。骑行、跑步、健身这些体力活动,我能坚持的一个主要原因就是有app记录数据成就。脑力活动虽然没有专门的app记录,但可以写笔记、博客,也是一种记录,只是以前一直没有做。

希望这次重新开始写博客能开一个好头,坚持写博客,坚持记录。

我的编程之路似乎总是与博客有着不解之缘,我走上编程之路是源于折腾博客,现在学习一种全新的编程语言也受到搭建博客的推动。
我的编程之路从一开始就是受实际需求推动的,这也是我喜欢编程的一个原因,编程能帮满足我的实际需求,为了实现某个我需要的功能,我能废寝忘食的研究。

黑客与画家,在保罗·格雷厄姆看来是最相像的职业,因为他们都是创造者,都试图创造出优秀的作品。

这一点我完全同意,程序员最大的价值就是写出优秀的软件或者程序,这也是程序员最大的乐趣和成就,这是一种可以让生活更简单,改变很多人行为的事情。

学以致用、按需学习一直是我学习编程的动力。我在2014年从一个文科生转而学习编程,完全是因为当时确实遇上了需要编程解决的问题。

阅读全文 »

记不得这是第几次决定要写博客了,很多年前就认识到了写作的重要性,也曾经很多次决定要好好写写博客,但至今没有一次坚持下来了。每次感到迷茫或者想要好好学习或者想要思考一些问题的时候,都会想写博客。

写作有利于思考

想要写博客的最直接的原因就是写作有利于思考。因为文字的逻辑性、记录性,它一方面能帮助我们理性地思考,另一方面也能记录下我们的思考,为将来检验反思留下存档。

记得刘未鹏有一篇文章就叫《书写是为了更好的思考》详细地论述了书对思考的作用。如果把人脑比作电脑的CPU的话,那写作就好比为我们的大脑增加了一块内存,而且没有大小限制。它可以缓存所有的思考,供大脑下一步思考使用,这样使思考得以连续不中断。

阅读全文 »

十年来,likumb几乎是我在网上的所有账号名称,它的出现是完全偶然的,它是毫无意义的,它也是独一无二的,它的一切都由我来创造。它代表了我对人生的一些根本认识。

阅读全文 »