Erlang/Elixir [Elixir 基础] 复合类型: Tuple

skpark1987 · 2017年09月18日 · 最后由 skpark1987 回复于 2017年09月19日 · 6267 次阅读

最近在学习 Elixir 开发,因为它是面向函数的编程语言,像我这种一直做面向对象开发的人来说很多东西很难理解。因此我打算写一系列基础篇的文章,既巩固自己的知识,也希望对其他学习该技术的人有帮助。

往期回顾
【Elixir 基础】复合类型:List

我们会以定义,常用的操作符,方法,性能的顺序来进行学习。

定义

Elixir 中的 Tuple 是一个有序集合,它既可以包含任意类型的元素,又可以包含不同的类型。使用大括号进行定义

iex(3)> {1, :two, "three"}
{1, :two, "three"}

tuple 在内存中是使用连续的内存块进行保存,它意味着访问某一个元素是固定消耗,不会随着 tuple 的大小而改变。

iex(4)> tuple = {1, :two, "three"}
{1, :two, "three"}
iex(5)> elem(tuple, 0)
1
iex(6)> elem(tuple, 2)
"three"

因为 tuple 是使用连续的内存区域进行保存,所以修改 tuple 中的某个元素的时候我们需要对整个代码块进行复制,所以数据量大的时候会相当消耗内存。在 Elixir 中 tuple 一般不会当做集合来使用,而是当做一个容器包含多个值。比如调用方法的时候返回{:ok, value}{:error, reason}

{:ok, ["tribe", "tribe.ex", "tribe_web", "tribe_web.ex", "tribe_web_admin.ex"]}
iex(6)> File.ls("aaa")
{:error, :enoent}

常用的方法

iex(7)> tuple = {1, "abc", :hello, true}
{1, "abc", :hello, true}
# 访问元素
iex(8)> elem(tuple, 1)
"abc"
# 更新元素
iex(9)> put_elem(tuple, 1, "def")
{1, "def", :hello, true}
# tuple大小
iex(10)> tuple_size(tuple)
4
# 尾部追加
iex(2)> Tuple.append(tuple, :world)
{1, "abc", :hello, true, :world}
# 删除指定下标的元素
iex(4)> Tuple.delete_at(tuple, 3)
{1, "abc", :hello}
# 插入元素(注意是插入,不会对已有的值进行更新)
iex(5)> Tuple.insert_at(tuple, 1, 25)
{1, 25, "abc", :hello, true}
# 用指定的元素和大小来创建tuple
iex(6)> Tuple.duplicate("hello", 5)
{"hello", "hello", "hello", "hello", "hello"}

性能

在前面的 List 文章里我们对比过头部追加和尾部追加时候的性能差异,这次我们看看当使用 tuple 的时候跟 list 有哪些的不同。

defmodule Recursion do
  def prepend_elem(tuple, elem, n) when n <= 1 do
    Tuple.insert_at(tuple, 0, elem)
  end

  def prepend_elem(tuple, elem, n) do
    tuple = Tuple.insert_at(tuple, 0, elem)
    prepend_elem(tuple, elem, n - 1)
  end

  def append_elem(tuple, elem, n) when n <= 1 do
    Tuple.insert_at(tuple, tuple_size(tuple), elem)
  end

  def append_elem(tuple, elem, n) do
    tuple = Tuple.insert_at(tuple, tuple_size(tuple), elem)
    append_elem(tuple, elem, n - 1)
  end
end
IO.puts "start prepend"
prepend_start_time = Time.utc_now |> Time.to_string
Recursion.prepend_elem({}, :a, 1_00_000)
prepend_end_time = Time.utc_now |> Time.to_string
IO.puts "start at #{prepend_start_time}" 
IO.puts "end at #{prepend_end_time}"
IO.puts "------------------------"
IO.puts "start append"
append_start_time = Time.utc_now |> Time.to_string
Recursion.append_elem({}, :b, 100000)
append_end_time = Time.utc_now |> Time.to_string
IO.puts "start at #{append_start_time}" 
IO.puts "end at #{append_end_time}"
tuple_10000 = Tuple.duplicate(:a, 10000)
tuple_1000000 = Tuple.duplicate(:a, 1000000)
IO.puts "------------------------"
IO.puts "start get element"
tuple10000_start_time = Time.utc_now |> Time.to_string
elem(tuple_10000, 5000)
tuple10000_end_time = Time.utc_now |> Time.to_string
IO.puts "access tuple(10000) at #{tuple10000_start_time}" 
IO.puts "end tuple(10000) at #{tuple10000_end_time}"
tuple1000000_start_time = Time.utc_now |> Time.to_string
elem(tuple_1000000, 500000)
tuple1000000_end_time = Time.utc_now |> Time.to_string
IO.puts "access tuple(1000000) at #{tuple1000000_start_time}" 
IO.puts "end tuple(1000000) at #{tuple1000000_end_time}"

start prepend
start at 15:41:26.662224
end at 15:41:35.553280
------------------------
start append
start at 15:41:35.553460
end at 15:41:44.633456
------------------------
start get element
access tuple(10000) at 15:41:44.643669
end tuple(10000) at 15:41:44.643698
access tuple(1000000) at 15:41:44.643723
end tuple(1000000) at 15:41:44.643729

我们首先测试了当追加 10 万条元素的时候,头部追加和尾部追加的性能。结果发现性能基本相同,不像 List 有很大的性能差异性。 后来又测试了不同的数据量的时候的访问的性能,结果也是一样,跟上面提的访问某一个元素是固定消耗的观点相吻合。

学习笔记写到博客,然后这里更新目录,不要一篇一篇往论坛贴。

skpark1987 关闭了讨论。 09月19日 10:21
需要 登录 后方可回复, 如果你还没有账号请 注册新账号