一起学习PHP中的DS数据结构扩展(一)

2021-12-06 14:45:38 浏览数 (1)

一起学习PHP中的DS数据结构扩展(一)

在之前学习 SPL 相关的文章中,我们已经学习过 SPL 中的一些数据结构相关的数据结构对象,非常强大也非常好用,最主要的是 SPL 已经集成在 PHP 源码中不需要我们再单独地安装别的什么扩展。但是,今天我们要学习的这个 DataStruct 扩展库的内容则更加地丰富,不过相对应的,这套扩展是需要我们自己手动再进行安装的。如果大家对于数据结构的需求不高的话,使用 SPL 中的相关对象就够用了,但是如果需要更加丰富的数据结构类型的话,这套 DS 扩展是更好的选择。

DS 扩展的安装和其它普通的扩展安装没有什么区别,也不需要额外的操作系统上的组件支持,直接安装即可。

首先还是从栈这个最基本的数据结构说起。DS 中的栈结构非常地简单好用。

代码语言:javascript复制
$stack = new DsStack();
var_dump($stack);
// object(DsStack)#1 (0) {
// }

$stack = new DsStack([1, 2, 3]);
var_dump($stack);
// object(DsStack)#2 (3) {
//     [0]=>
//     int(3)
//     [1]=>
//     int(2)
//     [2]=>
//     int(1)
//   }

两种方式实例化栈对象,其实就是参数的不同,如果我们直接给构造函数传递一个数组的话,那么这个数组就会做为栈内部的元素供我们使用。

代码语言:javascript复制
$stack->push(4);
$stack->push(5);
var_dump($stack);
// object(DsStack)#2 (5) {
//     [0]=>
//     int(5)
//     [1]=>
//     int(4)
//     [2]=>
//     int(3)
//     [3]=>
//     int(2)
//     [4]=>
//     int(1)
//   }

var_dump($stack->pop()); // int(5)
var_dump($stack->pop()); // int(4)
var_dump($stack);
// object(DsStack)#2 (3) {
//     [0]=>
//     int(3)
//     [1]=>
//     int(2)
//     [2]=>
//     int(1)
//   }

push() 就是将数据压栈,pop() 则是将栈顶的元素弹出。关于栈的最主要的操作其实就是这两个方法函数了。

代码语言:javascript复制
var_dump($stack->peek()); // int(3)
// object(DsStack)#2 (3) {
//     [0]=>
//     int(3)
//     [1]=>
//     int(2)
//     [2]=>
//     int(1)
//   }

peek() 这个函数是直接获取栈顶的数据,但是需要注意的是,它不会弹出栈顶的元素。也就是说,这个 peek() 方法只会取得数据的内容,不会改变栈内部的数据。

代码语言:javascript复制
var_dump($stack->count()); // int(3)
var_dump($stack->isEmpty()); // bool(false)
var_dump($stack->toArray());
// array(3) {
//     [0]=>
//     int(3)
//     [1]=>
//     int(2)
//     [2]=>
//     int(1)
//   }

$stack->clear();
var_dump($stack);
// object(DsStack)#2 (0) {
// }

count() 返回栈内部元素的数量,isEmpty() 用于判断栈是否为空,toArray() 直接以数组的格式返回栈内部的数据,clear() 方法用于清空栈。这些方法函数都非常地简单,所以就不多做解释了。最后我们来看看栈对象的赋值拷贝操作。

代码语言:javascript复制
$a = $stack;
$a->push(4);
var_dump($stack);
// object(DsStack)#2 (1) {
//     [0]=>
//     int(4)
//   }

$b = $stack->copy();
$b->push(5);

var_dump($stack);
// object(DsStack)#2 (1) {
//     [0]=>
//     int(4)
//   }

var_dump($b);
// object(DsStack)#1 (2) {
//     [0]=>
//     int(5)
//     [1]=>
//     int(4)
//   }

stack 对象是实例化之后的对象,在普通的赋值操作中是引用传递的。上文中我们清空了 stack ,然后在这里我们让 a 等于这个 stack ,然后操作 a 相应地 stack 里面的内容也发生了变化。对于引用传递这个问题,我们一般会使用 __clone() 魔术方法来解决, Stack 类直接就为我们提供了一个 copy() 方法,直接可以获得一个栈对象的拷贝,也可以说是一个新的栈对象。就像上面代码中的 b 一样,当使用 copy() 方法赋值给 b 之后,它就成为了一个新的栈对象,任何 b 的操作和 stack 对象就没有什么关系了。我们可以看到 对象id 也完全不同了。

队列

对于队列来说,整体上的功能方法和栈的内容差不多,它们实现的方法基本上是一模一样的。具体在实现层面上的不同就体现在弹栈和出队的不同,也就是 push() 方法在实现中有差别。

代码语言:javascript复制
$queue = new DsQueue();
var_dump($queue);
// object(DsQueue)#3 (0) {
// }

$queue = new DsQueue([1, 2, 3]);
var_dump($queue);
// object(DsQueue)#4 (3) {
//     [0]=>
//     int(1)
//     [1]=>
//     int(2)
//     [2]=>
//     int(3)
//   }

$queue->push(4);
$queue->push(5);
var_dump($queue);
// object(DsQueue)#4 (5) {
//     [0]=>
//     int(1)
//     [1]=>
//     int(2)
//     [2]=>
//     int(3)
//     [3]=>
//     int(4)
//     [4]=>
//     int(5)
//   }

var_dump($queue->pop()); // int(1)
var_dump($queue->pop()); // int(2)
var_dump($queue);
// object(DsQueue)#4 (3) {
//     [0]=>
//     int(3)
//     [1]=>
//     int(4)
//     [2]=>
//     int(5)
//   }

可以看出,在队列中,我们 push() 进来的数据的顺序是 1,2,3,4,5 这样正序的,也就是将数据放到内部这个数组的底部,出队 pop() 直接拿最顶上的数据也就实现了先进先出的效果。对比上面栈的数据内容,就可以发现栈的数据在 push() 的时候就是反过来的,5、4、3、2、1 这样的,然后在 pop() 的时候其实也是从顶部拿出数据,只不过栈是将数据 push() 到内部数组的顶部的,然后从顶部直接拿出数据实现了 后进先出 的效果。

优先队列

最重要的两个数据结构说完了,我们再来看一个队列的扩展结构,也就是优先队列的实现。其实这个队列就是在 push() 数据的时候多了一个参数,也就是数据的优先级,越大的越靠前,其它的方法和普通队列以及栈的方法都没什么太大的区别。

代码语言:javascript复制
$pQueue = new DsPriorityQueue();

$pQueue->push(1, 100);
$pQueue->push(2, 101);
$pQueue->push(3, 99);

var_dump($pQueue);
// object(DsPriorityQueue)#3 (3) {
//     [0]=>
//     int(2)
//     [1]=>
//     int(1)
//     [2]=>
//     int(3)
//   }

var_dump($pQueue->pop()); // int(2)
var_dump($pQueue->pop()); // int(1)
var_dump($pQueue->pop()); // int(3)

Map

最后我们来学习一个 Map 数据结构,其实也就是 HaspMap 这种 K/V 的键值对形式的数据结构。只能说 PHP 中的数组实在是太强大了,完全兼容了这种数据结构,所以使得单独的 Map 结构并没有什么实际的意义。

代码语言:javascript复制
$map = new DsMap(['a'=>1, 2, 5=>3]);
var_dump($map);
// object(DsMap)#5 (3) {
//     [0]=>
//     object(DsPair)#6 (2) {
//       ["key"]=>
//       string(1) "a"
//       ["value"]=>
//       int(1)
//     }
//     [1]=>
//     object(DsPair)#7 (2) {
//       ["key"]=>
//       int(0)
//       ["value"]=>
//       int(2)
//     }
//     [2]=>
//     object(DsPair)#8 (2) {
//       ["key"]=>
//       int(5)
//       ["value"]=>
//       int(3)
//     }
//   }


var_dump($map->get(0)); // int(2)
var_dump($map->get(5)); // int(3)

$map->put('b', '4');
$map->put('c', [1, 2, 3]);
$map->put('d', new class{public $t = 't';});

var_dump($map);
// object(DsMap)#5 (6) {
//     [0]=>
//     object(DsPair)#7 (2) {
//       ["key"]=>
//       string(1) "a"
//       ["value"]=>
//       int(1)
//     }
//     [1]=>
//     object(DsPair)#6 (2) {
//       ["key"]=>
//       int(0)
//       ["value"]=>
//       int(2)
//     }
//     [2]=>
//     object(DsPair)#9 (2) {
//       ["key"]=>
//       int(5)
//       ["value"]=>
//       int(3)
//     }
//     [3]=>
//     object(DsPair)#10 (2) {
//       ["key"]=>
//       string(1) "b"
//       ["value"]=>
//       string(1) "4"
//     }
//     [4]=>
//     object(DsPair)#11 (2) {
//       ["key"]=>
//       string(1) "c"
//       ["value"]=>
//       array(3) {
//         [0]=>
//         int(1)
//         [1]=>
//         int(2)
//         [2]=>
//         int(3)
//       }
//     }
//     [5]=>
//     object(DsPair)#12 (2) {
//       ["key"]=>
//       string(1) "d"
//       ["value"]=>
//       object(class@anonymous)#8 (1) {
//         ["t"]=>
//         string(1) "t"
//       }
//     }
//   }

$map->remove('d');
$map->remove('c');

var_dump($map);
// object(DsMap)#5 (4) {
//     [0]=>
//     object(DsPair)#8 (2) {
//       ["key"]=>
//       string(1) "a"
//       ["value"]=>
//       int(1)
//     }
//     [1]=>
//     object(DsPair)#12 (2) {
//       ["key"]=>
//       int(0)
//       ["value"]=>
//       int(2)
//     }
//     [2]=>
//     object(DsPair)#11 (2) {
//       ["key"]=>
//       int(5)
//       ["value"]=>
//       int(3)
//     }
//     [3]=>
//     object(DsPair)#10 (2) {
//       ["key"]=>
//       string(1) "b"
//       ["value"]=>
//       string(1) "4"
//     }
//   }

在 Java 之类的语言中,数组 和 HashMap 是两种东西,或者说是两种集合对象,比如 List<Obj> 就是一个数据集合,而 Map<Obj> 就是一个 HashMap 集合。相对应的,在 Java 的这种泛型集合中,我们需要添加和获取数据就要像这个 DS 扩展中的 Map 一样使用 get()、put()、remove() 类似的方法来实现。

Map 这个数据结构与上面的栈、队列之类的数据结构中实现的方法差别还是挺大的。

代码语言:javascript复制
var_dump($map->first());
// object(DsPair)#8 (2) {
//     ["key"]=>
//     string(1) "a"
//     ["value"]=>
//     int(1)
//   }

var_dump($map->last());
// object(DsPair)#8 (2) {
//     ["key"]=>
//     int(5)
//     ["value"]=>
//     int(3)
//   }

var_dump($map->sum()); // int(10)

var_dump($map->hasKey('b')); // bool(true)
var_dump($map->haskey('bb')); // bool(false)

var_dump($map->hasValue('4')); // bool(true)
var_dump($map->hasValue(4)); // bool(false)

它有 first() 和 last() 方法直接获取第一个和最后一个数据元素。也有 sum() 方法获得数据元素的个数,同时可以通过 hasKey() 和 hasValue() 来判断指定的键或者值是存在。是不是有点像 key_exists() 和 in_array() 这两个方法。当然,相对应的我们也可以直接获取这些 Key 和 Value 的内容。

代码语言:javascript复制
var_dump($map->keys());
// object(DsSet)#10 (4) {
//     [0]=>
//     string(1) "a"
//     [1]=>
//     int(0)
//     [2]=>
//     int(5)
//     [3]=>
//     string(1) "b"
//   }

var_dump($map->values());
// object(DsVector)#10 (4) {
//     [0]=>
//     int(1)
//     [1]=>
//     int(2)
//     [2]=>
//     int(3)
//     [3]=>
//     string(1) "4"
//   }

我们可以看到,keys() 返回的内容是 Set 类型的对象,而 values() 返回的内容是 Vector 类型的对象,这两种也是 DS 中的数据结构类型,我们将在下篇文章中再学习。除了 Key 和 Values 之外,它还可以直接返回一个 Vector 类型的对象集合结构,使用 paris() 方法。

代码语言:javascript复制
var_dump($map->pairs());
// object(DsVector)#9 (4) {
//     [0]=>
//     object(DsPair)#10 (2) {
//       ["key"]=>
//       string(1) "a"
//       ["value"]=>
//       int(1)
//     }
//     [1]=>
//     object(DsPair)#11 (2) {
//       ["key"]=>
//       int(0)
//       ["value"]=>
//       int(2)
//     }
//     [2]=>
//     object(DsPair)#12 (2) {
//       ["key"]=>
//       int(5)
//       ["value"]=>
//       int(3)
//     }
//     [3]=>
//     object(DsPair)#8 (2) {
//       ["key"]=>
//       string(1) "b"
//       ["value"]=>
//       string(1) "4"
//     }
//   }

在 Map 对象中,还提供了一些为数据排序、合并两个 Map 以及截取一部分数据的方法,直接贴出代码吧。

代码语言:javascript复制
$map->ksort();
var_dump($map);
// object(DsMap)#5 (4) {
//     [0]=>
//     object(DsPair)#9 (2) {
//       ["key"]=>
//       string(1) "a"
//       ["value"]=>
//       int(1)
//     }
//     [1]=>
//     object(DsPair)#8 (2) {
//       ["key"]=>
//       int(0)
//       ["value"]=>
//       int(2)
//     }
//     [2]=>
//     object(DsPair)#12 (2) {
//       ["key"]=>
//       string(1) "b"
//       ["value"]=>
//       string(1) "4"
//     }
//     [3]=>
//     object(DsPair)#11 (2) {
//       ["key"]=>
//       int(5)
//       ["value"]=>
//       int(3)
//     }
//   }

$map->reverse();
var_dump($map);
// object(DsMap)#5 (4) {
//     [0]=>
//     object(DsPair)#11 (2) {
//       ["key"]=>
//       int(5)
//       ["value"]=>
//       int(3)
//     }
//     [1]=>
//     object(DsPair)#12 (2) {
//       ["key"]=>
//       string(1) "b"
//       ["value"]=>
//       string(1) "4"
//     }
//     [2]=>
//     object(DsPair)#8 (2) {
//       ["key"]=>
//       int(0)
//       ["value"]=>
//       int(2)
//     }
//     [3]=>
//     object(DsPair)#9 (2) {
//       ["key"]=>
//       string(1) "a"
//       ["value"]=>
//       int(1)
//     }
//   }

$newMap = new DsMap();
$newMap->put('a', 'a');
$newMap->put('b', 'b');
$newMap->put('e', 'e');

var_dump($map->diff($newMap));
// object(DsMap)#8 (2) {
//     [0]=>
//     object(DsPair)#12 (2) {
//       ["key"]=>
//       int(5)
//       ["value"]=>
//       int(3)
//     }
//     [1]=>
//     object(DsPair)#11 (2) {
//       ["key"]=>
//       int(0)
//       ["value"]=>
//       int(2)
//     }
//   }

var_dump($map->union($newMap));
// object(DsMap)#8 (5) {
//     [0]=>
//     object(DsPair)#11 (2) {
//       ["key"]=>
//       int(5)
//       ["value"]=>
//       int(3)
//     }
//     [1]=>
//     object(DsPair)#12 (2) {
//       ["key"]=>
//       string(1) "b"
//       ["value"]=>
//       string(1) "b"
//     }
//     [2]=>
//     object(DsPair)#10 (2) {
//       ["key"]=>
//       int(0)
//       ["value"]=>
//       int(2)
//     }
//     [3]=>
//     object(DsPair)#6 (2) {
//       ["key"]=>
//       string(1) "a"
//       ["value"]=>
//       string(1) "a"
//     }
//     [4]=>
//     object(DsPair)#7 (2) {
//       ["key"]=>
//       string(1) "e"
//       ["value"]=>
//       string(1) "e"
//     }
//   }

var_dump($map->xor($newMap));
// object(DsMap)#8 (3) {
//     [0]=>
//     object(DsPair)#7 (2) {
//       ["key"]=>
//       int(5)
//       ["value"]=>
//       int(3)
//     }
//     [1]=>
//     object(DsPair)#6 (2) {
//       ["key"]=>
//       int(0)
//       ["value"]=>
//       int(2)
//     }
//     [2]=>
//     object(DsPair)#10 (2) {
//       ["key"]=>
//       string(1) "e"
//       ["value"]=>
//       string(1) "e"
//     }
//   }

var_dump($map->intersect($newMap));
// object(DsMap)#8 (2) {
//     [0]=>
//     object(DsPair)#10 (2) {
//       ["key"]=>
//       string(1) "b"
//       ["value"]=>
//       string(1) "4"
//     }
//     [1]=>
//     object(DsPair)#6 (2) {
//       ["key"]=>
//       string(1) "a"
//       ["value"]=>
//       int(1)
//     }
//   }

$map->putAll($newMap);
var_dump($map);
// object(DsMap)#5 (5) {
//     [0]=>
//     object(DsPair)#8 (2) {
//       ["key"]=>
//       int(5)
//       ["value"]=>
//       int(3)
//     }
//     [1]=>
//     object(DsPair)#6 (2) {
//       ["key"]=>
//       string(1) "b"
//       ["value"]=>
//       string(1) "b"
//     }
//     [2]=>
//     object(DsPair)#10 (2) {
//       ["key"]=>
//       int(0)
//       ["value"]=>
//       int(2)
//     }
//     [3]=>
//     object(DsPair)#7 (2) {
//       ["key"]=>
//       string(1) "a"
//       ["value"]=>
//       string(1) "a"
//     }
//     [4]=>
//     object(DsPair)#12 (2) {
//       ["key"]=>
//       string(1) "e"
//       ["value"]=>
//       string(1) "e"
//     }
//   }

var_dump($map->slice(1, 2));
// object(DsMap)#12 (2) {
//     [0]=>
//     object(DsPair)#7 (2) {
//       ["key"]=>
//       string(1) "b"
//       ["value"]=>
//       string(1) "b"
//     }
//     [1]=>
//     object(DsPair)#10 (2) {
//       ["key"]=>
//       int(0)
//       ["value"]=>
//       int(2)
//     }
//   }

var_dump($map->skip(2));
// object(DsPair)#12 (2) {
//     ["key"]=>
//     int(0)
//     ["value"]=>
//     int(2)
//   }

代码内容很多,展示的注释内容也就是我们执行的结果。可以看出,Map 这个数据结构提供的方法功能真的是非常丰富的。而且我们这里还没有完全展示出来,它还有一些类似的方法,大家有兴趣的可以自己多多地去探索。不过就像上面说过的,PHP 中的数组实在是太方便了,所以这个 Map 的应用场景有限,或者某些特殊的必须需要对象来表示数组结构的场景会有用。

总结

是不是有点意思呀,就像在开头时我们说过的,了解学习可以,但如果真实业务中只是需要一些简单的栈或队列的实现的话,直接使用 SPL 扩展库中的数据结构就可以了。当然,DS 中的内容还没有讲完,Vector 和 Set 相信学习过 Java 的同学一定不陌生,下篇文章我们将继续学习 DS 中剩余的数据结构。

测试代码:

https://github.com/zhangyue0503/dev-blog/blob/master/php/2021/02/source/2.一起学习PHP中的DS数据结构扩展(一).php

参考文档:

https://www.php.net/manual/zh/book.ds.php

0 人点赞