authorgravatar for john.schmidt.h@gmail.comJohn Schmidt <john.schmidt.h@gmail.com> 2024-01-27 22:43:35+01:00
committergravatar for john.schmidt.h@gmail.comJohn Schmidt <john.schmidt.h@gmail.com> 2024-02-07 21:00:24+01:00
loge487b576fadc2fd48100667ff233e4a0bb4f0aa5
tree27084584f2dd67cb33f5903d2c950f5149f6ce04
parent664c18544c2622352a4e38254496a349006d331b

Changes to lowerBound/upperBound/equalRange

The old definitions had some problems: - In `lowerBound`, the `lhs` (left hand side) argument was passed on the right hand side. - In `upperBound`, the `greaterThan` function needed to return `greaterThanOrEqual` for the function work, so either the name or the implementation is incorrect. To fix both problems, define the functions in terms of a `lessThan` function that returns `lhs < rhs`. The is more consistent with the rest of `sort.zig` and it's also how C++ implements lower/upperBound (1)(2). (1) https://en.cppreference.com/w/cpp/algorithm/lower_bound (2) https://en.cppreference.com/w/cpp/algorithm/upper_bound - Rewrite doc comments. - Add a couple of more test cases. - Add docstring for std.sort.binarySearch

1 files changed, 108 insertions(+), 124 deletions(-)

lib/std/sort.zig+108-124
......@@ -399,6 +399,13 @@ test "sort fuzz testing" {
399399 }
400400}
401401
402/// Returns the index of an element in `items` equal to `key`.
403/// If there are multiple such elements, returns the index of any one of them.
404/// If there are no such elements, returns `null`.
405///
406/// `items` must be sorted in ascending order with respect to `compareFn`.
407///
408/// O(log n) complexity.
402409pub fn binarySearch(
403410 comptime T: type,
404411 key: anytype,
......@@ -502,25 +509,25 @@ test "binarySearch" {
502509 );
503510}
504511
505/// Returns the index pointing to the first element in the range [first,last)
506/// which does not compare less than val.
507/// The function optimizes the number of comparisons performed by using a binary search O(log n).
508/// An example lessThan function:
509/// fn lower_u32(_: void, key: u32, lhs: u32) bool { return lhs < key; }
512/// Returns the index of the first element in `items` greater than or equal to `key`,
513/// or `items.len` if all elements are less than `key`.
514///
515/// `items` must be sorted in ascending order with respect to `compareFn`.
516///
517/// O(log n) complexity.
510518pub fn lowerBound(
511519 comptime T: type,
512520 key: anytype,
513521 items: []const T,
514522 context: anytype,
515 comptime lessThan: fn (context: @TypeOf(context), key: @TypeOf(key), lhs: T) bool,
523 comptime lessThan: fn (context: @TypeOf(context), lhs: @TypeOf(key), rhs: T) bool,
516524) usize {
517525 var left: usize = 0;
518526 var right: usize = items.len;
519 var mid: usize = undefined;
520527
521528 while (left < right) {
522 mid = left + (right - left) / 2;
523 if (lessThan(context, key, items[mid])) {
529 const mid = left + (right - left) / 2;
530 if (lessThan(context, items[mid], key)) {
524531 left = mid + 1;
525532 } else {
526533 right = mid;
......@@ -532,87 +539,85 @@ pub fn lowerBound(
532539
533540test "lowerBound" {
534541 const S = struct {
535 fn lower_u32(context: void, key: u32, lhs: u32) bool {
542 fn lower_u32(context: void, lhs: u32, rhs: u32) bool {
536543 _ = context;
537 return lhs < key;
544 return lhs < rhs;
538545 }
539 fn lower_i32(context: void, key: i32, lhs: i32) bool {
546 fn lower_i32(context: void, lhs: i32, rhs: i32) bool {
540547 _ = context;
541 return lhs < key;
548 return lhs < rhs;
542549 }
543 fn lower_f32(context: void, key: f32, lhs: f32) bool {
550 fn lower_f32(context: void, lhs: f32, rhs: f32) bool {
544551 _ = context;
545 return lhs < key;
552 return lhs < rhs;
546553 }
547554 };
548555
549 // u32
550 // test no data
551556 try testing.expectEqual(
552 @as(?usize, 0),
557 @as(usize, 0),
553558 lowerBound(u32, @as(u32, 0), &[_]u32{}, {}, S.lower_u32),
554559 );
555 // test below the first element
556560 try testing.expectEqual(
557 @as(?usize, 0),
561 @as(usize, 0),
558562 lowerBound(u32, @as(u32, 0), &[_]u32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_u32),
559563 );
560 // test equal to the first element
561564 try testing.expectEqual(
562 @as(?usize, 0),
565 @as(usize, 0),
563566 lowerBound(u32, @as(u32, 2), &[_]u32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_u32),
564567 );
565 // test between two numbers
566568 try testing.expectEqual(
567 @as(?usize, 2),
569 @as(usize, 2),
568570 lowerBound(u32, @as(u32, 5), &[_]u32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_u32),
569571 );
570 // test equal to a number not at the ends
571572 try testing.expectEqual(
572 @as(?usize, 2),
573 @as(usize, 2),
573574 lowerBound(u32, @as(u32, 8), &[_]u32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_u32),
574575 );
575 // test equal to the last element
576576 try testing.expectEqual(
577 @as(?usize, 5),
577 @as(usize, 6),
578 lowerBound(u32, @as(u32, 8), &[_]u32{ 2, 4, 7, 7, 7, 7, 16, 32, 64 }, {}, S.lower_u32),
579 );
580 try testing.expectEqual(
581 @as(usize, 2),
582 lowerBound(u32, @as(u32, 8), &[_]u32{ 2, 4, 8, 8, 8, 8, 16, 32, 64 }, {}, S.lower_u32),
583 );
584 try testing.expectEqual(
585 @as(usize, 5),
578586 lowerBound(u32, @as(u32, 64), &[_]u32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_u32),
579587 );
580 // test above the last element
581588 try testing.expectEqual(
582 @as(?usize, 6),
589 @as(usize, 6),
583590 lowerBound(u32, @as(u32, 100), &[_]u32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_u32),
584591 );
585 // i32
586592 try testing.expectEqual(
587 @as(?usize, 2),
593 @as(usize, 2),
588594 lowerBound(i32, @as(i32, 5), &[_]i32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_i32),
589595 );
590 // f32
591596 try testing.expectEqual(
592 @as(?usize, 1),
597 @as(usize, 1),
593598 lowerBound(f32, @as(f32, -33.4), &[_]f32{ -54.2, -26.7, 0.0, 56.55, 100.1, 322.0 }, {}, S.lower_f32),
594599 );
595600}
596601
597/// Returns the index pointing to the first element in the range [first,last)
598/// which compares greater than val.
599/// The function optimizes the number of comparisons performed by using a binary search O(log n).
600/// An example greaterThan function:
601/// fn upper_u32(_: void, key: u32, rhs: u32) bool { return key >= rhs; }
602/// Returns the index of the first element in `items` greater than `key`,
603/// or `items.len` if all elements are less than or equal to `key`.
604///
605/// `items` must be sorted in ascending order with respect to `compareFn`.
606///
607/// O(log n) complexity.
602608pub fn upperBound(
603609 comptime T: type,
604610 key: anytype,
605611 items: []const T,
606612 context: anytype,
607 comptime greaterThan: fn (context: @TypeOf(context), key: @TypeOf(key), rhs: T) bool,
613 comptime lessThan: fn (context: @TypeOf(context), lhs: @TypeOf(key), rhs: T) bool,
608614) usize {
609615 var left: usize = 0;
610616 var right: usize = items.len;
611 var mid: usize = undefined;
612617
613618 while (left < right) {
614 mid = (right + left) / 2;
615 if (greaterThan(context, key, items[mid])) {
619 const mid = (right + left) / 2;
620 if (!lessThan(context, key, items[mid])) {
616621 left = mid + 1;
617622 } else {
618623 right = mid;
......@@ -624,165 +629,144 @@ pub fn upperBound(
624629
625630test "upperBound" {
626631 const S = struct {
627 fn upper_u32(context: void, key: u32, rhs: u32) bool {
632 fn lower_u32(context: void, lhs: u32, rhs: u32) bool {
628633 _ = context;
629 return key >= rhs;
634 return lhs < rhs;
630635 }
631 fn upper_i32(context: void, key: i32, rhs: i32) bool {
636 fn lower_i32(context: void, lhs: i32, rhs: i32) bool {
632637 _ = context;
633 return key >= rhs;
638 return lhs < rhs;
634639 }
635 fn upper_f32(context: void, key: f32, rhs: f32) bool {
640 fn lower_f32(context: void, lhs: f32, rhs: f32) bool {
636641 _ = context;
637 return key >= rhs;
642 return lhs < rhs;
638643 }
639644 };
640645
641 // u32
642 // test no data
643646 try testing.expectEqual(
644 @as(?usize, 0),
645 upperBound(u32, @as(u32, 0), &[_]u32{}, {}, S.upper_u32),
647 @as(usize, 0),
648 upperBound(u32, @as(u32, 0), &[_]u32{}, {}, S.lower_u32),
646649 );
647 // test below the first element
648650 try testing.expectEqual(
649 @as(?usize, 0),
650 upperBound(u32, @as(u32, 0), &[_]u32{ 2, 4, 8, 16, 32, 64 }, {}, S.upper_u32),
651 @as(usize, 0),
652 upperBound(u32, @as(u32, 0), &[_]u32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_u32),
651653 );
652 // test equal to the first element
653654 try testing.expectEqual(
654 @as(?usize, 1),
655 upperBound(u32, @as(u32, 2), &[_]u32{ 2, 4, 8, 16, 32, 64 }, {}, S.upper_u32),
655 @as(usize, 1),
656 upperBound(u32, @as(u32, 2), &[_]u32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_u32),
656657 );
657 // test between two numbers
658658 try testing.expectEqual(
659 @as(?usize, 2),
660 upperBound(u32, @as(u32, 5), &[_]u32{ 2, 4, 8, 16, 32, 64 }, {}, S.upper_u32),
659 @as(usize, 2),
660 upperBound(u32, @as(u32, 5), &[_]u32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_u32),
661661 );
662 // test equal to a number not at the ends
663662 try testing.expectEqual(
664 @as(?usize, 3),
665 upperBound(u32, @as(u32, 8), &[_]u32{ 2, 4, 8, 16, 32, 64 }, {}, S.upper_u32),
663 @as(usize, 6),
664 upperBound(u32, @as(u32, 8), &[_]u32{ 2, 4, 7, 7, 7, 7, 16, 32, 64 }, {}, S.lower_u32),
666665 );
667 // test equal to the last element
668666 try testing.expectEqual(
669 @as(?usize, 6),
670 upperBound(u32, @as(u32, 64), &[_]u32{ 2, 4, 8, 16, 32, 64 }, {}, S.upper_u32),
667 @as(usize, 6),
668 upperBound(u32, @as(u32, 8), &[_]u32{ 2, 4, 8, 8, 8, 8, 16, 32, 64 }, {}, S.lower_u32),
671669 );
672 // test above the last element
673670 try testing.expectEqual(
674 @as(?usize, 6),
675 upperBound(u32, @as(u32, 100), &[_]u32{ 2, 4, 8, 16, 32, 64 }, {}, S.upper_u32),
671 @as(usize, 3),
672 upperBound(u32, @as(u32, 8), &[_]u32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_u32),
676673 );
677 // i32
678674 try testing.expectEqual(
679 @as(?usize, 2),
680 upperBound(i32, @as(i32, 5), &[_]i32{ 2, 4, 8, 16, 32, 64 }, {}, S.upper_i32),
675 @as(usize, 6),
676 upperBound(u32, @as(u32, 64), &[_]u32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_u32),
681677 );
682 // f32
683678 try testing.expectEqual(
684 @as(?usize, 1),
685 upperBound(f32, @as(f32, -33.4), &[_]f32{ -54.2, -26.7, 0.0, 56.55, 100.1, 322.0 }, {}, S.upper_f32),
679 @as(usize, 6),
680 upperBound(u32, @as(u32, 100), &[_]u32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_u32),
681 );
682 try testing.expectEqual(
683 @as(usize, 2),
684 upperBound(i32, @as(i32, 5), &[_]i32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_i32),
685 );
686 try testing.expectEqual(
687 @as(usize, 1),
688 upperBound(f32, @as(f32, -33.4), &[_]f32{ -54.2, -26.7, 0.0, 56.55, 100.1, 322.0 }, {}, S.lower_f32),
686689 );
687690}
688691
689/// Returns a range containing all elements equivalent to value in the range [first,last)
690/// The function optimizes the number of comparisons performed by using a binary search O(2 * log n).
691/// Example lessThan & greaterThan functions:
692/// fn lower_u32(_: void, key: u32, lhs: u32) bool { return lhs < key; }
693/// fn upper_u32(_: void, key: u32, rhs: u32) bool { return key >= rhs; }
692/// Returns a tuple of the lower and upper indices in `items` between which all elements are equal to `key`.
693/// If no element in `items` is equal to `key`, both indices are the
694/// index of the first element in `items` greater than `key`.
695/// If no element in `items` is greater than `key`, both indices equal `items.len`.
696///
697/// `items` must be sorted in ascending order with respect to `compareFn`.
698///
699/// O(log n) complexity.
700///
701/// See also: `lowerBound` and `upperBound`.
694702pub fn equalRange(
695703 comptime T: type,
696704 key: anytype,
697705 items: []const T,
698706 context: anytype,
699 comptime lessThan: fn (context: @TypeOf(context), key: @TypeOf(key), lhs: T) bool,
700 comptime greaterThan: fn (context: @TypeOf(context), key: @TypeOf(key), rhs: T) bool,
707 comptime lessThan: fn (context: @TypeOf(context), lhs: @TypeOf(key), rhs: T) bool,
701708) struct { usize, usize } {
702709 return .{
703710 lowerBound(T, key, items, context, lessThan),
704 upperBound(T, key, items, context, greaterThan),
711 upperBound(T, key, items, context, lessThan),
705712 };
706713}
707714
708715test "equalRange" {
709716 const S = struct {
710 fn lower_i32(context: void, key: i32, lhs: i32) bool {
711 _ = context;
712 return lhs < key;
713 }
714 fn upper_i32(context: void, key: i32, rhs: i32) bool {
715 _ = context;
716 return key >= rhs;
717 }
718 fn lower_u32(context: void, key: u32, lhs: u32) bool {
719 _ = context;
720 return lhs < key;
721 }
722 fn upper_u32(context: void, key: u32, rhs: u32) bool {
717 fn lower_u32(context: void, lhs: u32, rhs: u32) bool {
723718 _ = context;
724 return key >= rhs;
719 return lhs < rhs;
725720 }
726 fn lower_f32(context: void, key: f32, lhs: f32) bool {
721 fn lower_i32(context: void, lhs: i32, rhs: i32) bool {
727722 _ = context;
728 return lhs < key;
723 return lhs < rhs;
729724 }
730 fn upper_f32(context: void, key: f32, rhs: f32) bool {
725 fn lower_f32(context: void, lhs: f32, rhs: f32) bool {
731726 _ = context;
732 return key >= rhs;
727 return lhs < rhs;
733728 }
734729 };
735730
736 // i32
737 // test no data
738731 try testing.expectEqual(
739732 @as(struct { usize, usize }, .{ 0, 0 }),
740 equalRange(i32, @as(i32, 0), &[_]i32{}, {}, S.lower_i32, S.upper_i32),
733 equalRange(i32, @as(i32, 0), &[_]i32{}, {}, S.lower_i32),
741734 );
742 // test below the first element
743735 try testing.expectEqual(
744736 @as(struct { usize, usize }, .{ 0, 0 }),
745 equalRange(i32, @as(i32, 0), &[_]i32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_i32, S.upper_i32),
737 equalRange(i32, @as(i32, 0), &[_]i32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_i32),
746738 );
747 // test equal to the first element
748739 try testing.expectEqual(
749740 @as(struct { usize, usize }, .{ 0, 1 }),
750 equalRange(i32, @as(i32, 2), &[_]i32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_i32, S.upper_i32),
741 equalRange(i32, @as(i32, 2), &[_]i32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_i32),
751742 );
752 // test between two numbers
753743 try testing.expectEqual(
754744 @as(struct { usize, usize }, .{ 2, 2 }),
755 equalRange(i32, @as(i32, 5), &[_]i32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_i32, S.upper_i32),
745 equalRange(i32, @as(i32, 5), &[_]i32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_i32),
756746 );
757 // test equal to a number not at the ends
758747 try testing.expectEqual(
759748 @as(struct { usize, usize }, .{ 2, 3 }),
760 equalRange(i32, @as(i32, 8), &[_]i32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_i32, S.upper_i32),
749 equalRange(i32, @as(i32, 8), &[_]i32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_i32),
761750 );
762 // test equal to the last element
763751 try testing.expectEqual(
764752 @as(struct { usize, usize }, .{ 5, 6 }),
765 equalRange(i32, @as(i32, 64), &[_]i32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_i32, S.upper_i32),
753 equalRange(i32, @as(i32, 64), &[_]i32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_i32),
766754 );
767 // test above the last element
768755 try testing.expectEqual(
769756 @as(struct { usize, usize }, .{ 6, 6 }),
770 equalRange(i32, @as(i32, 100), &[_]i32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_i32, S.upper_i32),
757 equalRange(i32, @as(i32, 100), &[_]i32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_i32),
771758 );
772 // test many of the same element
773759 try testing.expectEqual(
774760 @as(struct { usize, usize }, .{ 2, 6 }),
775 equalRange(i32, @as(i32, 8), &[_]i32{ 2, 4, 8, 8, 8, 8, 15, 22 }, {}, S.lower_i32, S.upper_i32),
761 equalRange(i32, @as(i32, 8), &[_]i32{ 2, 4, 8, 8, 8, 8, 15, 22 }, {}, S.lower_i32),
776762 );
777 // u32
778763 try testing.expectEqual(
779764 @as(struct { usize, usize }, .{ 2, 2 }),
780 equalRange(u32, @as(u32, 5), &[_]u32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_u32, S.upper_u32),
765 equalRange(u32, @as(u32, 5), &[_]u32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_u32),
781766 );
782 // f32
783767 try testing.expectEqual(
784768 @as(struct { usize, usize }, .{ 1, 1 }),
785 equalRange(f32, @as(f32, -33.4), &[_]f32{ -54.2, -26.7, 0.0, 56.55, 100.1, 322.0 }, {}, S.lower_f32, S.upper_f32),
769 equalRange(f32, @as(f32, -33.4), &[_]f32{ -54.2, -26.7, 0.0, 56.55, 100.1, 322.0 }, {}, S.lower_f32),
786770 );
787771}
788772