class EL_SPARSE_ARRAY_TABLE

(source code)

description

Table abstraction that is implemented either as an array or a table depending on which has the greater memory saving for the hash table data specified in make routine. The key type must be convertible to an array index, i.e. an INTEGER_32 value.

note
	description: "[
		Table abstraction that is implemented either as an array or a table depending on which has the
		greater memory saving for the hash table data specified in `make' routine.
		The key type must be convertible to an array index, i.e. an ${INTEGER_32} value.
	]"

	author: "Finnian Reilly"
	copyright: "Copyright (c) 2001-2022 Finnian Reilly"
	contact: "finnian at eiffel hyphen loop dot com"

	license: "MIT license (See: en.wikipedia.org/wiki/MIT_License)"
	date: "2025-05-05 16:59:25 GMT (Monday 5th May 2025)"
	revision: "7"

deferred class
	EL_SPARSE_ARRAY_TABLE [G, K -> HASHABLE]

inherit
	EL_HASH_TABLE [G, K]
		rename
			make as make_sized,
			previous_iteration_position as table_previous_iteration_position,
			next_iteration_position as table_next_iteration_position
		export
			{NONE} all
			{ANY} after, as_map_list, content, count, forth, found_item, item_list,
					item_for_iteration, key_for_iteration, off, start
		redefine
			forth, has, has_key, key_for_iteration, key_list, is_off_position, item, item_area,
			next_iteration_index, new_cursor, start, valid_key
		end

	EL_MODULE_EIFFEL

	EL_OBJECT_PROPERTY_I

feature {NONE} -- Initialization

	make_empty
		do
			make (create {HASH_TABLE [G, K]}.make (0))
		end

	make (a_table: HASH_TABLE [G, K])
		local
			index_upper, value: INTEGER; is_value_expanded: BOOLEAN
			table_size, i: INTEGER; array: SPECIAL [G]
		do
			is_value_expanded := ({G}).is_expanded
			default_value_index := -1

			index_upper := value.Min_value
			index_lower := value.Max_value
			across a_table as table loop
				value := as_integer (table.key)
				index_lower := value.min (index_lower)
				index_upper := value.max (index_upper)
			end
			across <<
				a_table.deleted_marks, a_table.indexes_map, a_table.keys, a_table.content
			>> as structure loop
				table_size := table_size + property (structure.item).physical_size
			end
			create array.make_filled (computed_default_value, index_upper - index_lower + 1)
			if table_size < property (array).physical_size then
				copy_from (a_table)
			else
				is_array_indexed := True; count := a_table.count
				across a_table as table loop
					i := as_integer (table.key) - index_lower
					array [i] := table.item
					if is_value_expanded and then array [i] = computed_default_value then
						default_value_index := i
					end
				end
				deleted_marks := Empty_deleted_marks; indexes_map := Empty_indexes_map
				has_default := a_table.has_default -- satisfy `has_key' "default_case" post-condition
				create keys.make_empty (0)
				content := array
				capacity := a_table.count
			end
		end

feature -- Access

	item alias "[]" (key: K): detachable G
		do
			if is_array_indexed then
				Result := content [as_integer (key) - index_lower]
			else
				Result := Precursor (key)
			end
		end

	item_area: SPECIAL [G]
		local
			i, i_upper: INTEGER; i_th_item: like item
		do
			if is_array_indexed and then attached content as l_content then
				create Result.make_empty (count)
				i_upper := l_content.count - 1
				from i := 0 until i > i_upper loop
					i_th_item := l_content [i]
					if i = default_value_index or else i_th_item /= computed_default_value then
						Result.extend (i_th_item)
					end
					i := i + 1
				end
			else
				Result := Precursor
			end
		end

	to_sparse_array: ARRAY [G]
		require
			array_indexed: is_array_indexed
		do
			create Result.make_from_special (content)
			Result.rebase (index_lower)
		end

	key_for_iteration: K
		-- Key at current cursor position
		do
			if is_array_indexed then
				Result := index_to_key (iteration_position + index_lower)
			else
				Result := Precursor
			end
		end

	key_list: EL_ARRAYED_LIST [K]
		local
			i, i_upper: INTEGER; area: SPECIAL [K]
		do
			create area.make_empty (count)
			if is_array_indexed and then attached content as l_content then
				i_upper := l_content.count - 1
				from i := 0 until i > i_upper loop
					if i = default_value_index or else l_content [i] /= computed_default_value then
						area.extend (index_to_key (index_lower + i))
					end
					i := i + 1
				end
				create Result.make_from_special (area)
			else
				Result := Precursor
			end
		end

	new_cursor: EL_SPARSE_ARRAY_TABLE_ITERATION_CURSOR [G, K]
		do
			create Result.make (Current)
			Result.start
		end

feature -- Status query

	has (key: K): BOOLEAN
		local
			i: INTEGER
		do
			if is_array_indexed then
				i := as_integer (key) - index_lower
				if content.valid_index (i) then
					if default_value_index = i then
						Result := True
					else
						Result := content [i] /= computed_default_value
					end
				end
			else
				Result := Precursor (key)
			end
		end

	has_key (key: K): BOOLEAN
		local
			i: INTEGER; l_item: G
		do
			if is_array_indexed then
				i := as_integer (key) - index_lower
				if content.valid_index (i) then
					l_item := content [i]
					if default_value_index = i then
						Result := True
						found_item := l_item

					else
						Result := content [i] /= computed_default_value
						found_item := l_item

					end
					if Result then
						control := found_constant
					else
						control := not_found_constant
					end
				end
			else
				Result := Precursor (key)
			end
		end

	is_array_indexed: BOOLEAN

	is_off_position (pos: INTEGER): BOOLEAN
			-- Is `pos' a cursor position outside the authorized range?
		do
			if is_array_indexed then
				Result := pos < 0 or pos >= content.count
			else
				Result := Precursor (pos)
			end
		end

	valid_key (key: K): BOOLEAN
		do
			if is_array_indexed then
				Result := content.valid_index (as_integer (key) - index_lower)
			else
				Result := Precursor (key)
			end
		end

feature -- Cursor movement

	start
			-- Bring cursor to first position.
		do
				-- Get lower bound of iteration if any.
			iteration_position := next_iteration_position (-1)
		end

	forth
			-- Advance cursor to next occupied position,
			-- or `off' if no such position remains.
		do
			iteration_position := next_iteration_position (iteration_position)
		end

feature {NONE} -- Implementation

	copy_from (other: HASH_TABLE [G, K])
		local
			unexported_fields: STRING
		do
			capacity := other.capacity
			content := other.content
			control := other.control
			count := other.count
			deleted_item_position := other.deleted_item_position
			deleted_marks := other.deleted_marks
			found_item := other.found_item
			has_default := other.has_default
			indexes_map := other.indexes_map
			iteration_position := other.iteration_position
			item_position := other.item_position
			iteration_position := other.iteration_position
			keys := other.keys
			object_comparison := other.object_comparison

			unexported_fields := once "[
				hash_table_version_64, ht_lowest_deleted_position, ht_deleted_key, ht_deleted_item
			]"
			Eiffel.copy_fields (other, Current, unexported_fields)
		end

	next_iteration_index (a_position, i_upper: INTEGER; is_deleted: like deleted_marks): INTEGER
		-- Given an iteration position, advanced to the next one
		local
			i: INTEGER; break: BOOLEAN
		do
			if is_array_indexed then
				from i := a_position + 1 until i > i_upper or break loop
					if i = default_value_index or else content [i] /= computed_default_value then
						break := True
					else
						i := i + 1
					end
				end
				Result := i
			else
				Result := Precursor (a_position, i_upper, is_deleted)
			end
		end

	next_iteration_position (a_position: INTEGER): INTEGER
		-- Given an iteration position, advanced to the next one
		local
			i, i_upper: INTEGER; break: BOOLEAN
		do
			i_upper := content.count - 1
			from i := a_position + 1 until i > i_upper or break loop
				if i = default_value_index or else content [i] /= computed_default_value then
					break := True
				else
					i := i + 1
				end
			end
			Result := i
		end

	previous_iteration_position (a_position: INTEGER): INTEGER
		-- Given an iteration position, go to the previous one
		local
			i: INTEGER; break: BOOLEAN
		do
			from i := a_position - 1 until i < 0 or break loop
				if i = default_value_index or else content [i] /= computed_default_value then
					break := True
				else
					i := i - 1
				end
			end
			Result := i
		end

feature {EL_SPARSE_ARRAY_TABLE_ITERATION_CURSOR} -- Deferred

	as_integer (a_key: K): INTEGER
		deferred
		end

	index_to_key (index: INTEGER): K
		deferred
		end

feature {EL_SPARSE_ARRAY_TABLE_ITERATION_CURSOR} -- Internal attributes

	index_lower: INTEGER

	default_value_index: INTEGER

feature {NONE} -- Constants

	Empty_deleted_marks: SPECIAL [BOOLEAN]
		once ("PROCESS")
			create Result.make_empty (0)
		end

	Empty_indexes_map: SPECIAL [INTEGER]
		once ("PROCESS")
			create Result.make_empty (0)
		end

end