1 /** 2 Utility functions for array processing 3 4 Copyright: © 2012 Sönke Ludwig 5 License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. 6 Authors: Sönke Ludwig 7 */ 8 module vibe.utils.array; 9 10 import vibe.internal.utilallocator; 11 12 import std.algorithm; 13 import std.range : isInputRange, isOutputRange; 14 import std.traits; 15 static import std.utf; 16 17 18 deprecated("This function was accidentally exposed and should not be used") 19 void removeFromArray(T)(ref T[] array, T item) 20 { 21 foreach( i; 0 .. array.length ) 22 if( array[i] is item ){ 23 removeFromArrayIdx(array, i); 24 return; 25 } 26 } 27 28 deprecated("This function was accidentally exposed and should not be used") 29 void removeFromArrayIdx(T)(ref T[] array, size_t idx) 30 { 31 foreach( j; idx+1 .. array.length) 32 array[j-1] = array[j]; 33 array.length = array.length-1; 34 } 35 36 enum AppenderResetMode { 37 keepData, 38 freeData, 39 reuseData 40 } 41 42 struct AllocAppender(ArrayType : E[], E) { 43 alias ElemType = Unqual!E; 44 45 static assert(!hasIndirections!E && !hasElaborateDestructor!E); 46 47 private { 48 ElemType[] m_data; 49 ElemType[] m_remaining; 50 IAllocator m_alloc; 51 bool m_allocatedBuffer = false; 52 } 53 54 this(IAllocator alloc, ElemType[] initial_buffer = null) 55 { 56 m_alloc = alloc; 57 m_data = initial_buffer; 58 m_remaining = initial_buffer; 59 } 60 61 @disable this(this); 62 63 @property ArrayType data() { return cast(ArrayType)m_data[0 .. m_data.length - m_remaining.length]; } 64 65 void reset(AppenderResetMode reset_mode = AppenderResetMode.keepData) 66 { 67 if (reset_mode == AppenderResetMode.keepData) m_data = null; 68 else if (reset_mode == AppenderResetMode.freeData) { if (m_allocatedBuffer) m_alloc.deallocate(m_data); m_data = null; } 69 m_remaining = m_data; 70 } 71 72 /** Grows the capacity of the internal buffer so that it can hold a minumum amount of elements. 73 74 Params: 75 amount = The minimum amount of elements that shall be appendable without 76 triggering a re-allocation. 77 78 */ 79 void reserve(size_t amount) 80 @trusted { 81 size_t nelems = m_data.length - m_remaining.length; 82 if (!m_data.length) { 83 m_data = cast(ElemType[])m_alloc.allocate(amount*E.sizeof); 84 m_remaining = m_data; 85 m_allocatedBuffer = true; 86 } 87 if (m_remaining.length < amount) { 88 if (m_allocatedBuffer) { 89 void[] vdata = m_data; 90 m_alloc.reallocate(vdata, (nelems+amount)*E.sizeof); 91 m_data = () @trusted { return cast(ElemType[])vdata; } (); 92 } else { 93 auto newdata = cast(ElemType[])m_alloc.allocate((nelems+amount)*E.sizeof); 94 newdata[0 .. nelems] = m_data[0 .. nelems]; 95 m_data = newdata; 96 m_allocatedBuffer = true; 97 } 98 } 99 m_remaining = m_data[nelems .. m_data.length]; 100 } 101 102 void put(E el) 103 @safe { 104 if( m_remaining.length == 0 ) grow(1); 105 m_remaining[0] = el; 106 m_remaining = m_remaining[1 .. $]; 107 } 108 109 void put(ArrayType arr) 110 @safe { 111 if (m_remaining.length < arr.length) grow(arr.length); 112 m_remaining[0 .. arr.length] = arr[]; 113 m_remaining = m_remaining[arr.length .. $]; 114 } 115 116 static if( !hasAliasing!E ){ 117 void put(in ElemType[] arr) 118 @trusted 119 { 120 put(cast(ArrayType)arr); 121 } 122 } 123 124 static if( is(ElemType == char) ){ 125 void put(dchar el) 126 @safe 127 { 128 if( el < 128 ) put(cast(char)el); 129 else { 130 char[4] buf; 131 auto len = std.utf.encode(buf, el); 132 put(() @trusted { return cast(ArrayType)buf[0 .. len]; }()); 133 } 134 } 135 } 136 137 static if( is(ElemType == wchar) ){ 138 void put(dchar el) 139 @safe 140 { 141 if( el < 128 ) put(cast(wchar)el); 142 else { 143 wchar[3] buf; 144 auto len = std.utf.encode(buf, el); 145 put(() @trusted { return cast(ArrayType)buf[0 .. len]; } ()); 146 } 147 } 148 } 149 150 static if (!is(E == immutable) || !hasAliasing!E) { 151 /** Appends a number of bytes in-place. 152 153 The delegate will get the memory slice of the memory that follows 154 the already written data. Use `reserve` to ensure that this slice 155 has enough room. The delegate should overwrite as much of the 156 slice as desired and then has to return the number of elements 157 that should be appended (counting from the start of the slice). 158 */ 159 void append(scope size_t delegate(scope ElemType[] dst) @safe del) 160 { 161 auto n = del(m_remaining); 162 assert(n <= m_remaining.length); 163 m_remaining = m_remaining[n .. $]; 164 } 165 } 166 167 void grow(size_t min_free) 168 { 169 if( !m_data.length && min_free < 16 ) min_free = 16; 170 171 auto min_size = m_data.length + min_free - m_remaining.length; 172 auto new_size = max(m_data.length, 16); 173 while( new_size < min_size ) 174 new_size = (new_size * 3) / 2; 175 reserve(new_size - m_data.length + m_remaining.length); 176 } 177 } 178 179 unittest { 180 auto a = AllocAppender!string(theAllocator()); 181 a.put("Hello"); 182 a.put(' '); 183 a.put("World"); 184 assert(a.data == "Hello World"); 185 a.reset(); 186 assert(a.data == ""); 187 } 188 189 unittest { 190 char[4] buf; 191 auto a = AllocAppender!string(theAllocator(), buf); 192 a.put("He"); 193 assert(a.data == "He"); 194 assert(a.data.ptr == buf.ptr); 195 a.put("ll"); 196 assert(a.data == "Hell"); 197 assert(a.data.ptr == buf.ptr); 198 a.put('o'); 199 assert(a.data == "Hello"); 200 assert(a.data.ptr != buf.ptr); 201 } 202 203 unittest { 204 char[4] buf; 205 auto a = AllocAppender!string(theAllocator(), buf); 206 a.put("Hello"); 207 assert(a.data == "Hello"); 208 assert(a.data.ptr != buf.ptr); 209 } 210 211 unittest { 212 auto app = AllocAppender!(int[])(theAllocator); 213 app.reserve(2); 214 app.append((scope mem) { 215 assert(mem.length >= 2); 216 mem[0] = 1; 217 mem[1] = 2; 218 return size_t(2); 219 }); 220 assert(app.data == [1, 2]); 221 } 222 223 unittest { 224 auto app = AllocAppender!string(theAllocator); 225 app.reserve(3); 226 app.append((scope mem) { 227 assert(mem.length >= 3); 228 mem[0] = 'f'; 229 mem[1] = 'o'; 230 mem[2] = 'o'; 231 return size_t(3); 232 }); 233 assert(app.data == "foo"); 234 } 235 236 237 struct FixedAppender(ArrayType : E[], size_t NELEM, E) { 238 alias ElemType = Unqual!E; 239 private { 240 ElemType[NELEM] m_data; 241 size_t m_fill; 242 } 243 244 void clear() 245 { 246 m_fill = 0; 247 } 248 249 void put(E el) 250 { 251 m_data[m_fill++] = el; 252 } 253 254 static if( is(ElemType == char) ){ 255 void put(dchar el) 256 { 257 if( el < 128 ) put(cast(char)el); 258 else { 259 char[4] buf; 260 auto len = std.utf.encode(buf, el); 261 put(cast(ArrayType)buf[0 .. len]); 262 } 263 } 264 } 265 266 static if( is(ElemType == wchar) ){ 267 void put(dchar el) 268 { 269 if( el < 128 ) put(cast(wchar)el); 270 else { 271 wchar[3] buf; 272 auto len = std.utf.encode(buf, el); 273 put(cast(ArrayType)buf[0 .. len]); 274 } 275 } 276 } 277 278 void put(ArrayType arr) 279 { 280 m_data[m_fill .. m_fill+arr.length] = arr[]; 281 m_fill += arr.length; 282 } 283 284 @property ArrayType data() { return cast(ArrayType)m_data[0 .. m_fill]; } 285 286 static if (!is(E == immutable)) { 287 void reset() { m_fill = 0; } 288 } 289 } 290 291 292 /** 293 TODO: clear ring buffer fields upon removal (to run struct destructors, if T is a struct) 294 */ 295 struct FixedRingBuffer(T, size_t N = 0, bool INITIALIZE = true) { 296 private { 297 static if( N > 0 ) { 298 static if (INITIALIZE) T[N] m_buffer; 299 else T[N] m_buffer = void; 300 } else T[] m_buffer; 301 size_t m_start = 0; 302 size_t m_fill = 0; 303 } 304 305 static if( N == 0 ){ 306 this(size_t capacity) { m_buffer = new T[capacity]; } 307 } 308 309 @property bool empty() const { return m_fill == 0; } 310 311 @property bool full() const { return m_fill == m_buffer.length; } 312 313 @property size_t length() const { return m_fill; } 314 315 @property size_t freeSpace() const { return m_buffer.length - m_fill; } 316 317 @property size_t capacity() const { return m_buffer.length; } 318 319 static if( N == 0 ){ 320 /// Resets the capacity to zero and explicitly frees the memory for the buffer. 321 void dispose() 322 { 323 import core.memory : __delete; 324 __delete(m_buffer); 325 m_buffer = null; 326 m_start = m_fill = 0; 327 } 328 329 @property void capacity(size_t new_size) 330 { 331 if( m_buffer.length ){ 332 auto newbuffer = new T[new_size]; 333 auto dst = newbuffer; 334 auto newfill = min(m_fill, new_size); 335 read(dst[0 .. newfill]); 336 m_buffer = newbuffer; 337 m_start = 0; 338 m_fill = newfill; 339 } else { 340 m_buffer = new T[new_size]; 341 } 342 } 343 } 344 345 @property ref inout(T) front() inout { assert(!empty); return m_buffer[m_start]; } 346 347 @property ref inout(T) back() inout { assert(!empty); return m_buffer[mod(m_start+m_fill-1)]; } 348 349 void clear() 350 { 351 popFrontN(length); 352 assert(m_fill == 0); 353 m_start = 0; 354 } 355 356 void putBack()(T itm) { assert(m_fill < m_buffer.length); m_buffer[mod(m_start + m_fill++)] = itm; } 357 void putBack(TC : T)(TC[] itms) 358 { 359 if( !itms.length ) return; 360 assert(m_fill+itms.length <= m_buffer.length); 361 if( mod(m_start+m_fill) >= mod(m_start+m_fill+itms.length) ){ 362 size_t chunk1 = m_buffer.length - (m_start+m_fill); 363 size_t chunk2 = itms.length - chunk1; 364 m_buffer[m_start+m_fill .. m_buffer.length] = itms[0 .. chunk1]; 365 m_buffer[0 .. chunk2] = itms[chunk1 .. $]; 366 } else { 367 m_buffer[mod(m_start+m_fill) .. mod(m_start+m_fill)+itms.length] = itms[]; 368 } 369 m_fill += itms.length; 370 } 371 void putBackN(size_t n) { assert(m_fill+n <= m_buffer.length); m_fill += n; } 372 373 alias put = putBack; 374 alias putN = putBackN; 375 376 void putFront(T itm) 377 { 378 assert(m_fill < m_buffer.length); 379 m_start = mod(m_start + m_buffer.length - 1); 380 m_fill++; 381 m_buffer[m_start] = itm; 382 } 383 384 void popFront() { assert(!empty); m_start = mod(m_start+1); m_fill--; } 385 void popFrontN(size_t n) { assert(length >= n); m_start = mod(m_start + n); m_fill -= n; } 386 387 void popBack() { assert(!empty); m_fill--; } 388 void popBackN(size_t n) { assert(length >= n); m_fill -= n; } 389 390 void removeAt(Range r) 391 { 392 assert(r.m_buffer is m_buffer); 393 if (r.m_start == m_start) { popFront(); return; } 394 if( m_start + m_fill > m_buffer.length ){ 395 assert(r.m_start > m_start && r.m_start < m_buffer.length || r.m_start < mod(m_start+m_fill)); 396 if( r.m_start > m_start ){ 397 foreach(i; r.m_start .. m_buffer.length-1) 398 m_buffer[i] = m_buffer[i+1]; 399 m_buffer[$-1] = m_buffer[0]; 400 foreach(i; 0 .. mod(m_start + m_fill - 1)) 401 m_buffer[i] = m_buffer[i+1]; 402 } else { 403 foreach(i; r.m_start .. mod(m_start + m_fill - 1)) 404 m_buffer[i] = m_buffer[i+1]; 405 } 406 } else { 407 assert(r.m_start >= m_start && r.m_start < m_start+m_fill); 408 foreach(i; r.m_start .. m_start+m_fill-1) 409 m_buffer[i] = m_buffer[i+1]; 410 } 411 m_fill--; 412 destroy(m_buffer[mod(m_start+m_fill)]); // TODO: only call destroy for non-POD T 413 } 414 415 inout(T)[] peek() inout { return m_buffer[m_start .. min(m_start+m_fill, m_buffer.length)]; } 416 T[] peekDst() { 417 if( m_start + m_fill < m_buffer.length ) return m_buffer[m_start+m_fill .. $]; 418 else return m_buffer[mod(m_start+m_fill) .. m_start]; 419 } 420 421 void read(T[] dst) 422 { 423 assert(dst.length <= length); 424 if( !dst.length ) return; 425 if( mod(m_start) >= mod(m_start+dst.length) ){ 426 size_t chunk1 = m_buffer.length - m_start; 427 size_t chunk2 = dst.length - chunk1; 428 dst[0 .. chunk1] = m_buffer[m_start .. $]; 429 dst[chunk1 .. $] = m_buffer[0 .. chunk2]; 430 } else { 431 dst[] = m_buffer[m_start .. m_start+dst.length]; 432 } 433 popFrontN(dst.length); 434 } 435 436 int opApply(scope int delegate(ref T itm) @safe del) 437 { 438 if( m_start+m_fill > m_buffer.length ){ 439 foreach(i; m_start .. m_buffer.length) 440 if( auto ret = del(m_buffer[i]) ) 441 return ret; 442 foreach(i; 0 .. mod(m_start+m_fill)) 443 if( auto ret = del(m_buffer[i]) ) 444 return ret; 445 } else { 446 foreach(i; m_start .. m_start+m_fill) 447 if( auto ret = del(m_buffer[i]) ) 448 return ret; 449 } 450 return 0; 451 } 452 453 /// iterate through elements with index 454 int opApply(scope int delegate(size_t i, ref T itm) @safe del) 455 { 456 if( m_start+m_fill > m_buffer.length ){ 457 foreach(i; m_start .. m_buffer.length) 458 if( auto ret = del(i - m_start, m_buffer[i]) ) 459 return ret; 460 foreach(i; 0 .. mod(m_start+m_fill)) 461 if( auto ret = del(i + m_buffer.length - m_start, m_buffer[i]) ) 462 return ret; 463 } else { 464 foreach(i; m_start .. m_start+m_fill) 465 if( auto ret = del(i - m_start, m_buffer[i]) ) 466 return ret; 467 } 468 return 0; 469 } 470 471 ref inout(T) opIndex(size_t idx) inout { assert(idx < length); return m_buffer[mod(m_start+idx)]; } 472 473 Range opSlice() { return Range(m_buffer, m_start, m_fill); } 474 475 Range opSlice(size_t from, size_t to) 476 { 477 assert(from <= to); 478 assert(to <= m_fill); 479 return Range(m_buffer, mod(m_start+from), to-from); 480 } 481 482 size_t opDollar(size_t dim)() const if(dim == 0) { return length; } 483 484 private size_t mod(size_t n) 485 const { 486 static if( N == 0 ){ 487 /*static if(PotOnly){ 488 return x & (m_buffer.length-1); 489 } else {*/ 490 return n % m_buffer.length; 491 //} 492 } else static if( ((N - 1) & N) == 0 ){ 493 return n & (N - 1); 494 } else return n % N; 495 } 496 497 static struct Range { 498 private { 499 T[] m_buffer; 500 size_t m_start; 501 size_t m_length; 502 } 503 504 private this(T[] buffer, size_t start, size_t length) 505 { 506 m_buffer = buffer; 507 m_start = start; 508 m_length = length; 509 } 510 511 @property bool empty() const { return m_length == 0; } 512 513 @property ref inout(T) front() inout { assert(!empty); return m_buffer[m_start]; } 514 515 void popFront() 516 { 517 assert(!empty); 518 m_start++; 519 m_length--; 520 if( m_start >= m_buffer.length ) 521 m_start = 0; 522 } 523 } 524 } 525 526 unittest { 527 import std.range : only; 528 529 static assert(isInputRange!(FixedRingBuffer!int) && isOutputRange!(FixedRingBuffer!int, int)); 530 531 FixedRingBuffer!(int, 5) buf; 532 assert(buf.length == 0 && buf.freeSpace == 5); buf.put(1); // |1 . . . . 533 assert(buf.length == 1 && buf.freeSpace == 4); buf.put(2); // |1 2 . . . 534 assert(buf.length == 2 && buf.freeSpace == 3); buf.put(3); // |1 2 3 . . 535 assert(buf.length == 3 && buf.freeSpace == 2); buf.put(4); // |1 2 3 4 . 536 assert(buf.length == 4 && buf.freeSpace == 1); buf.put(5); // |1 2 3 4 5 537 assert(buf.length == 5 && buf.freeSpace == 0); 538 assert(buf.front == 1); 539 buf.popFront(); // .|2 3 4 5 540 assert(buf.front == 2); 541 buf.popFrontN(2); // . . .|4 5 542 assert(buf.front == 4); 543 assert(buf.length == 2 && buf.freeSpace == 3); 544 buf.put([6, 7, 8]); // 6 7 8|4 5 545 assert(buf.length == 5 && buf.freeSpace == 0); 546 int[5] dst; 547 buf.read(dst); // . . .|. . 548 assert(dst == [4, 5, 6, 7, 8]); 549 assert(buf.length == 0 && buf.freeSpace == 5); 550 buf.put([1, 2]); // . . .|1 2 551 assert(buf.length == 2 && buf.freeSpace == 3); 552 buf.read(dst[0 .. 2]); //|. . . . . 553 assert(dst[0 .. 2] == [1, 2]); 554 555 buf.put([0, 0, 0, 1, 2]); //|0 0 0 1 2 556 buf.popFrontN(2); //. .|0 1 2 557 buf.put([3, 4]); // 3 4|0 1 2 558 foreach(i, item; buf) 559 { 560 assert(i == item); 561 } 562 563 assert(buf.front == 0); 564 assert(buf.full); 565 buf.removeAt(buf[0..1]); //4 .|1 2 3 566 foreach(i, item; buf) 567 { 568 assert(i == item - 1); 569 } 570 571 buf.put(5); // 4 5|1 2 3 572 buf.removeAt(buf[3..4]); // 5 .|1 2 3 573 assert(buf.front == 1); buf.popFront(); 574 assert(buf.front == 2); buf.popFront(); 575 assert(buf.front == 3); buf.popFront(); 576 assert(buf.front == 5); buf.popFront(); 577 assert(buf.empty); 578 579 buf.put(1); 580 assert(buf.front == 1); 581 buf.front = 2; 582 assert(buf.front == 2); 583 buf[].front = 3; 584 assert(buf.front == 3); 585 586 buf.putFront(4); 587 assert(buf.length == 2); 588 assert(buf[].equal(only(4, 3))); 589 } 590 591 592 struct ArraySet(Key) 593 { 594 import stdx.allocator : makeArray, expandArray, dispose; 595 import stdx.allocator.building_blocks.affix_allocator : AffixAllocator; 596 597 private { 598 IAllocator AW(IAllocator a) { return a; } 599 alias AllocatorType = AffixAllocator!(IAllocator, int); 600 Key[4] m_staticEntries; 601 Key[] m_entries; 602 AllocatorType m_allocator; 603 } 604 605 ~this() 606 @trusted { 607 if (m_entries.ptr) { 608 if (--allocator.prefix(m_entries) <= 0) { 609 try allocator.dispose(m_entries); 610 catch (Exception e) assert(false, e.msg); // should never happen 611 } 612 } 613 } 614 615 this(this) 616 @trusted { 617 if (m_entries.ptr) { 618 allocator.prefix(m_entries)++; 619 } 620 } 621 622 @property ArraySet dup() 623 { 624 ArraySet ret; 625 ret.m_staticEntries = m_staticEntries; 626 ret.m_allocator = m_allocator; 627 628 if (m_entries.length) { 629 Key[] duped; 630 () @trusted { 631 try duped = allocator.makeArray!(Key)(m_entries.length); 632 catch (Exception e) assert(false, e.msg); 633 if (!duped.length) 634 assert(false, "Failed to allocate memory for duplicated "~ArraySet.stringof); 635 allocator.prefix(duped) = 1; 636 } (); 637 duped[] = m_entries[]; 638 ret.m_entries = duped; 639 } 640 641 return ret; 642 } 643 644 void setAllocator(IAllocator allocator) 645 in { assert(m_entries.ptr is null, "Cannot set allocator after elements have been inserted."); } 646 do { 647 m_allocator = AllocatorType(AW(allocator)); 648 } 649 650 bool opBinaryRight(string op)(Key key) if (op == "in") { return contains(key); } 651 652 int opApply(int delegate(ref Key) @safe del) 653 { 654 foreach (ref k; m_staticEntries) 655 if (k != Key.init) 656 if (auto ret = del(k)) 657 return ret; 658 foreach (ref k; m_entries) 659 if (k != Key.init) 660 if (auto ret = del(k)) 661 return ret; 662 return 0; 663 } 664 665 bool contains(Key key) 666 const { 667 foreach (ref k; m_staticEntries) if (k == key) return true; 668 foreach (ref k; m_entries) if (k == key) return true; 669 return false; 670 } 671 672 void insert(Key key) 673 { 674 if (contains(key)) return; 675 676 foreach (ref k; m_staticEntries) 677 if (k == Key.init) { 678 k = key; 679 return; 680 } 681 682 foreach (ref k; m_entries) 683 if (k == Key.init) { 684 k = key; 685 return; 686 } 687 688 size_t oldlen = m_entries.length; 689 690 () @trusted { 691 try { 692 if (!oldlen) { 693 m_entries = allocator.makeArray!Key(64); 694 assert(m_entries.length, "Failed to allocate memory for "~ArraySet.stringof); 695 allocator.prefix(m_entries) = 1; 696 } else { 697 int oldrc = allocator.prefix(m_entries); 698 if (!allocator.expandArray(m_entries, max(64, oldlen * 3 / 4))) 699 assert(false, "Failed to allocate memory for "~ArraySet.stringof); 700 allocator.prefix(m_entries) = oldrc; 701 } 702 } catch (Exception e) assert(false, e.msg); 703 } (); 704 705 m_entries[oldlen] = key; 706 } 707 708 void remove(Key key) 709 { 710 foreach (ref k; m_staticEntries) if (k == key) { k = Key.init; return; } 711 foreach (ref k; m_entries) if (k == key) { k = Key.init; return; } 712 } 713 714 ref allocator() 715 nothrow @trusted { 716 try { 717 auto palloc = m_allocator._parent; 718 if (!palloc) { 719 assert(vibeThreadAllocator !is null, "No theAllocator set!?"); 720 m_allocator = AllocatorType(AW(vibeThreadAllocator)); 721 } 722 } catch (Exception e) assert(false, e.msg); // should never throw 723 return m_allocator; 724 } 725 } 726 727 @safe nothrow unittest { 728 import stdx.allocator : allocatorObject; 729 import stdx.allocator.mallocator : Mallocator; 730 731 ArraySet!int s; 732 s.setAllocator(() @trusted { return Mallocator.instance.allocatorObject; } ()); 733 734 ArraySet!int t; 735 t = s; 736 737 s.insert(1); 738 s.insert(2); 739 s.insert(3); 740 s.insert(4); 741 assert(s.contains(1)); 742 assert(s.contains(2)); 743 assert(s.contains(3)); 744 assert(s.contains(4)); 745 assert(!t.contains(1)); 746 747 s.insert(5); 748 assert(s.contains(5)); 749 750 t = s; 751 assert(t.contains(5)); 752 assert(t.contains(1)); 753 754 s.insert(6); 755 assert(s.contains(6)); 756 assert(t.contains(6)); 757 758 s = ArraySet!int.init; 759 assert(!s.contains(1)); 760 assert(t.contains(1)); 761 assert(t.contains(6)); 762 763 s = t.dup; 764 assert(s.contains(1)); 765 assert(s.contains(6)); 766 767 t.remove(1); 768 assert(!t.contains(1)); 769 assert(s.contains(1)); 770 assert(t.contains(2)); 771 assert(t.contains(6)); 772 773 t.remove(6); 774 assert(!t.contains(6)); 775 assert(s.contains(6)); 776 assert(t.contains(5)); 777 }