From e3d09e6ebd20bbbd95ca151ae190db0a5cde9618 Mon Sep 17 00:00:00 2001 From: narawat Date: Sat, 27 Jun 2026 16:00:31 +0700 Subject: [PATCH] update docs --- Project.toml | 2 +- src/interface.jl | 452 +++++++++++++++++++++++++++++++++++++---------- 2 files changed, 356 insertions(+), 98 deletions(-) diff --git a/Project.toml b/Project.toml index e223031..ff42c21 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "GeneralUtils" uuid = "c6c72f09-b708-4ac8-ac7c-2084d70108fe" -version = "0.4.0" +version = "0.4.1" authors = ["tonaerospace "] [deps] diff --git a/src/interface.jl b/src/interface.jl index bd95562..48fb0c3 100644 --- a/src/interface.jl +++ b/src/interface.jl @@ -5,7 +5,7 @@ export noNegative!, randomWithProb, randomChoiceWithProb, findIndex, limitvalue, replaceLessThan, replaceBetween, cartesianAssign!, sumAlongDim3, matMul3Dto3DmanyTo1batch, matMul_3Dto4D_batchwise, isNotEqual, linearToCartesian, vectorMax, findMax, multiply_last, multiplyRandomElements, replaceElements, replaceElements!, isBetween, - isLess, allTrue, getStringBetweenCharacters, JSON3read_stringKey, mkDictPath!, + isLess, allTrue, getStringBetweenCharacters, mkDictPath!, getDictPath, detectKeywordVariation, textToDict, dictify, ordereddictify using JSON, DataStructures, Distributions, Random, Dates, UUIDs, DataFrames, CSV @@ -152,12 +152,32 @@ end """ read_textfile_by_index(folder_path::String, read_file_number::Integer=1) - with multiple text file in a folder, - this function read x_th text file in a folder (filename is sorted by OS) +# What it does + - Reads the x-th text file from a folder, where files are listed by the OS + without explicit sorting. Returns the file number, filename, and content. - # Example - utils.read_textfile_by_index(cleaned_data_path, 2) - read 2nd txt file in a folder +# Arguments +- `folder_path::String` + Path to the folder containing text files. +- `read_file_number::Integer=1` + Which file to read (1-based index). Defaults to the first file. + +# Return +- A tuple of `(file_number::Integer, filename::String, content)` where: + - `file_number` is the index of the file that was read + - `filename` is the actual filename string + - `content` is a vector of lines from the file (or `nothing` if error) + +# Notes +- Errors if `folder_path` is not a valid directory. +- Errors if `read_file_number` exceeds the number of files in the folder. + +# Examples +```jldoctest +julia> using GeneralUtils +julia> result = read_textfile_by_index("/path/to/folder", 2) +(2, "sample.txt", ["line1", "line2", ...]) +``` """ function read_textfile_by_index(folder_path::String, read_file_number::Integer=1) if isdir(folder_path) @@ -453,6 +473,34 @@ function randomChoiceOnTarget(target::AbstractVector, choiceList::AbstractVector return randomChoiceOnTarget.(target, 1, (choiceList,), (probability,)) end +""" Compute the linearly weighted average of an array. + +# What it does + - Assigns weights proportional to position indices (1, 2, 3, ...) to array + elements and returns the weighted average. + +# Arguments +- `a::Array` + Array of numeric values. Elements must support multiplication with numbers + and summation. + +# Return +- The linearly weighted average as a floating-point number. + +# Formula +For an array `a` with `n` elements, computes: +``` +sum(i * a[i]) / sum(a) for i = 1 to n +``` + +# Examples +```jldoctest +julia> using GeneralUtils +julia> a = [10, 20, 30] +julia> linearly_weighted_avg(a) +23.333333333333332 +``` +""" function linearly_weighted_avg(a::Array) total = 0.0 for (i, v) in enumerate(a) @@ -463,10 +511,36 @@ function linearly_weighted_avg(a::Array) end -""" Convert String that is holded inside a variable to Symbol -# Example -x = "hello" # x is a variable holding String "hello" \n -y = variable_to_symbol(x) # y holds :hello +""" Convert a variable's value (String) into a Symbol. + +# What it does + - Takes a variable containing a String value and converts it to a Symbol + using Julia's expression interpolation mechanism. + +# Arguments +- `variable` + Any variable whose value is a String. The function uses `string(variable)` + internally to obtain the value. + +# Return +- A `Symbol` constructed from the string value of the input variable. + +# Notes +- This function uses `:$variable` interpolation to capture the variable's + value as a Symbol. It works with any variable type that can be converted + to String. + +# Examples +```jldoctest +julia> using GeneralUtils +julia> x = "hello" +julia> variable_str_to_symbol(x) +:hello + +julia> y = "world_test" +julia> variable_str_to_symbol(y) +:world_test +``` """ function variable_str_to_symbol(variable) semi = :($variable) @@ -510,6 +584,45 @@ function fieldname_useable_type(somestruct, fieldname::Symbol; end +""" Draw unique elements from a list without replacement. + +# What it does + - Randomly selects a specified number of distinct elements from a collection, + optionally excluding certain elements from consideration. Uses in-place + shuffling for efficiency. + +# Arguments +- `drawOptions::Array` + Collection of elements to draw from. +- `draw_number::Integer` + Number of unique elements to draw. + +# Keyword Arguments +- `exclude_list::Union{AbstractArray,Nothing}=nothing` + Elements to exclude from the drawing pool. If `nothing`, no elements are + excluded. + +# Return +- An array of `draw_number` unique elements drawn from `drawOptions`, excluding + any elements in `exclude_list`. + +# Notes +- The function copies `drawOptions` and shuffles in-place, then pops elements + sequentially to ensure uniqueness. +- Errors if `draw_number` exceeds the number of available elements after + exclusion. + +# Examples +```jldoctest +julia> using GeneralUtils +julia> options = [1, 2, 3, 4, 5] +julia> randomNoRepeat(options, 3) +[3, 1, 5] + +julia> randomNoRepeat(options, 2; exclude_list=[1, 5]) +[4, 2] +``` +""" function randomNoRepeat(drawOptions::Array, draw_number::Integer; exclude_list::Union{AbstractArray,Nothing}=nothing) draw_option = copy(drawOptions) @@ -676,22 +789,49 @@ function selectRange(d::Dict{Symbol, <:AbstractVector}, range) return newDict end -""" Assign value to a given Dict by array of keys +""" assignDict!(dict::Dict, accessArray::Array{Symbol}, valueToAssign) - # Example - d = Dict( - :a1=> Dict(:c=> 5), - :a2=> Dict( - :k=> 10, - :b=> Dict( - :s=> "target", - ) - ) - ) - index = [:a2, :b, :s] \n - assignDict!(d, [:a2, :b, :s], "wow") +# What it does + - Recursively traverses a nested dictionary structure using a vector of keys + and assigns a value to the final key. Creates intermediate dictionaries + if they don't exist. - return 1 if no target key in a given dict. +# Arguments +- `dict::Dict` + The root dictionary to traverse and modify. +- `accessArray::Array{Symbol}` + A vector of symbols representing the key path to traverse. +- `valueToAssign` + The value to assign at the final key in the path. + +# Return +- `0` on success (value assigned) +- `1` if the path cannot be traversed (missing intermediate keys) + +# Notes +- The function walks through each key in `accessArray` except the last one, + expecting intermediate keys to already exist in the dictionary. +- If any intermediate key is missing, the function returns `1` without + modifying the dictionary. +- The final key in `accessArray` receives the `valueToAssign`. + +# Examples +```jldoctest +julia> using GeneralUtils +julia> d = Dict( + :a1=> Dict(:c=> 5), + :a2=> Dict( + :k=> 10, + :b=> Dict( + :s=> "target", + ) + ) + ) +julia> assignDict!(d, [:a2, :b, :s], "wow") +0 +julia> d[:a2][:b][:s] +"wow" +``` """ function assignDict!(dict::Dict, accessArray::Array{Symbol}, valueToAssign) wd = nothing @@ -712,9 +852,42 @@ function assignDict!(dict::Dict, accessArray::Array{Symbol}, valueToAssign) end end -""" convert hour(0-23), minute(0-59) into julia time object -# Example - time +""" iTime(h::Integer, m::Integer) + +# What it does + - Converts hour (0-23) and minute (0-59) into a Julia `Time` object using + 12-hour format with AM/PM indicator. + +# Arguments +- `h::Integer` + Hour in 24-hour format (0 to 23). +- `m::Integer` + Minute (0 to 59). + +# Return +- A `Time` object representing the time in 12-hour format with AM/PM. + +# Notes +- Hours 0 and 12 are special cases: 0 becomes 12 AM, 12 becomes 12 PM. +- Hours 1-11 remain the same with "am" suffix. +- Hours 13-23 are converted to 1-11 with "pm" suffix. +- Minutes less than 10 are zero-padded. + +# Examples +```jldoctest +julia> using GeneralUtils +julia> iTime(0, 30) +12:30 AM + +julia> iTime(9, 15) +9:15 AM + +julia> iTime(12, 0) +12:00 PM + +julia> iTime(14, 5) +2:05 PM +``` """ function iTime(h::Integer, m::Integer) if h == 0 @@ -758,19 +931,40 @@ function limitvalue(v::Number, lowerbound::Pair, upperbound::Pair) end -""" Assign matrix b to matrix a according to matrix b's CartesianIndex. +""" cartesianAssign!(a, b) - Arguments:\n - a : target matrix. - b : source matrix. - - Return:\n - Resulting matrix a. +# What it does + - Assigns elements from matrix `b` to matrix `a` using the Cartesian indices + of `b`. Elements are copied in the order they appear when iterating over `b`, + and placed into `a` at the corresponding Cartesian positions of `b`. - Example:\n - ```jldoctest - julia> not done yet - ``` +# Arguments +- `a` + Target matrix where values from `b` will be assigned. +- `b` + Source matrix whose Cartesian indices determine where values are placed in `a`. + +# Return +- `nothing` + +# Notes +- The function iterates through `b` in column-major order (Julia's default), + retrieving each element's Cartesian index and assigning it to the same + position in `a`. +- Matrix `a` must have sufficient size to accommodate all Cartesian indices + from `b`; otherwise, an `BoundsError` may occur. + +# Examples +```jldoctest +julia> using GeneralUtils +julia> a = zeros(4, 4); +julia> b = [1 2; 3 4]; +julia> cartesianAssign!(a, b); +julia> a[1:2, 1:2] +2×2 Matrix{Float64}: + 1.0 3.0 + 2.0 4.0 +``` """ function cartesianAssign!(a, b) for (i, v) in enumerate(b) @@ -1125,7 +1319,6 @@ julia> text = "{\"ask\": {\"text\": \"Could you please tell me about the special julia> GeneralUtils.getStringBetweenCharacters(text, '{', '}', endCharLocation="end") "{\"ask\": {\"text\": \"Could you please tell me about the special event?\"\n}}" ``` -# Signature """ function getStringBetweenCharacters(text::T, startChar::Char, endChar::Char; endCharLocation::String="next", includeChar::Bool=true)::String where {T<:AbstractString} @@ -1157,59 +1350,43 @@ function getStringBetweenCharacters(text::T, startChar::Char, endChar::Char; end +""" mkDictPath!(dict::Union{Dict{Symbol, Any}, Dict{String, Any}}, addkeys::Union{Vector{String}, Vector{Symbol}}, value) -""" Read JSON string and return a dictionary with string key. (JSON3 defaults to symbol key) - This function solve the problem of reading JSON with string key. - - Arguments: - jsonString::String - - Return: - a dictionary with string key - - Example: - ```jldoctest - julia> jsonString = {\"wine type\": \"Red\", \"intensity level\": \"medium-bodied\"} - julia> JSON3read_stringKey(jsonString) - Dict{String, Any} with 2 entries: - "intensity level" => "medium-bodied" - "wine type" => "Red" - ``` -""" -function JSON3read_stringKey(jsonString::AbstractString) - jsonobj = JSON3.read(jsonString) - newDict = OrderedDict{String,Any}() - for (k,v) in jsonobj - newDict[string(k)] = v - end - return newDict -end - - -""" Create nested dict path if it does not already exist. The same concept as Julia's mkpath() +# What it does + - Recursively creates nested dictionary paths if they do not exist and assigns + a value to the final key. Similar to `mkpath()` but for dictionaries. # Arguments - - `dict::Dict` - target dict - - `addkeys::Union{Vector{String}, Vector{Symbol}}` - keys to be added to dict - - `value` - value to be added to dict at final key in keypath +- `dict::Union{Dict{Symbol, Any}, Dict{String, Any}}` + The target dictionary to traverse and modify. Must use consistent key types + (either all `String` or all `Symbol`). +- `addkeys::Union{Vector{String}, Vector{Symbol}}` + A vector of keys representing the path to traverse. Intermediate dictionaries + are created if they don't exist. +- `value` + The value to assign at the final key in the path. # Return - - dict with added keypath +- The assigned `value`. -# Example +# Notes +- The function ensures key type consistency: the type of keys being added must + match the type of existing keys in the dictionary. +- Intermediate dictionaries are automatically created with the appropriate key + type when they don't exist. +- The function walks through each key in `addkeys` except the last one, + creating intermediate dictionaries as needed, and assigns `value` to the final + key. + +# Examples ```jldoctest -julia> using Revise julia> using GeneralUtils -julia> d = Dict{String, Any}("a" => Dict{String, Any}("b" => 10)) -julia> GeneralUtils.mkDictPath!(d, ["a", "v", "x", "y", "z"], 42) -Dict{String, Any} with 1 entry: -"path" => Dict{Any, Any}("to"=>Dict{Any, Any}("nested"=>Dict{Any, Any}("value"=>42))) +julia> d = Dict("a" => Dict("b" => 10)) +julia> mkDictPath!(d, ["a", "v", "x", "y", "z"], 42) +42 +julia> d["a"]["v"]["x"]["y"]["z"] +42 ``` - -# Signature """ function mkDictPath!(dict::Union{Dict{Symbol, Any}, Dict{String, Any}}, addkeys::Union{Vector{String}, Vector{Symbol}}, value) @@ -1233,27 +1410,36 @@ function mkDictPath!(dict::Union{Dict{Symbol, Any}, Dict{String, Any}}, end -""" Get nested dict value using a vector of keys +""" getDictPath(dict::Dict, keys::Vector) + +# What it does + - Retrieves a value from a nested dictionary by traversing a vector of keys. + Creates intermediate dictionaries if they don't exist. # Arguments - - `dict::Dict` - target dict - - `keys::Vector` - keys vector +- `dict::Dict` + The root dictionary to traverse. +- `keys::Vector` + A vector of keys representing the path to traverse. Each key in the vector + is used to access the next level of nesting. # Return - - dict with added keypath +- The value at the final key in the path. -# Example +# Notes +- Errors with `ArgumentError` if any intermediate key is missing from the + dictionary path. +- The function walks through each key in `keys` except the last one, + expecting intermediate keys to exist in the dictionary. +- The final key in `keys` is used to retrieve the value. + +# Examples ```jldoctest -julia> using Revise julia> using GeneralUtils -julia> d = Dict{Symbol, Any}(:a => Dict{Symbol, Any}(:b => 10)) -julia> GeneralUtils.getDictPath(d, [:a, :b]) +julia> d = Dict(:a => Dict(:b => 10)) +julia> getDictPath(d, [:a, :b]) 10 ``` - -# Signature """ function getDictPath(dict::Dict, keys::Vector) current_dict = dict @@ -1405,8 +1591,6 @@ OrderedCollections.OrderedDict{Any, Any}(:thought => "what to do", :plan => "wake up and going out", :action => "1. wake up 2. eat 3. sleep") ``` - -# Signature """ function textToDict(text::String, detectKeywords::Vector{String}; dictKey::Union{Vector{String}, Nothing}=nothing, @@ -1467,6 +1651,80 @@ function textToDict(text::String, detectKeywords::Vector{String}; return od2 end +""" Recursively convert dictionary into an HTML string representation. + +# What it does + - Walks a nested `AbstractDict` structure and produces a well-formed HTML string + where each dictionary key becomes an HTML tag. Nested dictionaries become + nested tags, and scalar values (numbers, strings, etc.) become the text + content of leaf tags. + +# Arguments +- `d::AbstractDict` + The dictionary to convert. Keys must be strings or symbols that form valid + HTML tag names. + +# Keyword Arguments +- `indent_level::Integer=1` + Initial indentation level for the output. Each recursive level increases + indentation by one. +- `indent_str::String=" "` + String used for each indentation level. + +# Return +- A single HTML string representing the dictionary structure, with proper + opening and closing tags and appropriate indentation. + +# Notes +- Keys are sorted alphabetically for deterministic output. +- Works recursively: dictionary values produce nested tags; non-dictionary + values are placed as text between opening/closing tags. + +# Examples +```jldoctest +julia> d = Dict( + "html" => Dict( + "head" => Dict("title" => "Test"), + "body" => Dict( + "h1" => "Hello", + "p" => "World" + ) + ) + ); + +julia> println(dict_to_string_html(d)) + + + Test + + +

Hello

+

World

+ + +``` +""" +function dict_to_string_html(d::AbstractDict; indent_level=1, indent_str=" ") + lines = String[] + padding = indent_str ^ indent_level + + # Sort keys for predictable, clean output + for k in sort(collect(keys(d))) + v = d[k] + + if v isa AbstractDict + # Open tag, recurse for children, then close tag + push!(lines, "$padding<$k>") + ind_level = indent_level + 1 + push!(lines, dict_to_string_html(v; indent_level=ind_level, indent_str=indent_str)) + push!(lines, "$padding") + else + # Leaf node: put key and value on a single line + push!(lines, "$padding<$k>$v") + end + end + return join(lines, "\n") +end