class Sarah

Sarah is a hybrid sequential array, sparse array, and random-access hash implementing alternate semantics for Ruby arrays.

By default, negative indexes are relative to the end of the array (just like a regular Ruby Array), but they can be configured to be distinct, actual indexes instead.

Sarahs can also be used to implement (pure Ruby) sparse matrices, especially when used in conjunction with the Sarah::XK module (gem sarah-xk), an XKeys extension for Sarah.

Background

Standard Ruby lets you create an array literal like:

a = [ 1, 2, 5 => :five, :six => 'hello' ]

which is a short-cut for an array with an embedded hash:

a = [ 1, 2, { 5 => :five, :six => 'hello' } }

Initially:             After v = a.shift:

a[0] = a[-3] = 1       v = 1
a[1] = a[-2] = 2       a[0] = a[-2] = 2
a[2] = a[-1] = a hash  a[1] = a[-1] = a hash
a[2][5] = :five        a[1][5] = :five
a[2][:six] = 'hello'   a[1][:six] = 'hello'

In contrast, using a Sarah looks like this:

s = Sarah[ 1, 2, 5 => :five, :six => 'hello' ]

Initially:             After v = s.shift:

s[0] = s[-6] = 1       v = 1
s[1] = s[-5] = 2       s[0] = s[-5] = 2
s[5] = s[-1] = :five   s[4] = s[-1] = :five
s[:six] = 'hello'      s[:six] = 'hello'

Internal Structure

As of version 2.0.0, there are three major data structures internally:

The sequential and sparse parts are collectively referred to as “ary”. In some contexts, the non-sequential (sparse and random) parts are available as “nsq”. All three parts together are collectively referred to as “all”.

Some methods allow you to direct their action to all or specific parts of the structure by specifying a corrsponding symbol, :seq, :spr, :ary, :nsq, :rnd, or :all.

@author Brian Katzung (briank@kappacs.com), Kappa Computer Solutions, LLC @copyright 2013-2014 Brian Katzung and Kappa Computer Solutions, LLC @license MIT License @version 3.0.0

Constants

VERSION

Attributes

default[RW]

@!attribute default The default value to return for missing keys and for pop or shift on an empty array.

default_proc[R]

@!attribute default_proc A proc to call to return a default value for missing keys and for pop or shift on an empty array. The proc is passed the Sarah and the missing key (or nil for pop and shift). @return [Proc|nil]

negative_mode[R]

@!attribute [r] negative_mode @return [:actual|:error|:ignore] How negative indexes/keys are handled.

:actual - Negative keys represent themselves and are not treated
  specially (although delete works like unset in this mode--values
  are not reindexed)
:error (default) - Negative keys are interpreted relative to the
  end of the array; keys < -@ary_next raise an IndexError
:ignore - Like :error, but keys < -@ary_next are treated as
  non-existent on fetch and silently ignored on set

Public Class Methods

[](*args) click to toggle source

Instantiate a “Sarah literal”.

s = Sarah[pos1, ..., posN, key1 => val1, ..., keyN => valN]

s = Sarah['sequen', 'tial', 5 => 'sparse', :key => 'random']

@return [Sarah] @since 2.0.0

# File lib/sarah.rb, line 113
def self.[] (*args); new.set *args; end
new(*args, &block) click to toggle source

Initialize a new instance.

An initialization array may optionally be passed as the first parameter. (Since 2.0.0)

If passed a block, the block is called to provide default values instead of using the :default option value. The block is passed the hash and the requested key (or nil for a shift or pop on an empty sequential array).

s = Sarah.new([pos1, ..., posN, key1 => val1, ..., keyN => valN],
  :default => default, :default_proc => Proc.new { |sarah, key| block },
  :array => source, :hash => source, :from => source,
  :negative_mode => negative_mode)
# File lib/sarah.rb, line 145
def initialize (*args, &block)
    opts = (!args.empty? && args[-1].is_a?(Hash)) ? args.pop : {}
    clear
    @negative_mode = :error
    self.negative_mode = opts[:negative_mode]
    @default = opts[:default]
    @default_proc = block || opts[:default_proc]
    if !args.empty?
        case args[0]
        when Array then set *args[0]
        when Sarah
            self.default = args[0].default
            self.default_proc = args[0].default_proc
            self.negative_mode = args[0].negative_mode
            merge! args[0]
        end
    end
    merge! opts[:array], opts[:hash], opts[:from]
end
try_convert(source) click to toggle source

Try to convert the passed object to a Sarah.

In the current implementation, the object must respond to to_sarah, each_index, or each_pair.

@param source [Object] The object to try to convert @return [Sarah|nil] @since 2.0.0

# File lib/sarah.rb, line 123
def self.try_convert (source)
    if source.respond_to? :to_sarah then source.to_sarah
    elsif source.respond_to?(:each_index) || source.respond_to?(:each_pair)
        new :from => source
    else nil
    end
end

Public Instance Methods

&(other) click to toggle source

Compute the intersection with another object.

The intersection of array values is taken without regard to indexes. Random-access hash values must match by key and value.

@return [Sarah] @since 2.0.0

# File lib/sarah.rb, line 174
def & (other)
    case other
    when Array then new_similar :array => (values(:ary) & other)
    when Hash, Sarah
        new_similar(:array => (values(:ary) & _hash_filter(other, :array).
          values), :hash => other.select { |k, v| @rnd[k] == v })
    else raise TypeError.new('Unsupported type in Sarah#&')
    end
end
+(other) click to toggle source

Return a new Sarah that is the concatenation of this one and another object.

Note: concat was incorrectly aliased to + in 2.x.x. concat is actually an alias for append!.

@since 2.0.0

# File lib/sarah.rb, line 210
def + (other)
    new_similar(:from => self).append!(other)
end
-(other) click to toggle source

Compute the array difference with another object.

Random-access hash values are only removed when both keys and values match.

@return [Sarah] @since 2.0.0

# File lib/sarah.rb, line 221
def - (other)
    case other
    when Array then new_similar(:array => (values(:ary) - other),
      :hash => @rnd)
    when Hash, Sarah
        new_similar(:array => (values(:ary) -
          other.select { |k, v| k.is_a? Integer}.values),
          :hash => @rnd.select { |k, v| other[k] != v })
    else raise TypeError.new('Unsupported type in Sarah#-')
    end
end
<<(*vlist) click to toggle source

Push (append) sequential values.

@param vlist [Array] A list of values to push (append). @return [Sarah]

# File lib/sarah.rb, line 237
def << (*vlist)
    if !@spr.empty?
        # Sparse push
        vlist.each { |value| self[@ary_next] = value }
    else
        # Sequential push
        @seq.push *vlist
        @ary_next = @seq.size
    end
    self
end
Also aliased as: push
==(other) click to toggle source

Check for equality with another object.

Compares to arrays via {#to_a} and to hashes or Sarahs via {#to_h}.

@since 2.0.0

# File lib/sarah.rb, line 258
def == (other)
    case other
    when Array then to_a == other
    when Hash then to_h == other
    when Sarah then to_h == other.to_h
    else false
    end
end
Also aliased as: eql?
[](key, len = nil) click to toggle source

Get a value by sequential or random-access key. If the key does not exist, the default value or initial block value is returned. If called with a range or an additional length parameter, a slice is returned instead.

@param key The key for the value to be returned, or a range. @param len [Integer] An optional slice length. @return [Object|Array]

# File lib/sarah.rb, line 277
def [] (key, len = nil)
    # Slice cases...
    return slice(key, len) if len
    return slice(key) if key.is_a? Range

    if key.is_a? Integer
        # A key in the sequential array or sparse array hash?
        catch :index_error do
            key = _adjust_key key
            return @seq[key] if key >= 0 && key < @seq.size
            return @spr[key] if @spr.has_key? key
        end
    else
        # A key in the random-access hash?
        return @rnd[key] if @rnd.has_key? key
    end

    # Return the default value
    @default_proc ? @default_proc.call(self, key) : @default
end
Also aliased as: at
[]=(key, value) click to toggle source

Set a value by sequential or random-access key.

@param key The key for which the value should be set. @param value The value to set for the key. @return Returns the value.

# File lib/sarah.rb, line 305
def []= (key, value)
    if key.is_a? Integer
        # A key in the sequential array or sparse array hash...
        catch :index_error do
            key = _adjust_key key
            if key >= 0 && key <= @seq.size && @ary_first >= 0
                # It's in sequence
                @seq[key] = value
                _spr_to_seq if @spr.has_key? @seq.size
            else
                # It's a sparse key
                if (@seq.empty? && @spr.empty?) || key < @ary_first
                    @ary_first = key
                end
                _seq_to_spr if key < 0 && !@seq.empty?
                @spr[key] = value
            end
            @ary_next = key + 1 if key >= @ary_next
        end
    else
        # A key in the random-access hash...
        @rnd[key] = value
    end

    value
end
Also aliased as: store
append!(*ahlist) click to toggle source

Append arrays or Sarahs and merge hashes.

(concat alias since 3.0.0)

@param ahlist [Array<Array, Hash, Sarah>] The structures to append. @return [Sarah]

# File lib/sarah.rb, line 340
def append! (*ahlist)
    ahlist.each do |ah|
        if ah.is_a? Sarah
            push *ah.values(:ary)
            merge! ah.to_h(:rnd)
        elsif ah.respond_to? :each_pair
            merge! ah
        elsif ah.respond_to? :each_index
            push *ah
        end
    end
    self
end
Also aliased as: concat
assoc(other, which = :all) click to toggle source

Return the first associated array. See Array#assoc and Hash#assoc. The result is always in Array#assoc format.

@param which [:all|:ary|:seq|:spr|:rnd] Which elements to search. @return [Array|nil] @since 2.0.0

# File lib/sarah.rb, line 362
def assoc (other, which = :all)
    res = nil
    case which when :all, :ary, :seq then res ||= @seq.assoc(other) end
    case which when :all, :ary, :spr
        res ||= @spr.values_at(*@spr.keys.sort).assoc(other)
    end
    case which when :all, :rnd
        if res.nil?
            res = @rnd.assoc(other)
            res.flatten!(1) unless res.nil?
        end
    end
    res
end
at(key, len = nil)
Alias for: []
clear(which = :all) click to toggle source

Clear sequential+sparse array and/or random-access hash values.

@param which [:all|:ary|:rnd] (since 2.0.0) @return [Sarah]

# File lib/sarah.rb, line 383
def clear (which = :all)
    case which when :all, :ary
        @seq, @spr, @ary_first, @ary_next = [], {}, 0, 0
    end
    case which when :all, :rnd then @rnd = {} end
    self
end
collect!(which = :all) { |seq, index| ... } click to toggle source

Collect (map) in-place. The block (required) is passed the current value and the index/key (in that order). The return value of the block becomes the new value.

s.collect!(which) { |value, key| block }

@param which [:all|:ary|:rnd|:seq|:spr] Which data structures

are mapped.

@return [Sarah] @since 2.0.0

# File lib/sarah.rb, line 401
def collect! (which = :all)
    case which when :all, :ary, :seq
        @seq.each_index { |index| @seq[index] = yield @seq[index], index }
    end
    case which when :all, :ary, :spr
        @spr.each_pair { |key, value| @spr[key] = yield value, key }
    end
    case which when :all, :rnd
        @rnd.each_pair { |key, value| @rnd[key] = yield value, key }
    end
    self
end
Also aliased as: map!
compact!(which = :all) click to toggle source

Remove nil values in place. In the case of the sequential and sparse arrays, the remaining values are reindexed sequentially from 0.

See also {#reindex}.

@param which [:all|:ary|:rnd] Which data structures are compacted. @return [Sarah]

# File lib/sarah.rb, line 425
def compact! (which = :all)
    case which when :all, :ary
        @seq = values(:ary).compact
        @spr, @ary_first, @ary_next = {}, 0, @seq.size
    end
    case which when :all, :rnd
        @rnd.delete_if { |key, value| value.nil? }
    end
    self
end
concat(*ahlist)
Alias for: append!
count(which = :all, *args) { |item| ... } click to toggle source

Return a count of values or matching objects

s.count(which, value)
s.count(which) { |item| block }

@param which [:all|:ary|:rnd] Where to count @return [Integer]

# File lib/sarah.rb, line 445
def count (which = :all, *args)
    if !args.empty? then values(which).count args[0]
    elsif block_given? then values(which).count { |item| yield item }
    else size which
    end
end
default_proc=(proc) click to toggle source

Set the default_proc block for generating default values.

@param proc [Proc|nil] The proc block for default values.

# File lib/sarah.rb, line 457
def default_proc= (proc)
    if proc.nil? || proc.is_a?(Proc) then @default_proc = proc
    else raise TypeError.new('Default_proc must be a Proc or nil')
    end
    proc
end
delete_at(key) click to toggle source

Delete a specific index or key.

(delete_at alias since 2.0.0)

# File lib/sarah.rb, line 470
def delete_at (key)
    return unset_at(key) if @negative_mode == :actual
    if key.is_a? Integer
        res = nil
        catch :index_error do
            key = _adjust_key key
            if key >= 0 && key < @seq.size
                res = @seq.delete_at key
                _scan_spr -1
            else
                res = @spr.delete key
                _scan_spr -1, key
            end
        end
        res
    else
        @rnd.delete key
    end
end
Also aliased as: delete_key
delete_if(which = :all) { |value, key| ... } click to toggle source

Deletes each value for which the required block returns true.

Subsequent values are re-indexed except when {#negative_mode} :actual. See also {#unset_if}.

The block is passed the current value and nil for the sequential and sparse arrays or the current value and key (in that order) for the random-access hash.

s.delete_if(which) { |value, key| block }

@param which [:all|:ary|:rnd] The data structures in which to delete. @return [Sarah] @since 2.0.0

# File lib/sarah.rb, line 506
def delete_if (which = :all)
    if @negative_mode == :actual
        return unset_if(which) { |value, key| yield value, key }
    end
    case which when :all, :ary
        num_del = @seq.size
        @seq.delete_if { |value| yield value, nil }
        num_del -= @seq.size
        new_spr = {}
        @spr.keys.sort.each do |key|
            if yield @spr[key], nil then num_del += 1
            else new_spr[key - num_del] = @spr[key]
            end
        end
        @spr = new_spr
        _scan_spr
    end
    case which when :all, :rnd
        @rnd.delete_if { |key, value| yield value, key }
    end
    self
end
delete_key(key)
Alias for: delete_at
delete_value(what, which = :all) click to toggle source

Delete by value.

Subsequent values are re-indexed except when {#negative_mode} is :actual. See also {#unset_value}.

@param what [Object] The value to be deleted @param which [:all|:ary|:rnd] The data structures in which to delete. @return [Sarah]

# File lib/sarah.rb, line 537
def delete_value (what, which = :all)
    if @negative_mode == :actual
        unset_if(which) { |value, key| value == what }
    else
        delete_if(which) { |value, key| value == what }
    end
end
each(which = :all) { |i, seq| ... } click to toggle source

Iterate a block (required) over each key and value like Hash#each.

s.each(which) { |key, value| block }

(each_key alias since 2.0.0)

@param which [:all|:ary|:nsq|:rnd|:seq|:spr] The data structures over

which to iterate. (Since 2.0.0; :nsq since 2.2.0)

@return [Sarah]

# File lib/sarah.rb, line 556
def each (which = :all)
    case which when :all, :ary, :seq
        @seq.each_index { |i| yield i, @seq[i] }
    end
    case which when :all, :ary, :nsq, :spr
        @spr.keys.sort.each { |i| yield i, @spr[i] }
    end
    case which when :all, :nsq, :rnd
        @rnd.each { |key, value| yield key, value }
    end
    self
end
Also aliased as: each_index, each_key, each_pair
each_index(which = :all)
Alias for: each
each_key(which = :all)
Alias for: each
each_pair(which = :all)
Alias for: each
empty?(which = :all) click to toggle source

Is the array (or are parts of it) empty?

@param which [:all|:ary|:nsq|:rnd|:seq|:spr] Which data structures to

check. (:nsq since 2.2.0)

@return [Boolean] @since 2.0.0

# File lib/sarah.rb, line 581
def empty? (which = :all)
    case which
    when :all then @seq.empty? && @spr.empty? && @rnd.empty?
    when :ary then @seq.empty? && @spr.empty?
    when :nsq then @spr.empty? && @rnd.empty?
    when :rnd then @rnd.empty?
    when :seq then @seq.empty?
    when :spr then @spr.empty?
    else true
    end
end
eql?(other)
Alias for: ==
fetch(key, *default) { |key| ... } click to toggle source

Get a value by sequential or random-access key. If the key does not exist, the local default or block value is returned. If no local or block value is supplied, a KeyError exception is raised instead.

If a local block is supplied, it is passed the key as a parameter.

fetch(key)
fetch(key, default)
fetch(key) { |key| block }

@param key The key for the value to be returned. @param default The value to return if the key does not exist.

# File lib/sarah.rb, line 605
def fetch (key, *default)
    if key.is_a? Integer
        # A key in the sequential array or sparse array hash?
        key += @ary_next if key < 0 && @negative_mode != :actual
        return @seq[key] if key >= 0 && key < @seq.size
        return @spr[key] if @spr.has_key? key
    else
        # A key in the random-access hash?
        return @rnd[key] if @rnd.has_key? key
    end

    if !default.empty? then default[0]
    elsif block_given? then yield key
    else raise KeyError.new('Key not found')
    end
end
find_index(*args) { |value| ... } click to toggle source

Find the first index within the sequential or sparse array for the specified value or yielding true from the supplied block.

find_index(value)
find_index { |value| block }

@return [Integer|nil] @since 2.0.0

# File lib/sarah.rb, line 632
def find_index (*args)
    if block_given?
        index = @seq.index { |value| yield value }
        return index unless index.nil?
        @spr.keys.sort.each do |index|
            return index if yield @spr[index]
        end
    else
        value = args.pop
        index = @seq.index value
        return index unless index.nil?
        @spr.keys.sort.each { |index| return index if @spr[index] == value }
    end
    nil
end
Also aliased as: index
first(*args) click to toggle source

Return the first array value or first n array values.

obj = s.first
list = s.first(n)

@return [Object|Array]

# File lib/sarah.rb, line 656
def first (*args); values(:ary).first(*args); end
flatten!(*levels) click to toggle source

Flatten sequential and sparse array values in place.

@since 2.0.0

# File lib/sarah.rb, line 661
def flatten! (*levels)
    if levels.empty? then @seq = values(:ary).flatten
    else @seq = values(:ary).flatten(levels[0])
    end
    @ary_first, @ary_next, @spr = 0, @seq.size, {}
    self
end
has_key?(key) click to toggle source

Test key/index existence.

(key? and member? aliases since 2.0.0)

@param key The key to check for existence. @return [Boolean]

# File lib/sarah.rb, line 700
def has_key? (key)
    if key.is_a? Integer
        key += @ary_next if key < 0 && @negative_mode != :actual
        (key >= 0 && key < @seq.size) || @spr.has_key?(key)
    else
        @rnd.has_key? key
    end
end
Also aliased as: key?, member?
has_value?(value, which = :all) click to toggle source

Test value presence.

@param value The value for which to check presence. @param which [:all|:ary|:rnd|:seq|:spr] Where to check for presence. @return [Boolean] @since 2.0.0

# File lib/sarah.rb, line 718
def has_value? (value, which = :all)
    case which when :all, :ary, :seq
        return true if @seq.include? value
    end
    case which when :all, :ary, :spr
        return true if @spr.has_value? value
    end
    case which when :all, :rnd
        return true if @rnd.has_value? value
    end
    false
end
Also aliased as: include?, value?
include?(value, which = :all)
Alias for: has_value?
index(*args)
Alias for: find_index
insert!(*ahlist) click to toggle source

Insert arrays or Sarahs and merge hashes.

@param ahlist [Array<Array, Hash, Sarah>] The structures to insert. @return [Sarah]

# File lib/sarah.rb, line 738
def insert! (*ahlist)
    ahlist.reverse_each do |ah|
        if ah.is_a? Sarah
            unshift *ah.values(:ary)
            merge! ah.to_h(:rnd)
        elsif ah.respond_to? :each_pair
            merge! ah
        elsif ah.respond_to? :each_index
            unshift *ah
        end
    end
    self
end
inspect() click to toggle source

Return a string representation of this object.

@since 2.0.0

# File lib/sarah.rb, line 678
def inspect; self.class.name + (@seq + [@spr.merge(@rnd)]).to_s; end
Also aliased as: to_s
join(separator = nil) click to toggle source

Return the sequential and sparse array values as a string, separated by the supplied separator (or $, or the empty string).

@param separator [String] @return [String] @since 2.0.0

# File lib/sarah.rb, line 690
def join (separator = nil); values(:ary).join(separator || $, || ''); end
key(value, which = :all) click to toggle source

Return the (first) key having the specified value.

@param value The value for which the key is desired. @param which [:all|:ary|:rnd] Where to search for the value.

# File lib/sarah.rb, line 756
def key (value, which = :all)
    case which when :all, :ary
        pos = index value
        return pos unless pos.nil?
    end
    case which when :all, :rnd then return @rnd.key(value) end
    nil
end
key?(key)
Alias for: has_key?
keys(which = :all) click to toggle source

Return an array of indexes and keys.

@param which [:all|:ary|:nsq|:rnd|:seq|:spr] Which indexes and keys

to return. (Since 2.0.0; :nsq since 2.1.0)

@return [Array]

# File lib/sarah.rb, line 786
def keys (which = :all)
    case which
    when :all then keys(:seq) + keys(:spr) + @rnd.keys
    when :ary then keys(:seq) + keys(:spr)
    when :nsq then keys(:spr) + @rnd.keys
    when :rnd then @rnd.keys
    when :seq then (0...@seq.size).to_a
    when :spr then @spr.keys.sort
    else []
    end
end
last(*args) click to toggle source

Return the last array value or last n array values.

obj = s.last
list = s.last(n)

@return [Object|Array]

# File lib/sarah.rb, line 804
def last (*args); values(:ary).last(*args); end
length(which = :all) click to toggle source

Return the number of stored values (AKA size or length).

@param which [:all|:ary|:nsq|:rnd|:seq|:spr] The data structures

for which the (combined) size is to be returned. (Since 2.0.0;
:nsq since 2.1.0)

@return [Integer]

# File lib/sarah.rb, line 831
def length (which = :all)
    size = 0
    case which when :all, :ary, :seq then size += @seq.size end
    case which when :all, :ary, :nsq, :spr then size += @spr.size end
    case which when :all, :nsq, :rnd then size += @rnd.size end
    size
end
Also aliased as: size
map!(which = :all)
Alias for: collect!
member?(key)
Alias for: has_key?
merge!(*ahlist) click to toggle source

Load/merge from a hash, array, or Sarah (beginning at key 0).

@param ahlist [Array<Array, Hash, Sarah>] The structures to load/merge. @return [Sarah]

# File lib/sarah.rb, line 845
def merge! (*ahlist)
    ahlist.each do |ah|
        if ah.respond_to? :each_pair
            ah.each_pair { |key, value| self[key] = value }
        elsif ah.respond_to? :each_index
            ah.each_index { |i| self[i] = ah[i] }
        end
    end
    self
end
Also aliased as: update
negative_mode=(mode) click to toggle source

Sets the negative mode, the manner in which negative integer index/key values are handled.

# File lib/sarah.rb, line 860
def negative_mode= (mode)
    case mode
    when :actual then @negative_mode = :actual
    when :error, :ignore
        # These modes are only possible if there aren't currently
        # any negative keys.
        @negative_mode = mode if @ary_first >= 0
    end
    @negative_mode
end
new_similar(*args) click to toggle source

Return a new instance configured similarly to this one.

@return [Sarah] @since 2.0.0

# File lib/sarah.rb, line 875
def new_similar (*args)
    opts = { :default => @default, :default_proc => @default_proc,
      :negative_mode => @negative_mode }
    opts.merge! args.pop if !args.empty? && args[-1].is_a?(Hash)
    self.class.new(*args, opts)
end
pairs_at(*list) click to toggle source

Return a hash of key/value pairs corresponding to the list of keys/indexes.

@return [Hash] @since 2.0.0

# File lib/sarah.rb, line 889
def pairs_at (*list)
    pairs = {}
    list.each { |key| pairs[key] = self[key] }
    pairs
end
pop(count = nil) click to toggle source

Pop one or more values off the end of the sequential and sparse arrays.

@param count [Integer|nil] The number of items to pop (1 if nil).

(Since 2.0.0)
# File lib/sarah.rb, line 902
def pop (count = nil)
    if !count.nil?
        max = size :ary
        count = max if max < count
        res = []
        count.times { res << pop }
        res.reverse
    elsif !@seq.empty? || !@spr.empty? then delete_at(@ary_next - 1)
    else @default_proc ? @default_proc.call(self, nil) : @default
    end
end
push(*vlist)
Alias for: <<
rehash() click to toggle source

Rehash keys

@return [Sarah] @since 2.0.0

# File lib/sarah.rb, line 924
def rehash; @spr.rehash; @rnd.rehash; self; end
reindex() click to toggle source

Reindex sparse array values sequentially after any existing sequential values (or else from index 0).

This is an immediate in-place operation (not a mode) and is unaffected by the {#negative_mode}.

@return [Sarah] @since 2.1.0

# File lib/sarah.rb, line 934
def reindex
    if !@spr.empty?
        @seq.concat values(:spr)
        @spr, @ary_first, @ary_next = {}, 0, @seq.size
    end
    self
end
replace(other) click to toggle source

Replace contents with the contents of another array, hash, or Sarah.

@param other [Sarah|hash] @return [Sarah]

# File lib/sarah.rb, line 950
def replace (other)
    clear
    negative_mode = other.negative_mode if other.is_a? Sarah
    merge! other
    self
end
reverse!() click to toggle source

Reverse sequential and sparse array values into a sequential list.

@return [Sarah] @since 2.0.0

# File lib/sarah.rb, line 961
def reverse!
    @seq = values(:ary).reverse
    @ary_first, @ary_next, @spr = 0, @seq.size, {}
    self
end
reverse_each(which = :ary) { |key, value| ... } click to toggle source

Iterate a block (required) over each key and value in reverse order like Hash#each (first the random-access hash, then array values in reverse key order).

s.reverse_each(which) { |key, value| block }

@param which [:all|:ary|:seq|:spr] The data structures over

which to iterate. The random-access hash is only included for :all.

@return [Sarah] @since 2.0.0

# File lib/sarah.rb, line 977
def reverse_each (which = :ary)
    case which when :all
        @rnd.each { |key, value| yield key, value }
    end
    case which when :all, :ary, :spr
        @spr.keys.sort.reverse_each { |i| yield i, @spr[i] }
    end
    case which when :all, :ary, :seq
        (@seq.size - 1).downto(0) { |i| yield i, @seq[i] }
    end
    self
end
rindex(*args) { |spr| ... } click to toggle source

Find the last index within the sequential or sparse array for the specified value or yielding true from the supplied block.

rindex(value)
rindex { |value| block }

@return [Integer|nil] @since 2.0.0

# File lib/sarah.rb, line 998
def rindex (*args)
    if block_given?
        @spr.keys.sort.reverse_each do |index|
            return index if yield @spr[index]
        end
        return @seq.rindex { |value| yield value }
    else
        value = args.pop
        @spr.keys.sort.reverse_each do |index|
            return index if @spr[index] == value
        end
        return @seq.rindex(value)
    end
end
rnd() click to toggle source

Return a copy of the merged sparse array and random-access hash (for backward-compatibility only).

Through version 2.0.0, this returned the actual underlying random-access hash.

@return [Hash] @deprecated Please use {#to_h} :nsq instead.

# File lib/sarah.rb, line 1021
def rnd; self.to_h :nsq; end
rnd_keys() click to toggle source

Return the random-access hash keys.

Since 2.0.0 returns only the random-access keys. Since 2.1.0 returns the non-sequential (sparse + random) keys like the pre-2.0.0 version.

@return [Array] @deprecated Please use {#keys} :nsq or keys :rnd instead.

# File lib/sarah.rb, line 773
def rnd_keys; self.keys :nsq; end
rnd_length() click to toggle source

Return the random-access hash size.

Since 2.1.0, this returns the non-sequential (sparse + random) hash size, which is more closely reflects the pre-2.0.0 value.

@deprecated Please use {#size} :rnd or size :nsq instead. @return [Integer]

# File lib/sarah.rb, line 813
def rnd_length; self.size :nsq; end
Also aliased as: rnd_size
rnd_size()
Alias for: rnd_length
rnd_values() click to toggle source

Return the sparse array and random-access hash values (for backward-compatibility only).

@return [Array] @deprecated Please use {#values} :nsq or values :rnd instead.

# File lib/sarah.rb, line 1028
def rnd_values; self.values :nsq; end
rotate!(count = 1) click to toggle source

Rotate sequential and sparse array values into a sequential list.

@param count [Integer] The amount to rotate. See Array#rotate!. @return [Sarah] @since 2.0.0

# File lib/sarah.rb, line 1035
def rotate! (count = 1)
    @seq = values(:ary).rotate(count)
    @ary_first, @ary_next, @spr = 0, @seq.size, {}
    self
end
sample(*args) click to toggle source

Return a random sample from the sequential and sparse arrays like Array#sample.

@since 2.0.0

# File lib/sarah.rb, line 1045
def sample (*args); values(:ary).sample(*args); end
select(which = :all) { |key, value| ... } click to toggle source

Select a subset of values as indicated by the supplied block and return as a hash like Hash#select.

s.select(which) { |key, value| block }

@param which [:all|:ary|:rnd|:seq|:spr] Which values to select.

# File lib/sarah.rb, line 1053
def select (which = :all)
    to_h(which).select { |key, value| yield key, value }
end
seq() click to toggle source

Return a copy of the sequential array values.

Prior to 2.0.0, this returned the actual underlying array.

@return [Array] @deprecated Please use {#values} :seq instead.

# File lib/sarah.rb, line 1063
def seq; self.values :seq; end
Also aliased as: seq_values
seq_keys() click to toggle source

Return the sequential array keys (indexes).

@return [Array<Integer>] @deprecated Please use {#keys} :seq instead.

# File lib/sarah.rb, line 779
def seq_keys; self.keys :seq; end
seq_length() click to toggle source

Return the sequential array size.

@deprecated Please use {#size} :seq instead. @return [Integer]

# File lib/sarah.rb, line 821
def seq_length; @seq.size; end
Also aliased as: seq_size
seq_size()
Alias for: seq_length
seq_slice(*args)
Alias for: slice
seq_slice!(*args)
Alias for: slice!
seq_values()
Alias for: seq
set(*list) click to toggle source

Set values and/or key/value pairs (in standard Ruby calling syntax).

set(seq1, ..., seqN, key1 => val1, ..., keyN => valN)

@param list [Array] A list of sequential values or random-access

key/value pairs to set.

@return [Sarah]

# File lib/sarah.rb, line 1074
def set (*list)
    hash = (list.size > 0 and list[-1].is_a? Hash) ? list.pop : nil
    merge! list
    merge! hash if hash
    self
end
set_kv(*kvlist) click to toggle source

Set from a list of key/value pairs.

set_kv(key1, val1, ..., keyN, valN)

@param kvlist [Array] The list of key/value pairs to set. @return [Sarah]

# File lib/sarah.rb, line 1087
def set_kv (*kvlist)
    kvlist.each_slice(2) { |kv| self.[]=(*kv) }
    self
end
Also aliased as: set_pairs
set_pairs(*kvlist)
Alias for: set_kv
shift(count = nil) click to toggle source

Shift one or more values off the beginning of the sequential and sparse arrays.

Subsequent values are re-indexed unless {#negative_mode=} :actual.

@param count [Integer|nil] The number of items to shift (1 if nil).

(Since 2.0.0)
# File lib/sarah.rb, line 1101
def shift (count = nil)
    if !count.nil?
        max = size :ary
        count = max if max < count
        res = []
        count.times { res << shift }
        res
    elsif !@seq.empty? || !@spr.empty? then delete_at @ary_first
    else @default_proc ? @default_proc.call(self, nil) : @default
    end
end
shuffle(*args) click to toggle source

Return the sequential and sparse array values in a shuffled sequence like Array#shuffle.

shuffle
shuffle(random: rng)

@return [Array] @since 2.0.0

# File lib/sarah.rb, line 1121
def shuffle (*args); values(:ary).shuffle(*args); end
shuffle!(*args) click to toggle source

Replaces the sequential and sparse array values with shuffled sequential values.

shuffle!
shuffle!(random: rng)

@return [Sarah] @since 2.0.0

# File lib/sarah.rb, line 1131
def shuffle! (*args)
    @seq = values(:ary).shuffle
    @ary_first, @ary_next, @spr = 0, @seq.size, {}
    self
end
size(which = :all)
Alias for: length
slice(*args) click to toggle source

Return an element or return a slice of elements from the sequential + sparse array as a new Sarah.

The original array is unmodified.

NOTES: This implementation is new since 2.0.0 and incompatible with prior versions. Alias seq_slice is deprecated since 2.0.0.

s.slice(key)           # a single element (or nil)
s.slice(start, length) # up to length elements at index >= start
s.slice(range)         # elements with indexes within the range

@return [Object|Sarah] @since 2.0.0

# File lib/sarah.rb, line 1151
def slice (*args)
    case args.size
    when 1
        if args[0].is_a? Range
            range = args[0]
            new_similar(:hash => pairs_at(*keys(:ary).select do |key|
                range.include? key
              end))
        else fetch args[0], nil
        end
    when 2
        in_range = []
        catch :index_error do
            start = _adjust_key args[0]
            in_range = keys(:ary).select { |key| key >= start }
        end
        new_similar(:hash => pairs_at(*in_range.slice(0, args[1])))
    else nil
    end
end
Also aliased as: seq_slice
slice!(*args) click to toggle source

Extract (delete) and return an element, or a slice of elements from the sequential + sparse array as a new Sarah. Elements are unset rather than deleted when {#negative_mode=} :actual.

NOTES: This implementation is new since 2.0.0 and incompatible with prior versions. Alias seq_slice! is deprecated since 2.0.0.

s.slice!(key)           # a single element (or nil)
s.slice!(start, length) # up to length elements at index >= start
s.slice!(range)         # elements with indexes within the range

@return [Object|Sarah] @since 2.0.0

# File lib/sarah.rb, line 1187
def slice! (*args)
    case args.size
    when 1
        if args[0].is_a? Range then res = slice *args
        else return has_key?(args[0]) ? delete_key(args[0]) : nil
        end
    when 2 then res = slice *args
    else return nil
    end
    res.reverse_each(:all) { |key, value| delete_key key }
end
Also aliased as: seq_slice!
sort() { |a, b| ... } click to toggle source

Return a sorted list of sequential and sparse array values. Accepts an optional block to compare pairs of values.

s.sort
s.sort { |a, b| block }

@return [Array] @since 2.0.0

# File lib/sarah.rb, line 1209
def sort
    if block_given? then values(:ary).sort { |a, b| yield a, b }
    else values(:ary).sort
    end
end
sort!() { |a, b| ... } click to toggle source

Replace the sequential and sparse array values by a sorted sequential list of values. Accepts an optional block to compare pairs of values.

s.sort!
s.sort! { |a, b| block }

@return [Sarah] @since 2.0.0

# File lib/sarah.rb, line 1224
def sort!
    if block_given? then @seq = values(:ary).sort { |a, b| yield a, b }
    else @seq = values(:ary).sort
    end
    @ary_first, @ary_next, @spr = 0, @seq.size, {}
    self
end
store(key, value)
Alias for: []=
to_a(which = :all) click to toggle source

Return all or part of the structure in array representation.

@param which [:all|:ary|:nsq|:rnd|:seq|:spr] The parts to represent.

(:nsq since 2.1.0)

@since 2.0.0

# File lib/sarah.rb, line 1243
def to_a (which = :all)
    ary, hsh = [], {}
    case which when :all, :ary, :seq then ary = @seq end
    case which when :all, :ary, :nsq, :spr then hsh.merge! @spr end
    case which when :all, :nsq, :rnd then hsh.merge! @rnd end
    ary + [hsh]
end
to_h(which = :all) click to toggle source

Return all or part of the structure in hash representation.

@param which [:all|:ary|:nsq|:rnd|:seq|:spr] The parts to represent.

(:nsq since 2.1.0)

@since 2.0.0

# File lib/sarah.rb, line 1256
def to_h (which = :all)
    hsh = {}
    case which when :all, :ary, :seq
        @seq.each_index { |i| hsh[i] = @seq[i] }
    end
    case which when :all, :ary, :nsq, :spr then hsh.merge! @spr end
    case which when :all, :nsq, :rnd then hsh.merge! @rnd end
    hsh
end
to_s()
Alias for: inspect
uniq() { |item| ... } click to toggle source

Return unique sequential and sparse values as a sequential list.

s.uniq
s.uniq { |item| block }

@return [Array] @since 2.0.0

# File lib/sarah.rb, line 1275
def uniq
    if block_given? then values(:ary).uniq { |item| yield item }
    else values(:ary).uniq
    end
end
uniq!() { |item| ... } click to toggle source

Replace sequential and sparse values with sequential unique values.

s.uniq!
s.uniq! { |item| block }

@return [Sarah] @since 2.0.0

# File lib/sarah.rb, line 1288
def uniq!
    if block_given? then @seq = values(:ary).uniq { |item| yield item }
    else @seq = values(:ary).uniq
    end
    @ary_first, @ary_next, @spr = 0, @seq.size, {}
    self
end
unset_at(key) click to toggle source

Unset a specific index or key (without reindexing other values).

@since 2.0.0

# File lib/sarah.rb, line 1299
def unset_at (key)
    if key.is_a? Integer
        res = nil
        catch :index_error do
            key = _adjust_key key
            if key >= 0 && key < @seq.size
                _seq_to_spr key + 1
                res = @seq.pop
            else
                res = @spr.delete key
            end
            _scan_spr
        end
        res
    else
        @rnd.delete key
    end
end
Also aliased as: unset_key
unset_if(which = :all) { |seq, index| ... } click to toggle source

Unsets each value for which the required block returns true.

Subsequent values are never re-indexed. See also {#delete_if}.

The block is passed the current value and index for the sequential and sparse arrays or the current value and key (in that order) for the random-access hash.

s.unset_if { |value, key| block }

@param which [:all|:ary|:rnd] The data structures in which to unset. @since 2.0.0 @return [Sarah]

# File lib/sarah.rb, line 1333
def unset_if (which = :all)
    case which when :all, :ary
        @seq.each_index do |index|
            if yield @seq[index], index
                unset_at index
                break     # Any other sequentials are now sparse
            end
        end
        @spr.keys.sort.each do |key|
            @spr.delete(key) if yield @spr[key], key
        end
        _scan_spr
    end
    case which when :all, :rnd
        @rnd.delete_if { |key, value| yield value, key }
    end
    self
end
unset_key(key)
Alias for: unset_at
unset_value(what, which = :all) click to toggle source

Unset by value.

Subsequent values are never re-indexed. See also {#delete_value}.

@param what [Object] The value to be deleted @param which [:all|:ary|:rnd] The data structures in which to unset. @return [Sarah] @since 2.0.0

# File lib/sarah.rb, line 1360
def unset_value (what, which = :all)
    unset_if(which) { |value, key| value == what }
end
unshift(*vlist) click to toggle source

Unshift (insert) sequential values onto the beginning of the sequential or sparse array.

Subsequent values are re-indexed except when {#negative_mode=} :actual.

@param vlist [Array] A list of values to unshift (insert). @return [Sarah]

# File lib/sarah.rb, line 1371
def unshift (*vlist)
    if @negative_mode == :actual
        vlist.reverse_each do |value|
            @ary_first -= 1
            self[@ary_first] = value
        end
    else
        _scan_spr vlist.size
        @seq.unshift *vlist
    end
    self
end
update(*ahlist)
Alias for: merge!
value?(value, which = :all)
Alias for: has_value?
values(which = :all) click to toggle source

Return an array of values.

@param which [:all|:ary|:nsq|:rnd|:seq|:spr] Which values to return.

(Since 2.0.0; :nsq since 2.1.0)

@return [Array]

# File lib/sarah.rb, line 1389
def values (which = :all)
    case which
    when :all then @seq + values(:spr) + @rnd.values
    when :ary then @seq + values(:spr)
    when :nsq then values(:spr) + @rnd.values
    when :rnd then @rnd.values
    when :seq then Array.new @seq
    when :spr then @spr.values_at(*@spr.keys.sort)
    else []
    end
end
values_at(*list) click to toggle source

Return the values corresponding to the list of keys/indexes.

@return [Array] @since 2.0.0

# File lib/sarah.rb, line 1405
def values_at (*list); list.collect { |key| self[key] }; end
zip(*args) click to toggle source

Zip sequential and sparse array values with other arrays. See Array#zip.

@return [Array] @since 2.0.0

# File lib/sarah.rb, line 1412
def zip (*args); values(:ary).zip(*args); end
|(other) click to toggle source

Compute the union with another object.

Changed random-access hash values for a given key overwrite the original value.

@return [Sarah] @since 2.0.0

# File lib/sarah.rb, line 191
def | (other)
    case other
    when Array then new_similar(:hash => @rnd).merge!(values(:ary) | other)
    when Hash, Sarah
        new_similar.merge!(@rnd, _hash_filter(other, :other),
          values(:ary) | _hash_filter(other, :array).values)
    else raise TypeError.new('Unsupported type in Sarah#|')
    end
end

Protected Instance Methods

_adjust_key(key) click to toggle source

Protected methods #####

# File lib/sarah.rb, line 1418
def _adjust_key (key)
    case @negative_mode
    when :error
        raise IndexError.new('Index is too small') if key < -@ary_next
    when :ignore
        throw :index_error, nil if key < -@ary_next
    end
    if key < 0 && @negative_mode != :actual then key + @ary_next
    else key
    end
end
_hash_filter(h, type) click to toggle source

Extract integer-keyed or other parts from a hash

# File lib/sarah.rb, line 1431
def _hash_filter (h, type)
    case type
    when :array then h.select { |k, v| k.is_a?(Integer) && k >= 0 }
    when :other then h.select { |k, v| !k.is_a?(Integer) || k < 0 }
    end
end
_scan_spr(adjustment = 0, from = 0) click to toggle source

Scan the sparse array for min and max, possibly adjusting keys as we go.

# File lib/sarah.rb, line 1440
def _scan_spr (adjustment = 0, from = 0)
    @ary_first = @seq.empty? ? nil : 0
    @ary_next = @seq.size

    adj = {}               # adjusted sparse array
    @spr.each do |key, value|
        if adjustment != 0
            key += adjustment if key >= from
            adj[key] = value
        end
        @ary_first = key if !@ary_first || key < @ary_first
        @ary_next = key + 1 if key >= @ary_next
    end
    @ary_first ||= 0
    @spr = adj unless adj.empty?
end
_seq_to_spr(index = 0) click to toggle source

Migrate selected sequential values to the sparse hash.

# File lib/sarah.rb, line 1458
def _seq_to_spr (index = 0)
    (@seq.size - 1).downto(index) { |i| @spr[i] = @seq.pop }
end
_spr_to_seq() click to toggle source

Migrate newly adjacent sparse values to the sequential array.

# File lib/sarah.rb, line 1463
def _spr_to_seq
    @seq.push @spr.delete(@seq.size) while @spr.has_key? @seq.size
end