diff --git a/spec/std/array_spec.cr b/spec/std/array_spec.cr index 4bd4903d904d..9c78e7f26bdc 100644 --- a/spec/std/array_spec.cr +++ b/spec/std/array_spec.cr @@ -1311,128 +1311,100 @@ describe "Array" do end describe "sort" do - [true, false].each do |stable| - describe "stable: #{stable}" do - it "sort without block" do + {% for sort in ["sort".id, "unstable_sort".id] %} + describe {{ "##{sort}" }} do + it "without block" do a = [3, 4, 1, 2, 5, 6] - b = a.sort(stable: stable) + b = a.{{ sort }} b.should eq([1, 2, 3, 4, 5, 6]) a.should_not eq(b) end - it "sort with a block" do + it "with a block" do a = ["foo", "a", "hello"] - b = a.sort(stable: stable) { |x, y| x.size <=> y.size } + b = a.{{ sort }} { |x, y| x.size <=> y.size } b.should eq(["a", "foo", "hello"]) a.should_not eq(b) end - end - end - - it "stable sort without block" do - is_stable_sort(mutable: false, &.sort(stable: true)) - end - - it "stable sort with a block" do - is_stable_sort(mutable: false, &.sort(stable: true) { |a, b| a.value <=> b.value }) - end - it "default is stable (without block)" do - is_stable_sort(mutable: false, &.sort) - end + {% if sort == "sort" %} + it "stable sort without a block" do + is_stable_sort(mutable: false, &.sort) + end - it "default is stable (with a block)" do - is_stable_sort(mutable: false, &.sort { |a, b| a.value <=> b.value }) - end - end + it "stable sort with a block" do + is_stable_sort(mutable: false, &.sort { |a, b| a.value <=> b.value }) + end + {% end %} + end - describe "sort!" do - [true, false].each do |stable| - describe "stable: #{stable}" do - it "sort! without block" do + describe {{ "##{sort}!" }} do + it "without block" do a = [3, 4, 1, 2, 5, 6] - a.sort!(stable: stable) + a.{{ sort.id }}! a.should eq([1, 2, 3, 4, 5, 6]) end - it "sort! with a block" do + it "with a block" do a = ["foo", "a", "hello"] - a.sort!(stable: stable) { |x, y| x.size <=> y.size } + a.{{ sort.id }}! { |x, y| x.size <=> y.size } a.should eq(["a", "foo", "hello"]) end - end - end - - it "stable sort! without block" do - is_stable_sort(mutable: true, &.sort!(stable: true)) - end - - it "stable sort! with a block" do - is_stable_sort(mutable: true, &.sort!(stable: true) { |a, b| a.value <=> b.value }) - end - it "default is stable (without block)" do - is_stable_sort(mutable: true, &.sort!) - end + {% if sort == "sort" %} + it "stable sort without a block" do + is_stable_sort(mutable: true, &.sort!) + end - it "default is stable (with a block)" do - is_stable_sort(mutable: true, &.sort! { |a, b| a.value <=> b.value }) - end - end + it "stable sort with a block" do + is_stable_sort(mutable: true, &.sort! { |a, b| a.value <=> b.value }) + end + {% end %} + end - describe "sort_by" do - [true, false].each do |stable| - describe "stable: #{stable}" do - it "sorts by" do + describe {{ "##{sort}_by" }} do + it "sorts" do a = ["foo", "a", "hello"] - b = a.sort_by(stable: stable, &.size) + b = a.{{ sort }}_by(&.size) b.should eq(["a", "foo", "hello"]) a.should_not eq(b) end it "unpacks tuple" do a = [{"d", 4}, {"a", 1}, {"c", 3}, {"e", 5}, {"b", 2}] - b = a.sort_by(stable: stable) { |x, y| y } + b = a.{{ sort }}_by { |x, y| y } b.should eq([{"a", 1}, {"b", 2}, {"c", 3}, {"d", 4}, {"e", 5}]) a.should_not eq(b) end - end - end - - it "stable sort by" do - is_stable_sort(mutable: false, &.sort_by(stable: true, &.value)) - end - it "default is stable" do - is_stable_sort(mutable: false, &.sort_by(&.value)) - end - end + {% if sort == "sort" %} + it "stable sort" do + is_stable_sort(mutable: false, &.sort_by(&.value)) + end + {% end %} + end - describe "sort_by!" do - [true, false].each do |stable| - describe "stable: #{stable}" do - it "sorts by!" do + describe {{ "##{sort}_by!" }} do + it "sorts" do a = ["foo", "a", "hello"] - a.sort_by!(stable: stable, &.size) + a.{{ sort }}_by!(&.size) a.should eq(["a", "foo", "hello"]) end it "calls given block exactly once for each element" do calls = Hash(String, Int32).new(0) a = ["foo", "a", "hello"] - a.sort_by!(stable: stable) { |e| calls[e] += 1; e.size } + a.{{ sort }}_by! { |e| calls[e] += 1; e.size } calls.should eq({"foo" => 1, "a" => 1, "hello" => 1}) end - end - end - it "stable sort by!" do - is_stable_sort(mutable: true, &.sort_by!(stable: true, &.value)) - end - - it "default is stable" do - is_stable_sort(mutable: true, &.sort_by!(&.value)) - end + {% if sort == "sort" %} + it "stable sort" do + is_stable_sort(mutable: true, &.sort_by!(&.value)) + end + {% end %} + end + {% end %} end describe "swap" do diff --git a/spec/std/slice_spec.cr b/spec/std/slice_spec.cr index 4838237fa636..5a1aed451132 100644 --- a/spec/std/slice_spec.cr +++ b/spec/std/slice_spec.cr @@ -632,59 +632,49 @@ describe "Slice" do end describe "sort" do - [true, false].each do |stable| - describe "stable: #{stable}" do - it "sort without block" do + {% for sort in ["sort".id, "unstable_sort".id] %} + describe {{ "##{sort}" }} do + it "without block" do slice = Slice[3, 4, 1, 2, 5, 6] - sorted_slice = slice.sort(stable: stable) + sorted_slice = slice.{{ sort }} sorted_slice.to_a.should eq([1, 2, 3, 4, 5, 6]) slice.should_not eq(sorted_slice) end - it "sort with a block" do + it "with a block" do a = Slice["foo", "a", "hello"] - b = a.sort(stable: stable) { |x, y| x.size <=> y.size } + b = a.{{ sort }} { |x, y| x.size <=> y.size } b.to_a.should eq(["a", "foo", "hello"]) a.should_not eq(b) end - end - end - - it "stable sort without block" do - is_stable_sort(mutable: false, &.sort(stable: true)) - end - - it "stable sort with a block" do - is_stable_sort(mutable: false, &.sort(stable: true) { |a, b| a.value <=> b.value }) - end - it "default is stable (without block)" do - is_stable_sort(mutable: false, &.sort) - end + {% if sort == "sort" %} + it "stable sort without a block" do + is_stable_sort(mutable: false, &.sort) + end - it "default is stable (with a block)" do - is_stable_sort(mutable: false, &.sort { |a, b| a.value <=> b.value }) - end - end + it "stable sort with a block" do + is_stable_sort(mutable: false, &.sort { |a, b| a.value <=> b.value }) + end + {% end %} + end - describe "sort!" do - [true, false].each do |stable| - describe "stable: #{stable}" do - it "sort! without block" do + describe {{ "##{sort}!" }} do + it "without block" do a = [3, 4, 1, 2, 5, 6] - a.sort!(stable: stable) + a.{{ sort }}! a.should eq([1, 2, 3, 4, 5, 6]) end - it "sort! with a block" do + it "with a block" do a = ["foo", "a", "hello"] - a.sort!(stable: stable) { |x, y| x.size <=> y.size } + a.{{ sort }}! { |x, y| x.size <=> y.size } a.should eq(["a", "foo", "hello"]) end it "sorts with invalid block (#4379)" do a = [1] * 17 - b = a.sort(stable: stable) { -1 } + b = a.{{ sort }} { -1 } a.should eq(b) end @@ -696,7 +686,7 @@ describe "Slice" do Spaceship.new(3), ] - spaceships.sort!(stable: stable) + spaceships.{{ sort }}! 4.times do |i| spaceships[i].value.should eq(i) end @@ -709,81 +699,63 @@ describe "Slice" do ] expect_raises(ArgumentError) do - spaceships.sort!(stable: stable) + spaceships.{{ sort }}! end end it "raises if sort! block returns nil" do expect_raises(ArgumentError) do - Slice[1, 2].sort!(stable: stable) { nil } + Slice[1, 2].{{ sort }}! { nil } end end - end - end - - it "stable sort! without block" do - is_stable_sort(mutable: true, &.sort!(stable: true)) - end - - it "stable sort! with a block" do - is_stable_sort(mutable: true, &.sort!(stable: true) { |a, b| a.value <=> b.value }) - end - it "default is stable (without block)" do - is_stable_sort(mutable: true, &.sort!) - end + {% if sort == "sort" %} + it "stable sort without a block" do + is_stable_sort(mutable: true, &.sort!) + end - it "default is stable (with a block)" do - is_stable_sort(mutable: true, &.sort! { |a, b| a.value <=> b.value }) - end - end + it "stable sort with a block" do + is_stable_sort(mutable: true, &.sort! { |a, b| a.value <=> b.value }) + end + {% end %} + end - describe "sort_by" do - [true, false].each do |stable| - describe "stable: #{stable}" do - it "sorts by" do + describe {{ "##{sort}_by" }} do + it "sorts" do a = Slice["foo", "a", "hello"] - b = a.sort_by(stable: stable, &.size) + b = a.{{ sort }}_by(&.size) b.to_a.should eq(["a", "foo", "hello"]) a.should_not eq(b) end - end - end - - it "stable sort by" do - is_stable_sort(mutable: false, &.sort_by(stable: true, &.value)) - end - it "default is stable" do - is_stable_sort(mutable: false, &.sort_by(&.value)) - end - end + {% if sort == "sort" %} + it "stable sort" do + is_stable_sort(mutable: false, &.sort_by(&.value)) + end + {% end %} + end - describe "sort_by!" do - [true, false].each do |stable| - describe "stable: #{stable}" do - it "sorts by!" do + describe {{ "##{sort}_by" }} do + it "sorts" do a = Slice["foo", "a", "hello"] - a.sort_by!(stable: stable, &.size) + a.{{ sort }}_by!(&.size) a.to_a.should eq(["a", "foo", "hello"]) end it "calls given block exactly once for each element" do calls = Hash(String, Int32).new(0) a = Slice["foo", "a", "hello"] - a.sort_by!(stable: stable) { |e| calls[e] += 1; e.size } + a.{{ sort }}_by! { |e| calls[e] += 1; e.size } calls.should eq({"foo" => 1, "a" => 1, "hello" => 1}) end - end - end - it "stable sort by!" do - is_stable_sort(mutable: true, &.sort_by!(stable: true, &.value)) - end - - it "default is stable" do - is_stable_sort(mutable: true, &.sort_by!(&.value)) - end + {% if sort == "sort" %} + it "stable sort" do + is_stable_sort(mutable: true, &.sort_by!(&.value)) + end + {% end %} + end + {% end %} end describe "<=>" do diff --git a/src/array.cr b/src/array.cr index 54b4f25b2796..30b9eb51057d 100644 --- a/src/array.cr +++ b/src/array.cr @@ -1671,8 +1671,16 @@ class Array(T) # a.sort # => [1, 2, 3] # a # => [3, 1, 2] # ``` - def sort(*, stable : Bool = true) : Array(T) - dup.sort!(stable: stable) + def sort : Array(T) + dup.sort! + end + + # :ditto: + # + # This method does not guarantee stability between equally sorting elements. + # Which results in a performance advantage over stable sort. + def unstable_sort : Array(T) + dup.unstable_sort! end # Returns a new array with all elements sorted based on the comparator in the @@ -1689,12 +1697,24 @@ class Array(T) # b # => [3, 2, 1] # a # => [3, 1, 2] # ``` - def sort(*, stable : Bool = true, &block : T, T -> U) : Array(T) forall U + def sort(&block : T, T -> U) : Array(T) forall U {% unless U <= Int32? %} {% raise "expected block to return Int32 or Nil, not #{U}" %} {% end %} - dup.sort!(stable: stable, &block) + dup.sort! &block + end + + # :ditto: + # + # This method does not guarantee stability between equally sorting elements. + # Which results in a performance advantage over stable sort. + def unstable_sort(&block : T, T -> U) : Array(T) forall U + {% unless U <= Int32? %} + {% raise "expected block to return Int32 or Nil, not #{U}" %} + {% end %} + + dup.unstable_sort!(&block) end # Modifies `self` by sorting all elements based on the return value of their @@ -1705,8 +1725,17 @@ class Array(T) # a.sort! # a # => [1, 2, 3] # ``` - def sort!(*, stable : Bool = true) : Array(T) - to_unsafe_slice.sort!(stable: stable) + def sort! : Array(T) + to_unsafe_slice.sort! + self + end + + # :ditto: + # + # This method does not guarantee stability between equally sorting elements. + # Which results in a performance advantage over stable sort. + def unstable_sort! : Array(T) + to_unsafe_slice.unstable_sort! self end @@ -1723,12 +1752,25 @@ class Array(T) # a.sort! { |a, b| b <=> a } # a # => [3, 2, 1] # ``` - def sort!(*, stable : Bool = true, &block : T, T -> U) : Array(T) forall U + def sort!(&block : T, T -> U) : Array(T) forall U + {% unless U <= Int32? %} + {% raise "expected block to return Int32 or Nil, not #{U}" %} + {% end %} + + to_unsafe_slice.sort!(&block) + self + end + + # :ditto: + # + # This method does not guarantee stability between equally sorting elements. + # Which results in a performance advantage over stable sort. + def unstable_sort!(&block : T, T -> U) : Array(T) forall U {% unless U <= Int32? %} {% raise "expected block to return Int32 or Nil, not #{U}" %} {% end %} - to_unsafe_slice.sort!(stable: stable, &block) + to_unsafe_slice.unstable_sort!(&block) self end @@ -1742,8 +1784,16 @@ class Array(T) # b # => ["fig", "pear", "apple"] # a # => ["apple", "pear", "fig"] # ``` - def sort_by(*, stable : Bool = true, &block : T -> _) : Array(T) - dup.sort_by!(stable: stable) { |e| yield(e) } + def sort_by(&block : T -> _) : Array(T) + dup.sort_by! { |e| yield(e) } + end + + # :ditto: + # + # This method does not guarantee stability between equally sorting elements. + # Which results in a performance advantage over stable sort. + def unstable_sort_by(&block : T -> _) : Array(T) + dup.unstable_sort_by! { |e| yield(e) } end # Modifies `self` by sorting all elements. The given block is called for @@ -1755,8 +1805,20 @@ class Array(T) # a.sort_by! { |word| word.size } # a # => ["fig", "pear", "apple"] # ``` - def sort_by!(*, stable : Bool = true, &block : T -> _) : Array(T) - sorted = map { |e| {e, yield(e)} }.sort!(stable: stable) { |x, y| x[1] <=> y[1] } + def sort_by!(&block : T -> _) : Array(T) + sorted = map { |e| {e, yield(e)} }.sort! { |x, y| x[1] <=> y[1] } + @size.times do |i| + @buffer[i] = sorted.to_unsafe[i][0] + end + self + end + + # :ditto: + # + # This method does not guarantee stability between equally sorting elements. + # Which results in a performance advantage over stable sort. + def unstable_sort_by!(&block : T -> _) : Array(T) + sorted = map { |e| {e, yield(e)} }.unstable_sort! { |x, y| x[1] <=> y[1] } @size.times do |i| @buffer[i] = sorted.to_unsafe[i][0] end diff --git a/src/compiler/crystal/tools/doc/type.cr b/src/compiler/crystal/tools/doc/type.cr index e435b52e74ce..6c10b70e646a 100644 --- a/src/compiler/crystal/tools/doc/type.cr +++ b/src/compiler/crystal/tools/doc/type.cr @@ -176,7 +176,7 @@ class Crystal::Doc::Type defs << method(def_with_metadata.def, false) end end - defs.sort_by!(stable: true, &.name.downcase) + defs.sort_by!(&.name.downcase) end end end @@ -201,7 +201,7 @@ class Crystal::Doc::Type end end end - class_methods.sort_by!(stable: true, &.name.downcase) + class_methods.sort_by!(&.name.downcase) end end @@ -225,7 +225,7 @@ class Crystal::Doc::Type end end end - macros.sort_by!(stable: true, &.name.downcase) + macros.sort_by!(&.name.downcase) end end diff --git a/src/slice.cr b/src/slice.cr index 0bd3cc5f1690..f4e518dbf77b 100644 --- a/src/slice.cr +++ b/src/slice.cr @@ -737,8 +737,16 @@ struct Slice(T) # a.sort # => Slice[1, 2, 3] # a # => Slice[3, 1, 2] # ``` - def sort(*, stable : Bool = true) : Slice(T) - dup.sort!(stable: stable) + def sort : Slice(T) + dup.sort! + end + + # :ditto: + # + # This method does not guarantee stability between equally sorting elements. + # Which results in a performance advantage over stable sort. + def unstable_sort : Slice(T) + dup.unstable_sort! end # Returns a new slice with all elements sorted based on the comparator in the @@ -755,12 +763,24 @@ struct Slice(T) # b # => Slice[3, 2, 1] # a # => Slice[3, 1, 2] # ``` - def sort(*, stable : Bool = true, &block : T, T -> U) : Slice(T) forall U + def sort(&block : T, T -> U) : Slice(T) forall U + {% unless U <= Int32? %} + {% raise "expected block to return Int32 or Nil, not #{U}" %} + {% end %} + + dup.sort! &block + end + + # :ditto: + # + # This method does not guarantee stability between equally sorting elements. + # Which results in a performance advantage over stable sort. + def unstable_sort(&block : T, T -> U) : Slice(T) forall U {% unless U <= Int32? %} {% raise "expected block to return Int32 or Nil, not #{U}" %} {% end %} - dup.sort!(stable: stable, &block) + dup.unstable_sort!(&block) end # Modifies `self` by sorting all elements based on the return value of their @@ -771,12 +791,19 @@ struct Slice(T) # a.sort! # a # => Slice[1, 2, 3] # ``` - def sort!(*, stable : Bool = true) : Slice(T) - if stable - Slice.merge_sort!(self) - else - Slice.intro_sort!(to_unsafe, size) - end + def sort! : Slice(T) + Slice.merge_sort!(self) + + self + end + + # :ditto: + # + # This method does not guarantee stability between equally sorting elements. + # Which results in a performance advantage over stable sort. + def unstable_sort! : Slice(T) + Slice.intro_sort!(to_unsafe, size) + self end @@ -793,16 +820,27 @@ struct Slice(T) # a.sort! { |a, b| b <=> a } # a # => Slice[3, 2, 1] # ``` - def sort!(*, stable : Bool = true, &block : T, T -> U) : Slice(T) forall U + def sort!(&block : T, T -> U) : Slice(T) forall U {% unless U <= Int32? %} {% raise "expected block to return Int32 or Nil, not #{U}" %} {% end %} - if stable - Slice.merge_sort!(self, block) - else - Slice.intro_sort!(to_unsafe, size, block) - end + Slice.merge_sort!(self, block) + + self + end + + # :ditto: + # + # This method does not guarantee stability between equally sorting elements. + # Which results in a performance advantage over stable sort. + def unstable_sort!(&block : T, T -> U) : Slice(T) forall U + {% unless U <= Int32? %} + {% raise "expected block to return Int32 or Nil, not #{U}" %} + {% end %} + + Slice.intro_sort!(to_unsafe, size, block) + self end @@ -816,8 +854,16 @@ struct Slice(T) # b # => Slice["fig", "pear", "apple"] # a # => Slice["apple", "pear", "fig"] # ``` - def sort_by(*, stable : Bool = true, &block : T -> _) : Slice(T) - dup.sort_by!(stable: stable) { |e| yield(e) } + def sort_by(&block : T -> _) : Slice(T) + dup.sort_by! { |e| yield(e) } + end + + # :ditto: + # + # This method does not guarantee stability between equally sorting elements. + # Which results in a performance advantage over stable sort. + def unstable_sort_by(&block : T -> _) : Slice(T) + dup.unstable_sort_by! { |e| yield(e) } end # Modifies `self` by sorting all elements. The given block is called for @@ -829,8 +875,20 @@ struct Slice(T) # a.sort_by! { |word| word.size } # a # => Slice["fig", "pear", "apple"] # ``` - def sort_by!(*, stable : Bool = true, &block : T -> _) : Slice(T) - sorted = map { |e| {e, yield(e)} }.sort!(stable: stable) { |x, y| x[1] <=> y[1] } + def sort_by!(&block : T -> _) : Slice(T) + sorted = map { |e| {e, yield(e)} }.sort! { |x, y| x[1] <=> y[1] } + size.times do |i| + to_unsafe[i] = sorted.to_unsafe[i][0] + end + self + end + + # :ditto: + # + # This method does not guarantee stability between equally sorting elements. + # Which results in a performance advantage over stable sort. + def unstable_sort_by!(&block : T -> _) : Slice(T) + sorted = map { |e| {e, yield(e)} }.unstable_sort! { |x, y| x[1] <=> y[1] } size.times do |i| to_unsafe[i] = sorted.to_unsafe[i][0] end