Skip to content

Instantly share code, notes, and snippets.

@eevmanu
Created June 4, 2025 15:17
Show Gist options
  • Save eevmanu/229d81cd8886a4ca7a77a7e7bd63a2b0 to your computer and use it in GitHub Desktop.
Save eevmanu/229d81cd8886a4ca7a77a7e7bd63a2b0 to your computer and use it in GitHub Desktop.
2 scripts to decompose a number that is greater than 501, split into numbers between 501 to 999, close to hundreds first, later close to tenth, later close to digit, in blocks which sum is lower than 5000 in case the number to decompose is greater than 5000 , little algorithm for yape cambiar dolares since yape te da un tasa de cambio preferenci…
#!/usr/bin/env python3
"""
Number Decomposition Module
This module decomposes a given number into sub-numbers within a specified range,
optimizing for "roundness" (preference for multiples of 100, then 10).
Usage Examples:
python decompose_number.py 1234
python decompose_number.py 5000
python decompose_number.py 10000
The module takes a single integer argument and decomposes it into sub-numbers
between MIN_SUB_VALUE (501) and MAX_SUB_VALUE (999), with optimization for
rounder numbers (multiples of 100 are preferred, then multiples of 10).
For numbers larger than CHUNK_THRESHOLD (5000), the output is grouped into
chunks to improve readability.
Constants:
MIN_SUB_VALUE (int): Minimum value for sub-numbers (501)
MAX_SUB_VALUE (int): Maximum value for sub-numbers (999)
CHUNK_THRESHOLD (int): Threshold for grouping output (5000)
Functions:
get_niceness(val): Scores a number based on its roundness
get_initial_decomposition(n): Decomposes number into valid sub-numbers
optimize_parts(parts_list_orig): Optimizes sub-numbers for roundness
decompose_number_main(n_total): Main decomposition function
test_decompose_number_main(n_total): Test function with predefined values
Example Output:
$ python decompose_number.py 1234
Number: 1234
Sub-numbers: [600, 634], Sum: 1234
$ python decompose_number.py 6000
Number: 6000
Group 1: [900, 900, 900, 900, 900], Sum: 4500
Group 2: [800, 700], Sum: 1500
"""
import math
import sys
# --- Constants ---
MIN_SUB_VALUE = 501
MAX_SUB_VALUE = 999
CHUNK_THRESHOLD = 5000 # For grouping output if N_total > 5000
# --- Helper Functions ---
def get_niceness(val):
"""
Scores a number based on its roundness. Higher score is better.
"""
if not (MIN_SUB_VALUE <= val <= MAX_SUB_VALUE): # Should not happen if logic is correct
return -1 # Invalid part
if val % 100 == 0:
return 3 # Multiple of 100
elif val % 10 == 0:
return 2 # Multiple of 10
else:
return 1 # Not particularly round
def get_initial_decomposition(n):
"""
Decomposes a number n into sub-numbers, each between MIN_SUB_VALUE and MAX_SUB_VALUE.
Returns a list of numbers or an error string.
"""
if not isinstance(n, int):
return "Input for initial decomposition must be an integer."
if n == 0: # No parts needed for zero
return []
if n < MIN_SUB_VALUE:
return (f"Value {n} is too small to form any sub-number "
f"(min is {MIN_SUB_VALUE}). It must be 0 or >= {MIN_SUB_VALUE}.")
if MIN_SUB_VALUE <= n <= MAX_SUB_VALUE:
# If n itself is a valid single sub-number
return [n]
# Now, n > MAX_SUB_VALUE (or n requires multiple parts like 1002)
min_k = (n + MAX_SUB_VALUE - 1) // MAX_SUB_VALUE # ceil(n / MAX_SUB_VALUE)
max_k = n // MIN_SUB_VALUE # floor(n / MIN_SUB_VALUE)
if min_k > max_k or min_k == 0 : # min_k can be 0 if n is small, handled by earlier checks
# min_k > max_k handles cases like 1000, 1001
return (f"Number {n} cannot be decomposed into sub-numbers between "
f"{MIN_SUB_VALUE} and {MAX_SUB_VALUE} (e.g. N=1000, 1001).")
k = min_k # Use min_k to get fewer, larger parts initially
base_value = n // k
remainder = n % k
sub_numbers = []
for i in range(k):
current_part = base_value + 1 if i < remainder else base_value
sub_numbers.append(current_part)
# Verification
current_sum = sum(sub_numbers)
all_valid_range = all(MIN_SUB_VALUE <= val <= MAX_SUB_VALUE for val in sub_numbers)
if not (current_sum == n and all_valid_range):
# This should ideally not be hit if logic for k and distribution is correct
return (f"Internal error in initial decomposition for {n}. "
f"Parts: {sub_numbers}, Sum: {current_sum}, All valid: {all_valid_range}")
return sub_numbers
def optimize_parts(parts_list_orig):
"""
Optimizes the list of sub-numbers to prefer rounder numbers.
Iteratively adjusts pairs of numbers.
"""
parts = list(parts_list_orig) # Work on a copy
k = len(parts)
if k < 2:
return parts # Nothing to optimize with fewer than 2 parts
improved_in_outer_loop = True
max_outer_loops = k * k * 5 # Safety break for very complex scenarios, adjust as needed
outer_loop_count = 0
while improved_in_outer_loop and outer_loop_count < max_outer_loops:
improved_in_outer_loop = False
outer_loop_count += 1
for i in range(k):
for j in range(i + 1, k):
s_i, s_j = parts[i], parts[j]
original_pair_sum = s_i + s_j
current_best_niceness_sum = get_niceness(s_i) + get_niceness(s_j)
best_si_cand, best_sj_cand = s_i, s_j
# Potential candidates for s_i by trying to make it rounder
# Check values around s_i, then calculate required s_j
# Include s_i itself to ensure we don't worsen things if no better option
potential_si_targets = sorted(list(set([
s_i,
(s_i // 100) * 100, # floor to hundred
((s_i + 99) // 100) * 100, # ceil to hundred
(s_i // 10) * 10, # floor to ten
((s_i + 9) // 10) * 10, # ceil to ten
])))
for si_target in potential_si_targets:
sj_candidate = original_pair_sum - si_target
if MIN_SUB_VALUE <= si_target <= MAX_SUB_VALUE and \
MIN_SUB_VALUE <= sj_candidate <= MAX_SUB_VALUE:
candidate_niceness_sum = get_niceness(si_target) + get_niceness(sj_candidate)
if candidate_niceness_sum > current_best_niceness_sum:
current_best_niceness_sum = candidate_niceness_sum
best_si_cand, best_sj_cand = si_target, sj_candidate
# Optional: Add tie-breaking if scores are equal.
# For now, only strictly better total niceness updates.
# Symmetrically, try to make s_j rounder and adjust s_i
potential_sj_targets = sorted(list(set([
s_j,
(s_j // 100) * 100,
((s_j + 99) // 100) * 100,
(s_j // 10) * 10,
((s_j + 9) // 10) * 10,
])))
for sj_target in potential_sj_targets:
si_candidate = original_pair_sum - sj_target
if MIN_SUB_VALUE <= sj_target <= MAX_SUB_VALUE and \
MIN_SUB_VALUE <= si_candidate <= MAX_SUB_VALUE:
candidate_niceness_sum = get_niceness(si_candidate) + get_niceness(sj_target)
if candidate_niceness_sum > current_best_niceness_sum:
current_best_niceness_sum = candidate_niceness_sum
best_si_cand, best_sj_cand = si_candidate, sj_target
if best_si_cand != s_i or best_sj_cand != s_j: # If an improvement was found for this pair
parts[i] = best_si_cand
parts[j] = best_sj_cand
improved_in_outer_loop = True # Signal to re-iterate over all pairs in a new outer loop pass
if outer_loop_count == max_outer_loops:
print(f"Warning: Max iterations ({max_outer_loops}) reached during optimization for original parts: {parts_list_orig}. Result: {parts}")
return parts
# --- Main Function ---
def decompose_number_main(n_total):
"""
Main function to decompose N_total.
Handles N_total >= 1000.
Optimizes sub-numbers for roundness.
Groups output if N_total > CHUNK_THRESHOLD.
"""
# if not isinstance(n_total, int) or n_total < 1000:
# return "Input number must be an integer >= 1000."
if not isinstance(n_total, int) or n_total < 501:
return "Input number must be an integer >= 501."
# if not isinstance(n_total, int):
# return "Input number must be an integer."
# 1. Get the initial decomposition for the entire N_total
initial_parts_flat = get_initial_decomposition(n_total)
if isinstance(initial_parts_flat, str): # This is an error message
return initial_parts_flat
if not initial_parts_flat and n_total > 0 : # Should be caught by error string from get_initial_decomposition
return f"Number {n_total} could not be decomposed initially (e.g. 1000, 1001)."
# 2. Optimize the flat list of parts
optimized_parts_flat = optimize_parts(initial_parts_flat)
# 3. Group output if N_total > CHUNK_THRESHOLD
if n_total <= CHUNK_THRESHOLD:
return optimized_parts_flat # Return a single list
else:
# Group the optimized_parts_flat into blocks whose sum <= CHUNK_THRESHOLD
grouped_results = []
current_block = []
current_block_sum = 0
for part_val in optimized_parts_flat:
# Add to current block if it's empty or adding the part doesn't exceed threshold
if not current_block or (current_block_sum + part_val <= CHUNK_THRESHOLD):
current_block.append(part_val)
current_block_sum += part_val
else:
# Current block is full (or would become too full), start a new one
grouped_results.append(current_block)
current_block = [part_val] # Start new block with current part
current_block_sum = part_val
if current_block: # Add the last remaining block
grouped_results.append(current_block)
return grouped_results # Return list of lists
def test_decompose_number_main(n_total):
"""
Test function for decompose_number_main with various input values.
This function tests the decompose_number_main function with a predefined set of
test numbers ranging from 500 to 5000. For each test number, it calls the
decompose_number_main function and displays the results including:
- The original number being decomposed
- The resulting sub-numbers (if successful decomposition)
- The sum of sub-numbers to verify correctness
- Whether all parts fall within the valid range [MIN_SUB_VALUE, MAX_SUB_VALUE]
The function includes breakpoints for debugging purposes and stores all results
in a dictionary for further analysis.
Args:
n_total: Total number parameter (appears to be unused in current implementation)
Returns:
None: This function prints results and doesn't return a value
Note:
This function assumes the existence of:
- decompose_number_main() function
- MIN_SUB_VALUE and MAX_SUB_VALUE constants
"""
# Let's test with some numbers
test_numbers = [500, 501, 502, 998, 999, 1000, 1001, 1002, 1500, 1998, 2000, 2500, 5000,]
results = {}
print("Decomposition Test Results:")
for num in test_numbers:
print(f" Number: {num}")
result = decompose_number_main(num)
results[num] = result
breakpoint()
if isinstance(result, list):
print(f"-> Sub-numbers: {result}, Sum: {sum(result)}")
all_parts_in_range = all(MIN_SUB_VALUE <= val <= MAX_SUB_VALUE for val in result)
print(f" All parts in [{MIN_SUB_VALUE}, {MAX_SUB_VALUE}]: {all_parts_in_range}")
else:
print(f"-> {result}")
breakpoint()
if __name__ == "__main__":
if len(sys.argv) > 1:
try:
input_number = int(sys.argv[1])
result = decompose_number_main(input_number)
print(f"Number: {input_number}")
if isinstance(result, list):
if all(isinstance(item, list) for item in result):
# Grouped results
for i, group in enumerate(result):
print(f"Group {i+1}: {group}, Sum: {sum(group)}")
else:
# Single list
print(f"Sub-numbers: {result}, Sum: {sum(result)}")
else:
print(f"Error: {result}")
except ValueError:
print("Error: Please provide a valid integer as argument.")
except IndexError:
print("Error: Please provide a number as argument.")
else:
print("Usage: python3 decompose_number.py <number>")
# Run the test when script is executed directly
pass
#!/usr/bin/env bash
# Expected Script Purpose (based on filename):
# - Likely decomposes a number into its constituent parts (digits, factors, etc.)
# - May break down numbers into prime factors, place values, or mathematical components
#
# Example usage scenarios (inferred from filename):
# ./decompose_number.sh 123 # Decompose number into digits: 1, 2, 3
# ./decompose_number.sh 24 # Find prime factors: 2^3 * 3
# ./decompose_number.sh -p 100 # Decompose into prime factors
# ./decompose_number.sh -d 456 # Break into place values: 400 + 50 + 6
#
# Edge cases to consider:
# - Negative numbers: ./decompose_number.sh -42
# - Zero: ./decompose_number.sh 0
# - Large numbers: ./decompose_number.sh 999999999
# - Decimal numbers: ./decompose_number.sh 3.14159
# - Invalid input: ./decompose_number.sh abc
# Split a single block (1000-4999) – greedy but honours the same preference
split_block() {
local target=$1 remain=$1 out=()
while (( remain )); do
# 1. try multiples of 100
local cand=$(( (remain>999 ? 999 : remain) / 100 * 100 ))
while (( cand >= 501 )); do
(( remain -= cand ))
if (( remain == 0 || remain >= 501 )); then
out+=("$cand")
break
fi
(( remain += cand, cand -= 100 )) # back-off & retry
done
# 2. if step 1 failed, fall back to multiples of 10
if (( cand < 501 )); then
cand=$(( (remain>999 ? 999 : remain) / 10 * 10 ))
while (( cand >= 501 )); do
(( remain -= cand ))
if (( remain == 0 || remain >= 501 )); then
out+=("$cand"); break
fi
(( remain += cand, cand -= 10 ))
done
fi
# 3. last resort: exact number (must be within range)
if (( cand < 501 )); then
if (( remain < 501 || remain > 999 )); then
echo "Impossible to split $target" >&2; return 1
fi
out+=("$remain"); remain=0
fi
done
printf '%s ' "${out[@]}"; echo
}
# Dispatcher for any input ≥1000
split_number() {
local n=$1
while (( n > 0 )); do
local block=$(( n > 4999 ? 4999 : n ))
if (( n - block > 0 && n - block < 1000 )); then
block=$(( block - (1000 - (n - block)) ))
fi
split_block "$block" || return 1
n=$(( n - block ))
done
}
# ---- quick test ------------------------------------------------------------
# for t in 500 501 502 998 999 1000 1001 1002 1300 2000 1501 4999 7321; do
# echo -n "$t → "; split_number "$t"
# done
# ---- quick test ------------------------------------------------------------
# Main execution
if [[ $# -eq 0 ]]; then
echo "Usage: $0 <number1> [number2] [number3] ..." >&2
exit 1
fi
for num in "$@"; do
if [[ ! $num =~ ^[0-9]+$ ]]; then
echo "Error: '$num' is not a valid number" >&2
exit 1
fi
echo -n "$num → "
split_number "$num"
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment