Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Stephen von Takach committed Jul 11, 2018
0 parents commit a71f16f
Show file tree
Hide file tree
Showing 7 changed files with 325 additions and 0 deletions.
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
doc
lib
.crystal
.shards
app
*.dwarf
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
language: crystal
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
The MIT License (MIT)

Copyright (c) 2018 Stephen von Takach

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Crystal Lang Bisect

Provides helpers for dealing with sorted Arrays.
It uses binary search to reduce the number of comparisons.


Usage
=====

There are two primary functions that you need to know about `Bisect.insort` and `Bisect.bisect`.

`Bisect.insort` adds a new element to the Array, but keeps the Array sorted:

```ruby
require 'bisect'
a = [1, 2, 4]
Bisect.insort(a, 3)
a == [1, 2, 3, 4]
```

`Bisect.bisect` gives you the index at which the element would have been inserted:

```ruby
require 'bisect'
a = ['a', 'b', 'd']
Bisect.bisect(a, 'c') == 2
```

If there are equal elements in the Array then `insort` will insert the element after the last equal element. Similarly `bisect` will return the index one higher than the last equal element. If you'd like to add new elements before equal elements, use `insort_left` and `bisect_left`. If you need to be explicit then `insort_right` and `bisect_right` are aliases for `insort` and `bisect`.


Core ext
========

These methods are also available directly on Arrays

```ruby
require 'bisect'
a = [1, 2, 4]
a.insort(3)
a == [1, 2, 3, 4]
```
2 changes: 2 additions & 0 deletions shard.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
name: bisect
version: 1.0.0
144 changes: 144 additions & 0 deletions spec/bisect_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
require "spec"
require "../src/bisect"

class Tests
def self.values
[
{:right, [] of Int32, 1, 0},
{:right, [1], 0, 0},
{:right, [1], 1, 1},
{:right, [1], 2, 1},
{:right, [1, 1], 0, 0},
{:right, [1, 1], 1, 2},
{:right, [1, 1], 2, 2},
{:right, [1, 1, 1], 0, 0},
{:right, [1, 1, 1], 1, 3},
{:right, [1, 1, 1], 2, 3},
{:right, [1, 1, 1, 1], 0, 0},
{:right, [1, 1, 1, 1], 1, 4},
{:right, [1, 1, 1, 1], 2, 4},
{:right, [1, 2], 0, 0},
{:right, [1, 2], 1, 1},
{:right, [1, 2], 1.5, 1},
{:right, [1, 2], 2, 2},
{:right, [1, 2], 3, 2},
{:right, [1, 1, 2, 2], 0, 0},
{:right, [1, 1, 2, 2], 1, 2},
{:right, [1, 1, 2, 2], 1.5, 2},
{:right, [1, 1, 2, 2], 2, 4},
{:right, [1, 1, 2, 2], 3, 4},
{:right, [1, 2, 3], 0, 0},
{:right, [1, 2, 3], 1, 1},
{:right, [1, 2, 3], 1.5, 1},
{:right, [1, 2, 3], 2, 2},
{:right, [1, 2, 3], 2.5, 2},
{:right, [1, 2, 3], 3, 3},
{:right, [1, 2, 3], 4, 3},
{:right, [1, 2, 2, 3, 3, 3, 4, 4, 4, 4], 0, 0},
{:right, [1, 2, 2, 3, 3, 3, 4, 4, 4, 4], 1, 1},
{:right, [1, 2, 2, 3, 3, 3, 4, 4, 4, 4], 1.5, 1},
{:right, [1, 2, 2, 3, 3, 3, 4, 4, 4, 4], 2, 3},
{:right, [1, 2, 2, 3, 3, 3, 4, 4, 4, 4], 2.5, 3},
{:right, [1, 2, 2, 3, 3, 3, 4, 4, 4, 4], 3, 6},
{:right, [1, 2, 2, 3, 3, 3, 4, 4, 4, 4], 3.5, 6},
{:right, [1, 2, 2, 3, 3, 3, 4, 4, 4, 4], 4, 10},
{:right, [1, 2, 2, 3, 3, 3, 4, 4, 4, 4], 5, 10},

{:left, [] of Int32, 1, 0},
{:left, [1], 0, 0},
{:left, [1], 1, 0},
{:left, [1], 2, 1},
{:left, [1, 1], 0, 0},
{:left, [1, 1], 1, 0},
{:left, [1, 1], 2, 2},
{:left, [1, 1, 1], 0, 0},
{:left, [1, 1, 1], 1, 0},
{:left, [1, 1, 1], 2, 3},
{:left, [1, 1, 1, 1], 0, 0},
{:left, [1, 1, 1, 1], 1, 0},
{:left, [1, 1, 1, 1], 2, 4},
{:left, [1, 2], 0, 0},
{:left, [1, 2], 1, 0},
{:left, [1, 2], 1.5, 1},
{:left, [1, 2], 2, 1},
{:left, [1, 2], 3, 2},
{:left, [1, 1, 2, 2], 0, 0},
{:left, [1, 1, 2, 2], 1, 0},
{:left, [1, 1, 2, 2], 1.5, 2},
{:left, [1, 1, 2, 2], 2, 2},
{:left, [1, 1, 2, 2], 3, 4},
{:left, [1, 2, 3], 0, 0},
{:left, [1, 2, 3], 1, 0},
{:left, [1, 2, 3], 1.5, 1},
{:left, [1, 2, 3], 2, 1},
{:left, [1, 2, 3], 2.5, 2},
{:left, [1, 2, 3], 3, 2},
{:left, [1, 2, 3], 4, 3},
{:left, [1, 2, 2, 3, 3, 3, 4, 4, 4, 4], 0, 0},
{:left, [1, 2, 2, 3, 3, 3, 4, 4, 4, 4], 1, 0},
{:left, [1, 2, 2, 3, 3, 3, 4, 4, 4, 4], 1.5, 1},
{:left, [1, 2, 2, 3, 3, 3, 4, 4, 4, 4], 2, 1},
{:left, [1, 2, 2, 3, 3, 3, 4, 4, 4, 4], 2.5, 3},
{:left, [1, 2, 2, 3, 3, 3, 4, 4, 4, 4], 3, 3},
{:left, [1, 2, 2, 3, 3, 3, 4, 4, 4, 4], 3.5, 6},
{:left, [1, 2, 2, 3, 3, 3, 4, 4, 4, 4], 4, 6},
{:left, [1, 2, 2, 3, 3, 3, 4, 4, 4, 4], 5, 10},
]
end
end

describe Bisect do
it "should bisect an array" do
Tests.values.each do |test|
direction, array, element, result = test

case direction
when :left
Bisect.bisect_left(array, element).should eq(result)
when :right
Bisect.bisect_right(array, element).should eq(result)
else
raise "wtf?!"
end
end
end

it "should insort a sorted array" do
array = [] of Int32
500.times do
digit = rand(10)
if digit % 2 == 0
Bisect.insort_left(array, digit)
else
Bisect.insort_right(array, digit)
end
array.should eq(array.sort)
end
end

it "should insort directly on an array" do
array = [] of Int32
500.times do
digit = rand(10)
if digit % 2 == 0
array.insort_left(digit)
else
array.insort_right(digit)
end
array.should eq(array.sort)
end

Tests.values.each do |test|
direction, array, element, result = test

case direction
when :left
array.bisect_left(element).should eq(result)
when :right
array.bisect_right(element).should eq(result)
else
raise "wtf?!"
end
end
end
end
109 changes: 109 additions & 0 deletions src/bisect.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# A direct port of the Python bisect standard library.
#
# http://svn.python.org/view/python/branches/py3k/Lib/bisect.py?view=markup&pathrev=70846
#
module Bisect
extend self

# Insert item x in list a, and keep it sorted assuming a is sorted.
#
# If x is already in a, insert it to the right of the rightmost x.
#
# Optional args lo (default 0) and hi (default len(a)) bound the
# slice of a to be searched.
def insort_right(a, x, lo = 0, hi = a.size)
index = bisect_right(a, x, lo, hi)
a.insert(index, x)
end

def insort(a, x, lo = 0, hi = a.size)
insort_right(a, x, lo, hi)
end

# Return the index where to insert item x in list a, assuming a is sorted.
#
# The return value i is such that all e in a[:i] have e <= x, and all e in
# a[i:] have e > x. So if x already appears in the list, a.insert(x) will
# insert just after the rightmost x already there.
#
# Optional args lo (default 0) and hi (default len(a)) bound the
# slice of a to be searched.
def bisect_right(a, x, lo = 0, hi = a.size)
raise ArgumentError.new("lo must be non-negative") if lo < 0

while lo < hi
mid = (lo + hi) / 2
if x < a[mid]
hi = mid
else
lo = mid + 1
end
end

lo
end

def bisect(a, x, lo = 0, hi = a.size)
bisect_right(a, x, lo, hi)
end

# Insert item x in list a, and keep it sorted assuming a is sorted.
#
# If x is already in a, insert it to the left of the leftmost x.
#
# Optional args lo (default 0) and hi (default len(a)) bound the
# slice of a to be searched.
def insort_left(a, x, lo = 0, hi = a.size)
index = bisect_left(a, x, lo, hi)
a.insert(index, x)
end

# Return the index where to insert item x in list a, assuming a is sorted.
#
# The return value i is such that all e in a[:i] have e < x, and all e in
# a[i:] have e >= x. So if x already appears in the list, a.insert(x) will
# insert just before the leftmost x already there.
#
# Optional args lo (default 0) and hi (default len(a)) bound the
# slice of a to be searched.
def bisect_left(a, x, lo = 0, hi = a.size)
raise ArgumentError.new("lo must be non-negative") if lo < 0

while lo < hi
mid = (lo + hi) / 2
if a[mid] < x
lo = mid + 1
else
hi = mid
end
end

lo
end
end

class Array
def insort_left(x, lo = 0, hi = self.size)
Bisect.insort_left(self, x, lo, hi)
end

def insort_right(x, lo = 0, hi = self.size)
Bisect.insort_right(self, x, lo, hi)
end

def insort(x, lo = 0, hi = self.size)
Bisect.insort_right(self, x, lo, hi)
end

def bisect_left(x, lo = 0, hi = self.size)
Bisect.bisect_left(self, x, lo, hi)
end

def bisect_right(x, lo = 0, hi = self.size)
Bisect.bisect_right(self, x, lo, hi)
end

def bisect(x, lo = 0, hi = self.size)
Bisect.bisect_right(self, x, lo, hi)
end
end

0 comments on commit a71f16f

Please sign in to comment.