seekia/internal/helpers/helpers.go

2147 lines
47 KiB
Go

// helpers provides miscellaneous functions
package helpers
import "seekia/internal/encoding"
import "seekia/internal/identity"
import "seekia/internal/translation"
import "errors"
import "math"
import "strconv"
import "strings"
import "time"
import "slices"
import "maps"
import cryptoRand "crypto/rand"
import mathRand "math/rand/v2"
//Outputs:
// -int: Feet
// -float64: Inches
// -error
func ConvertCentimetersToFeetInches(centimeters float64) (int, float64, error) {
if (centimeters < 0){
return 0, 0, errors.New("ConvertCentimetersToFeetInches called with negative centimeters.")
}
totalInches := centimeters / 2.54
feetFloored := math.Floor(totalInches/12)
inches := totalInches - (feetFloored * 12)
feetInt := int(feetFloored)
return feetInt, inches, nil
}
//Outputs:
// -string: Example: 1 foot, 10 centimeters
// -error
func ConvertCentimetersToFeetInchesTranslatedString(centimeters float64)(string, error){
inputFeet, inputInches, err := ConvertCentimetersToFeetInches(centimeters)
if (err != nil) {
return "", errors.New("ConvertCentimetersToFeetInchesTranslatedString called with invalid centimeters.")
}
getInchUnits := func()string{
if (inputInches == 1){
result := translation.TranslateTextFromEnglishToMyLanguage("inch")
return result
}
inchesTranslated := translation.TranslateTextFromEnglishToMyLanguage("inches")
return inchesTranslated
}
inchUnits := getInchUnits()
inputInchesString := ConvertFloat64ToStringRounded(inputInches, 1)
if (inputFeet == 0){
formattedResult := inputInchesString + " " + inchUnits
return formattedResult, nil
}
getFeetUnits := func()string{
if (inputFeet <= 1){
result := translation.TranslateTextFromEnglishToMyLanguage("foot")
return result
}
feetTranslated := translation.TranslateTextFromEnglishToMyLanguage("feet")
return feetTranslated
}
feetUnits := getFeetUnits()
inputFeetString := ConvertIntToString(inputFeet)
formattedResult := inputFeetString + " " + feetUnits + ", " + inputInchesString + " " + inchUnits
return formattedResult, nil
}
func ConvertFeetInchesToCentimeters(feet int, inches float64)(float64, error) {
if (feet < 0 || inches < 0){
return 0, errors.New("ConvertFeetInchesToCentimeters called with negative feet/inches.")
}
totalInches := float64(feet*12) + inches
centimeters := totalInches * 2.54
return centimeters, nil
}
func ConvertKilometersToMiles(kilometers float64)(float64, error){
if (kilometers < 0){
return 0, errors.New("ConvertKilometersToMiles called with negative kilometers.")
}
miles := kilometers * 0.621371
return miles, nil
}
func ConvertMilesToKilometers(miles float64)(float64, error){
if (miles < 0){
return 0, errors.New("ConvertMilesToKilometers called with negative miles.")
}
kilometers := miles * 1.609344
return kilometers, nil
}
func VerifyLatitude(latitude float64)bool{
if (latitude >= -90 && latitude <= 90){
return true
}
return false
}
func VerifyLongitude(longitude float64)bool{
if (longitude >= -180 && longitude <= 180){
return true
}
return false
}
func VerifyStringIsFloat(input string)bool{
_, err := ConvertStringToFloat64(input)
if (err != nil){
return false
}
return true
}
func VerifyStringIsIntWithinRange(inputString string, targetMin int64, targetMax int64)(bool, error){
if (targetMin >= targetMax){
return false, errors.New("VerifyStringIsIntWithinRange called with min greater than or equal to max")
}
num, err := ConvertStringToInt64(inputString)
if (err != nil) {
return false, nil
}
if (num < targetMin || num > targetMax){
return false, nil
}
return true, nil
}
func VerifyStringIsFloatWithinRange(inputString string, targetMin float64, targetMax float64)(bool, error){
if (targetMin >= targetMax){
return false, errors.New("VerifyStringIsFloatWithinRange called with min greater than or equal to max")
}
num, err := ConvertStringToFloat64(inputString)
if (err != nil) {
return false, nil
}
if (num < targetMin || num > targetMax){
return false, nil
}
return true, nil
}
func CheckIfFloat64IsInteger(float float64)bool{
floatRounded := float64(int64(float))
if (float == floatRounded){
return true
}
return false
}
func FloorFloat64ToInt(input float64)(int, error){
flooredFloat := math.Floor(input)
if (flooredFloat < -2147483648 || flooredFloat > 2147483647){
return 0, errors.New("FloorFloat64ToInt called with float out of range.")
}
newInt := int(flooredFloat)
return newInt, nil
}
func FloorFloat64ToInt64(input float64)(int64, error){
flooredFloat := math.Floor(input)
if (flooredFloat < -9223372036854775808 || flooredFloat > 9223372036854775807){
return 0, errors.New("FloorFloat64ToInt64 called with float out of range.")
}
newInt := int64(flooredFloat)
return newInt, nil
}
func CeilFloat64ToInt64(input float64)(int64, error){
ceiledFloat := math.Ceil(input)
if (ceiledFloat < -9223372036854775808 || ceiledFloat > 9223372036854775807){
return 0, errors.New("CeilFloat64ToInt64 called with float out of range.")
}
ceiledInt := int64(ceiledFloat)
return ceiledInt, nil
}
func CeilFloat64ToInt(input float64)(int, error){
ceiledFloat := math.Ceil(input)
if (ceiledFloat < -2147483648 || ceiledFloat > 2147483647){
return 0, errors.New("CeilFloat64ToInt called with float out of range.")
}
ceiledInt := int(ceiledFloat)
return ceiledInt, nil
}
func ConvertFloat32ToString(input float32) string{
result := strconv.FormatFloat(float64(input), 'f', 5, 32)
return result
}
func ConvertFloat64ToString(input float64) string{
result := strconv.FormatFloat(input, 'f', 5, 64)
return result
}
func ConvertFloat64ToStringRounded(input float64, outputPrecision int)string{
if (outputPrecision < 0){
outputPrecision = 0
}
converted := strconv.FormatFloat(input, 'f', outputPrecision, 64)
if (outputPrecision == 0){
return converted
}
// If all numbers after the decimal are zero, we will remove them
// Example: "5.000" -> "5"
beforeDecimal, afterDecimal, decimalFound := strings.Cut(converted, ".")
if (decimalFound == false){
return converted
}
for _, number := range afterDecimal{
if (number != '0'){
return converted
}
}
return beforeDecimal
}
func ConvertStringToFloat64(input string)(float64, error){
num, err := strconv.ParseFloat(input, 64)
if (err != nil){
return 0, errors.New("ConvertStringToFloat64 called with invalid input: " + input)
}
return num, nil
}
func ConvertInt64ToInt(input int64)(int, error){
if (input < -2147483648 || input > 2147483647){
return 0, errors.New("ConvertInt64ToInt called with input out of range.")
}
result := int(input)
return result, nil
}
func ConvertByteToString(input byte) string{
result := ConvertIntToString(int(input))
return result
}
func ConvertStringToByte(input string)(byte, error){
resultInt, err := strconv.Atoi(input)
if (err != nil) {
return 0, errors.New("ConvertStringToByte called with invalid input: " + input)
}
if (resultInt < 0 || resultInt > 255){
return 0, errors.New("ConvertStringToByte called with invalid input: " + input)
}
result := byte(resultInt)
return result, nil
}
func ConvertNetworkTypeStringToByte(input string)(byte, error){
if (input == "1"){
return 1, nil
}
if (input == "2"){
return 2, nil
}
return 0, errors.New("ConvertNetworkTypeStringToByte called with invalid input: " + input)
}
func VerifyNetworkType(input byte)bool{
if (input != 1 && input != 2){
return false
}
return true
}
func ConvertIntToString(input int) string{
str := strconv.Itoa(input)
return str
}
func ConvertInt64ToString(input int64)string{
str := strconv.FormatInt(input, 10)
return str
}
func ConvertStringToInt(input string)(int, error){
result, err := strconv.Atoi(input)
if (err != nil) {
return 0, errors.New("ConvertStringToInt called with invalid input: " + input)
}
return result, nil
}
func ConvertStringToInt64(input string) (int64, error) {
result, err := strconv.ParseInt(input, 10, 64)
if (err != nil){
return 0, errors.New("ConvertStringToInt64 called with invalid input: " + input)
}
return result, nil
}
func ConvertCreationTimeStringToInt64(input string)(int64, error){
inputInt64, err := ConvertStringToInt64(input)
if (err != nil) {
return 0, errors.New("ConvertCreationTimeStringToInt64 called with invalid input: " + input)
}
isValid := VerifyCreationTime(inputInt64)
if (isValid == false){
return 0, errors.New("ConvertCreationTimeStringToInt64 called with invalid input. Too early: " + input)
}
return inputInt64, nil
}
func VerifyCreationTime(input int64)bool{
if (input < 1717000000){
return false
}
//TODO: Add upper limit
return true
}
func ConvertIntToStringWithCommas(num int)string{
numAsString := ConvertIntToString(num)
if (num <= 999){
return numAsString
}
resultWithCommas := ""
finalIndex := len(numAsString)-1
counter := 0
for i := finalIndex; i >= 0; i--{
currentCharacter := string(numAsString[i])
if (counter == 3){
resultWithCommas = currentCharacter + "," + resultWithCommas
counter = 1
continue
}
resultWithCommas = currentCharacter + resultWithCommas
counter += 1
}
return resultWithCommas
}
//Outputs:
// -string: Content Type ("Profile"/"Attribute"/"Message"/"Review"/"Report"/"Parameters")
// -error
func GetContentTypeFromContentHash(contentHash []byte)(string, error){
hashLength := len(contentHash)
switch hashLength{
case 26:{
return "Message", nil
}
case 27:{
metadataByte := contentHash[26]
if (metadataByte >= 1 && metadataByte <= 6){
return "Attribute", nil
}
}
case 28:{
metadataByte := contentHash[27]
if (metadataByte >= 1 && metadataByte <= 6){
return "Profile", nil
}
}
case 29:{
metadataByte := contentHash[28]
if (metadataByte >= 1 && metadataByte <= 4){
return "Review", nil
}
}
case 30:{
metadataByte := contentHash[29]
if (metadataByte >= 1 && metadataByte <= 4){
return "Report", nil
}
}
case 31:{
return "Parameters", nil
}
}
contentHashHex := encoding.EncodeBytesToHexString(contentHash)
return "", errors.New("GetContentTypeFromContentHash called with invalid contentHash: " + contentHashHex)
}
//Outputs:
// -[]byte: Reported hash bytes
// -string: Reported hash type (Identity, Profile, Message, or Attribute)
// -error
func ReadReportedHashString(reportedHash string)([]byte, string, error){
reportedHashBytes, reportedHashType, err := ReadReviewedHashString(reportedHash)
if (err != nil){
return nil, "", errors.New("ReadReportedHashString called with invalid reportedHash: " + err.Error())
}
return reportedHashBytes, reportedHashType, nil
}
//Outputs:
// -[]byte: Reviewed hash bytes
// -string: Reviewed hash type (Identity, Profile, Message, or Attribute)
// -error
func ReadReviewedHashString(reviewedHash string)([]byte, string, error){
if (len(reviewedHash) == 27){
// Must be identity hash
identityHashBytes, _, err := identity.ReadIdentityHashString(reviewedHash)
if (err != nil) {
return nil, "", errors.New("ReadReviewedHashString called with invalid reviewedHash: " + reviewedHash)
}
return identityHashBytes[:], "Identity", nil
}
// All other reviewedHash types are encoded in Hex
reviewedHashBytes, err := encoding.DecodeHexStringToBytes(reviewedHash)
if (err != nil) {
return nil, "", errors.New("ReadReviewedHashString called with invalid reviewedHash: " + reviewedHash)
}
reviewedType, err := GetReviewedTypeFromReviewedHash(reviewedHashBytes)
if (err != nil){
return nil, "", errors.New("ReadReviewedHashString called with invalid reviewedHash: " + reviewedHash)
}
return reviewedHashBytes, reviewedType, nil
}
func EncodeReportedHashBytesToString(reportedHash []byte)(string, error){
result, err := EncodeReviewedHashBytesToString(reportedHash)
return result, err
}
func EncodeReviewedHashBytesToString(reviewedHash []byte)(string, error){
reviewedType, err := GetReviewedTypeFromReviewedHash(reviewedHash)
if (err != nil){
return "", errors.New("EncodeReviewedHashToString called with invalid reviewedHashBytes: " + err.Error())
}
if (reviewedType == "Identity"){
if (len(reviewedHash) != 16){
return "", errors.New("GetReviewedTypeFromReviewedHash returning Identity reviewedType when bytes length is not 16.")
}
reviewedHashArray := [16]byte(reviewedHash)
identityHash, _, err := identity.EncodeIdentityHashBytesToString(reviewedHashArray)
if (err != nil) { return "", err }
return identityHash, nil
}
reviewedHashString := encoding.EncodeBytesToHexString(reviewedHash)
return reviewedHashString, nil
}
func GetReportedTypeFromReportedHash(reportedHash []byte)(string, error){
reportedType, err := GetReviewedTypeFromReviewedHash(reportedHash)
if (err != nil) { return "", err }
return reportedType, nil
}
func GetReviewedTypeFromReviewedHash(reviewedHash []byte)(string, error){
hashLength := len(reviewedHash)
if (hashLength == 16){
metadataByte := reviewedHash[15]
if (metadataByte >= 1 && metadataByte <= 3){
return "Identity", nil
}
} else if (hashLength == 26){
return "Message", nil
} else if (hashLength == 27){
metadataByte := reviewedHash[26]
if (metadataByte >= 1 && metadataByte <= 6){
return "Attribute", nil
}
} else if (hashLength == 28){
metadataByte := reviewedHash[27]
// We must make sure profile is not disabled
// Disabled profiles cannot be reviewed
if (metadataByte == 2 || metadataByte == 4 || metadataByte == 6){
return "Profile", nil
}
}
reviewedHashHex := encoding.EncodeBytesToHexString(reviewedHash)
return "", errors.New("GetReviewedTypeFromReviewedHashBytes called with invalid reviewedHash: " + reviewedHashHex)
}
func GetNewRandomProfileHash(profileTypeProvided bool, profileType string, isDisabledProvided bool, isDisabled bool)([28]byte, error){
if (profileTypeProvided == true){
if (profileType != "Mate" && profileType != "Host" && profileType != "Moderator"){
return [28]byte{}, errors.New("GetNewRandomProfileHash called with invalid profileType: " + profileType)
}
}
getMetadataByte := func()(byte, error){
if (profileTypeProvided == false && isDisabledProvided == false){
randomByte, err := GetRandomByteWithinRange(1, 6)
if (err != nil) { return 0, err }
return randomByte, nil
}
if (profileTypeProvided == true && isDisabledProvided == false){
randomByte, err := GetRandomByteWithinRange(0, 1)
if (err != nil) { return 0, err }
if (profileType == "Mate"){
result := 1 + randomByte
return result, nil
}
if (profileType == "Host"){
result := 3 + randomByte
return result, nil
}
if (profileType == "Moderator"){
result := 5 + randomByte
return result, nil
}
}
if (profileTypeProvided == false && isDisabledProvided == true){
randomInt := GetRandomIntWithinRange(1, 3)
if (randomInt == 1){
// ProfileType == "Mate"
if (isDisabled == true){
return 1, nil
}
return 2, nil
}
if (randomInt == 2){
// ProfileType == "Host"
if (isDisabled == true){
return 3, nil
}
return 4, nil
}
// ProfileType == "Moderator"
// randomInt == 3
if (isDisabled == true){
return 5, nil
}
return 6, nil
}
//profileTypeProvided == true && isDisabledProvided == true
if (profileType == "Mate"){
if (isDisabled == true){
return 1, nil
}
return 2, nil
}
if (profileType == "Host"){
if (isDisabled == true){
return 3, nil
}
return 4, nil
}
// profileType == "Moderator"
if (isDisabled == true){
return 5, nil
}
return 6, nil
}
metadataByte, err := getMetadataByte()
if (err != nil){ return [28]byte{}, err }
var profileHash [28]byte
_, err = cryptoRand.Read(profileHash[:])
if (err != nil) { return [28]byte{}, err }
profileHash[27] = metadataByte
return profileHash, nil
}
func GetNewRandomAttributeHash(profileTypeProvided bool, profileType string, isCanonicalProvided bool, isCanonical bool)([27]byte, error){
if (profileTypeProvided == true){
if (profileType != "Mate" && profileType != "Host" && profileType != "Moderator"){
return [27]byte{}, errors.New("GetNewRandomAttributeHash called with invalid profileType: " + profileType)
}
}
getMetadataByte := func()(byte, error){
if (profileTypeProvided == false && isCanonicalProvided == false){
randomByte, err := GetRandomByteWithinRange(1, 6)
if (err != nil) { return 0, err }
return randomByte, nil
}
if (profileTypeProvided == true && isCanonicalProvided == false){
randomByte, err := GetRandomByteWithinRange(0, 1)
if (err != nil) { return 0, err }
if (profileType == "Mate"){
result := 1 + randomByte
return result, nil
}
if (profileType == "Host"){
result := 3 + randomByte
return result, nil
}
if (profileType == "Moderator"){
result := 5 + randomByte
return result, nil
}
}
if (profileTypeProvided == false && isCanonicalProvided == true){
randomInt := GetRandomIntWithinRange(1, 3)
if (randomInt == 1){
// ProfileType == "Mate"
if (isCanonical == true){
return 1, nil
}
return 2, nil
}
if (randomInt == 2){
// ProfileType == "Host"
if (isCanonical == true){
return 3, nil
}
return 4, nil
}
// ProfileType == "Moderator"
// randomInt == 3
if (isCanonical == true){
return 5, nil
}
return 6, nil
}
//profileTypeProvided == true && isCanonicalProvided == true
if (profileType == "Mate"){
if (isCanonical == true){
return 1, nil
}
return 2, nil
}
if (profileType == "Host"){
if (isCanonical == true){
return 3, nil
}
return 4, nil
}
// profileType == "Moderator"
if (isCanonical == true){
return 5, nil
}
return 6, nil
}
metadataByte, err := getMetadataByte()
if (err != nil) { return [27]byte{}, err }
var attributeHash [27]byte
_, err = cryptoRand.Read(attributeHash[:])
if (err != nil) { return [27]byte{}, err }
attributeHash[26] = metadataByte
return attributeHash, nil
}
func GetNewRandomMessageHash()([26]byte, error){
var messageHash [26]byte
_, err := cryptoRand.Read(messageHash[:])
if (err != nil) { return [26]byte{}, err }
return messageHash, nil
}
func GetNewRandomReviewHash(reviewTypeProvided bool, reviewType string)([29]byte, error){
getMetadataByte := func()(byte, error){
if (reviewTypeProvided == false){
randomByte, err := GetRandomByteWithinRange(1, 4)
if (err != nil) { return 0, err }
return randomByte, nil
}
if (reviewType == "Identity"){
return 1, nil
}
if (reviewType == "Profile"){
return 2, nil
}
if (reviewType == "Attribute"){
return 3, nil
}
if (reviewType == "Message"){
return 4, nil
}
return 0, errors.New("GetNewRandomReviewHash called with invalid reviewType: " + reviewType)
}
metadataByte, err := getMetadataByte()
if (err != nil){ return [29]byte{}, err }
var reviewHash [29]byte
_, err = cryptoRand.Read(reviewHash[:])
if (err != nil) { return [29]byte{}, err }
reviewHash[28] = metadataByte
return reviewHash, nil
}
func GetNewRandomReportHash(reportTypeProvided bool, reportType string)([30]byte, error){
getMetadataByte := func()(byte, error){
if (reportTypeProvided == false){
randomByte, err := GetRandomByteWithinRange(1, 4)
if (err != nil) { return 0, err }
return randomByte, nil
}
if (reportType == "Identity"){
return 1, nil
}
if (reportType == "Profile"){
return 2, nil
}
if (reportType == "Attribute"){
return 3, nil
}
if (reportType == "Message"){
return 4, nil
}
return 0, errors.New("GetNewRandomReportHash called with invalid reportType: " + reportType)
}
metadataByte, err := getMetadataByte()
if (err != nil){ return [30]byte{}, err }
var reportHash [30]byte
_, err = cryptoRand.Read(reportHash[:])
if (err != nil) { return [30]byte{}, err }
reportHash[29] = metadataByte
return reportHash, nil
}
func GetNewRandomParametersHash()([31]byte, error){
var parametersHash [31]byte
_, err := cryptoRand.Read(parametersHash[:])
if (err != nil) { return [31]byte{}, err }
return parametersHash, nil
}
func GetNewRandomBytes(lengthInBytes int)([]byte, error){
if (lengthInBytes == 0){
return nil, errors.New("GetNewRandomBytes called with 0 lengthInBytes.")
}
randomBytes := make([]byte, lengthInBytes)
_, err := cryptoRand.Read(randomBytes[:])
if (err != nil) { return nil, err }
return randomBytes, nil
}
func GetNewRandomDeviceIdentifier()([11]byte, error){
var newArray [11]byte
_, err := cryptoRand.Read(newArray[:])
if (err != nil) { return [11]byte{}, err }
return newArray, nil
}
func GetNewRandom16ByteArray()([16]byte, error){
var newArray [16]byte
_, err := cryptoRand.Read(newArray[:])
if (err != nil) { return [16]byte{}, err }
return newArray, nil
}
func GetNewRandom32ByteArray()([32]byte, error){
var newArray [32]byte
_, err := cryptoRand.Read(newArray[:])
if (err != nil) { return [32]byte{}, err }
return newArray, nil
}
func GetNewRandom24ByteArray()([24]byte, error){
var newArray [24]byte
_, err := cryptoRand.Read(newArray[:])
if (err != nil) { return [24]byte{}, err }
return newArray, nil
}
func GetNewRandomHexString(lengthInBytes int)(string, error){
if (lengthInBytes == 0){
return "", errors.New("GetNewRandomHexString called with 0 lengthInBytes.")
}
randomBytes := make([]byte, lengthInBytes)
_, err := cryptoRand.Read(randomBytes[:])
if (err != nil) { return "", err }
randomString := encoding.EncodeBytesToHexString(randomBytes)
return randomString, nil
}
func VerifyHexString(expectedLengthInBytes uint32, stringToVerify string)bool{
inputStringBytes, err := encoding.DecodeHexStringToBytes(stringToVerify)
if (err != nil) {
return false
}
if (len(inputStringBytes) != int(expectedLengthInBytes)){
return false
}
return true
}
func ConvertBoolToYesOrNoString(input bool)string{
if (input == true){
return "Yes"
}
return "No"
}
func ConvertYesOrNoStringToBool(input string)(bool, error){
if (input == "Yes"){
return true, nil
}
if (input == "No"){
return false, nil
}
return false, errors.New("ConvertYesOrNoStringToBool called with invalid yes/no: " + input)
}
func DeleteIndexFromStringList(inputList []string, indexToDelete int)([]string, error){
if (len(inputList) == 0){
return nil, errors.New("DeleteIndexFromStringList called with empty list.")
}
if (indexToDelete < 0){
return nil, errors.New("DeleteIndexFromStringList called with negative indexToDelete.")
}
finalIndex := len(inputList) - 1
if (indexToDelete > finalIndex){
return nil, errors.New("DeleteIndexFromStringList called with indexToDelete which is too large.")
}
newList := slices.Delete(inputList, indexToDelete, indexToDelete+1)
return newList, nil
}
//Outputs:
// -[]string: New list
// -bool: Deleted any items
func DeleteAllMatchingItemsFromList[E comparable](inputList []E, itemToDelete E)([]E, bool){
listCopy := slices.Clone(inputList)
deletionFunction := func(input E)bool{
if (input == itemToDelete){
return true
}
return false
}
newList := slices.DeleteFunc(listCopy, deletionFunction)
if (len(newList) == len(inputList)){
return newList, false
}
return newList, true
}
func GetSharedItemsOfTwoStringLists(listA []string, listB []string)[]string{
sharedItemsList := make([]string, 0)
for _, element := range listA{
containsItem := slices.Contains(listB, element)
if (containsItem == true){
sharedItemsList = append(sharedItemsList, element)
}
}
return sharedItemsList
}
// This function will compare two lists. It does not care about item order.
func CheckIfTwoListsContainIdenticalItems[E comparable](listA []E, listB []E)bool{
if (len(listA) != len(listB)){
return false
}
itemCountMapA := make(map[E]int)
for _, item := range listA{
itemCountMapA[item] += 1
}
itemCountMapB := make(map[E]int)
for _, item := range listB{
itemCountMapB[item] += 1
}
areEqual := maps.Equal(itemCountMapA, itemCountMapB)
if (areEqual == false){
return false
}
return true
}
// This function will split a list into sublists
func SplitListIntoSublists[E any](inputList []E, maximumItemsPerSublist int)([][]E, error){
if (maximumItemsPerSublist <= 0){
return nil, errors.New("maximumItemsPerSublist is <= 0")
}
lengthOfInputList := len(inputList)
if (lengthOfInputList <= maximumItemsPerSublist){
result := [][]E{inputList}
return result, nil
}
numberOfSublists := float64(lengthOfInputList)/float64(maximumItemsPerSublist)
numberOfSublistsCeil := math.Ceil(numberOfSublists)
itemsPerSublist := float64(lengthOfInputList)/numberOfSublistsCeil
itemsPerSublistCeiled := math.Ceil(itemsPerSublist)
if (itemsPerSublistCeiled > 2147483647){
return nil, errors.New("Items per sublist is out of range.")
}
sizeOfEachSublist := int(itemsPerSublistCeiled)
maximumIndex := lengthOfInputList-1
listOfSublists := make([][]E, 0)
currentSublist := make([]E, 0)
for index, element := range inputList{
currentSublist = append(currentSublist, element)
currentSublistLength := len(currentSublist)
if (currentSublistLength == sizeOfEachSublist || index == maximumIndex){
listOfSublists = append(listOfSublists, currentSublist)
currentSublist = make([]E, 0)
}
}
return listOfSublists, nil
}
func AddItemToStringListAndAvoidDuplicate(inputList []string, newItem string)[]string{
for _, element := range inputList{
if (element == newItem){
return inputList
}
}
inputList = append(inputList, newItem)
return inputList
}
// Will combine two lists, and avoid any duplicate entries
func CombineTwoListsAndAvoidDuplicates[E comparable](listA []E, listB []E)[]E{
combinedListsMap := make(map[E]struct{})
for _, element := range listA{
combinedListsMap[element] = struct{}{}
}
for _, element := range listB{
combinedListsMap[element] = struct{}{}
}
combinedList := GetListOfMapKeys(combinedListsMap)
return combinedList
}
func RemoveDuplicatesFromStringList(inputList []string)[]string{
if (len(inputList) <= 1){
listCopy := slices.Clone(inputList)
return listCopy
}
listAsMap := make(map[string]struct{})
for _, element := range inputList{
listAsMap[element] = struct{}{}
}
newList := GetListOfMapKeys(listAsMap)
return newList
}
// Outputs:
// -bool: Duplicate exists
// -E: The first duplicate we found
func CheckIfListContainsDuplicates[E comparable](inputList []E)(bool, E){
if (len(inputList) <= 1){
var emptyItem E
return false, emptyItem
}
listMap := make(map[E]struct{})
for index, element := range inputList{
if (index != 0){
_, exists := listMap[element]
if (exists == true){
return true, element
}
}
listMap[element] = struct{}{}
}
var emptyItem E
return false, emptyItem
}
// Uses weak randomness
func GetRandomItemFromList[E any](inputList []E)(E, error){
lengthOfList := len(inputList)
if (lengthOfList == 0){
var emptyItem E
return emptyItem, errors.New("GetRandomItemFromList called with empty list.")
}
if (lengthOfList == 1){
result := inputList[0]
return result, nil
}
randomIndex := mathRand.IntN(lengthOfList)
result := inputList[randomIndex]
return result, nil
}
// This function will return a list containing a map's keys
func GetListOfMapKeys[M map[K]V, K comparable, V any](inputMap M)[]K{
newList := make([]K, 0, len(inputMap))
for key, _ := range inputMap{
newList = append(newList, key)
}
return newList
}
// This function will return a list containing a map's values
func GetListOfMapValues[M map[K]V, K comparable, V any](inputMap M)[]V{
newList := make([]V, 0, len(inputMap))
for _, value := range inputMap{
newList = append(newList, value)
}
return newList
}
// This function will return a deep copy of a list of string->string maps
func DeepCopyStringToStringMapList(inputMapList []map[string]string)[]map[string]string{
mapListCopy := make([]map[string]string, 0, len(inputMapList))
for _, element := range inputMapList{
elementCopy := maps.Clone(element)
mapListCopy = append(mapListCopy, elementCopy)
}
return mapListCopy
}
//Uses weak randomness
func RandomizeListOrder[E any](inputList []E){
mathRand.Shuffle(len(inputList), func(i int, j int){
inputList[i], inputList[j] = inputList[j], inputList[i]
})
}
func SortIdentityHashListToUnicodeOrder(inputList [][16]byte)error{
// Map Structure: Identity hash -> Identity hash string
identityHashStringsMap := make(map[[16]byte]string)
for _, identityHash := range inputList{
identityHashString, _, err := identity.EncodeIdentityHashBytesToString(identityHash)
if (err != nil){
identityHashHex := encoding.EncodeBytesToHexString(identityHash[:])
return errors.New("SortIdentityHashListToUnicodeOrder called with invalid identityHash: " + identityHashHex)
}
identityHashStringsMap[identityHash] = identityHashString
}
compareFunction := func(identityHash1 [16]byte, identityHash2 [16]byte)int{
if (identityHash1 == identityHash2){
return 0
}
identityHash1String, exists := identityHashStringsMap[identityHash1]
if (exists == false){
panic("identityHash1 is missing from identityHashStringsMap.")
}
identityHash2String, exists := identityHashStringsMap[identityHash2]
if (exists == false){
panic("identityHash2 is missing from identityHashStringsMap.")
}
if (identityHash1String < identityHash2String){
return -1
}
return 1
}
slices.SortFunc(inputList, compareFunction)
return nil
}
func CopyAndSortStringListToUnicodeOrder(inputList []string)[]string{
listCopy := slices.Clone(inputList)
slices.Sort(listCopy)
return listCopy
}
func SortStringListToUnicodeOrder(inputList []string){
slices.Sort(inputList)
}
func GetRandomByteWithinRange(minimum byte, maximum byte)(byte, error){
if (maximum < minimum){
return 0, errors.New("GetRandomByteWithinRange called with maximum < minimum")
}
randomInt := GetRandomIntWithinRange(int(minimum), int(maximum))
if (randomInt < 0 || randomInt > 255){
return 0, errors.New("GetRandomIntWithinRange returning out of bounds result.")
}
randomByte := byte(randomInt)
return randomByte, nil
}
// Will return random int between two input numbers
// Returns weak randomness
func GetRandomIntWithinRange(numA int, numB int)int{
if (numA == numB){
return numA
}
lesserValue := min(numA, numB)
greaterValue := max(numA, numB)
differenceBetweenValues := greaterValue - lesserValue
randomValue := mathRand.IntN(differenceBetweenValues + 1)
resultValue := lesserValue + randomValue
return resultValue
}
// This function will return a bool with the provided probabilityOfTrue
// For example, if the probabilityOfTrue is 0.6, then 60% of the time, this function will return true
//
func GetRandomBoolWithProbability(probabilityOfTrue float64)(bool, error){
if (probabilityOfTrue < 0 || probabilityOfTrue > 1){
probabilityOfTrueString := ConvertFloat64ToString(probabilityOfTrue)
return false, errors.New("GetRandomBoolWithProbability called with invalid probabilityOfTrue: " + probabilityOfTrueString)
}
if (probabilityOfTrue == 0){
return false, nil
}
if (probabilityOfTrue == 1){
return true, nil
}
// This function returns a random float between 0-1
randomFloat := mathRand.Float64()
if (randomFloat <= probabilityOfTrue){
return true, nil
}
return false, nil
}
// Will return random int64 between two input numbers
// Returns weak randomness
func GetRandomInt64WithinRange(numA int64, numB int64)int64{
if (numA == numB){
return numA
}
lesserValue := min(numA, numB)
greaterValue := max(numA, numB)
differenceBetweenValues := greaterValue - lesserValue
randomValue := mathRand.Int64N(differenceBetweenValues + 1)
resultValue := lesserValue + randomValue
return resultValue
}
func GetRandomBool()bool{
randomNumber := mathRand.IntN(2)
if (randomNumber == 1){
return true
}
return false
}
// This will replace newline characters with the "⁋" character
// It also counts tabs as being 5 characters long
//Outputs:
// -string: Trimmed/flattened string (or original string if no changes were made)
// -bool: Any trimming/flattening occurred
// -error
func TrimAndFlattenString(inputString string, maximumCharacters int)(string, bool, error){
if (maximumCharacters < 1) {
return "", false, errors.New("TrimAndFlattenString called with maximumCharacters which is less than 1.")
}
// Note that some characters are multiple runes in length.
// This is not a perfect estimate, but it will only overestimate the length
// All english characters are 1 byte long, so they are also 1 rune long
// We treat tabs as being 5 characters long
tabsReplacedString := strings.ReplaceAll(inputString, "\t", " ")
getFlattenedString := func()string{
containsNewlines := strings.Contains(tabsReplacedString, "\n")
if (containsNewlines == true){
// String must be flattened
newlinesReplacedString := strings.ReplaceAll(tabsReplacedString, "\n", "⁋")
return newlinesReplacedString
}
return tabsReplacedString
}
flattenedString := getFlattenedString()
numberOfCharacters := len([]rune(flattenedString))
if (numberOfCharacters <= maximumCharacters){
// No trimming is needed.
return flattenedString, false, nil
}
stringCharactersList := []rune(flattenedString)
outputString := string(stringCharactersList[:maximumCharacters])
outputStringWithEllipsis := outputString + "..."
return outputStringWithEllipsis, true, nil
}
func TranslateAndJoinStringListItems(inputList []string, delimiter string)string{
if (len(inputList) == 0){
return ""
}
// We use this to build the output
var outputBuilder strings.Builder
finalIndex := len(inputList) - 1
for index, element := range inputList{
elementTranslated := translation.TranslateTextFromEnglishToMyLanguage(element)
outputBuilder.WriteString(elementTranslated)
if (index != finalIndex){
outputBuilder.WriteString(delimiter)
}
}
result := outputBuilder.String()
return result
}
// Show remainder will include two units, such as "5 days 10 hours" or "3 months 2 weeks"
func ConvertUnixTimeDurationToUnitsTimeTranslated(unixDuration int64, showRemainder bool)(string, error){
// TODO: Add translation
getDurationWithRemainder := func()(string, int64){
if (unixDuration <= 0) {
return "0 seconds", 0
}
minuteUnix := int64(60)
if (unixDuration < minuteUnix){
numSeconds := unixDuration
durationSecondsString := ConvertInt64ToString(numSeconds)
if (durationSecondsString == "1"){
return durationSecondsString + " Second", 0
}
return durationSecondsString + " Seconds", 0
}
hourUnix := int64(3600)
if (unixDuration < hourUnix){
numMinutes := unixDuration/minuteUnix
remainderTime := unixDuration - (numMinutes * minuteUnix)
durationMinutesString := ConvertInt64ToString(numMinutes)
if (durationMinutesString == "1"){
return durationMinutesString + " Minute", remainderTime
}
return durationMinutesString + " Minutes", remainderTime
}
dayUnix := int64(86400)
if (unixDuration < dayUnix){
numHours := unixDuration/hourUnix
remainderTime := unixDuration - (numHours * hourUnix)
if (remainderTime < minuteUnix){
remainderTime = 0
}
durationHoursString := ConvertInt64ToString(numHours)
if (durationHoursString == "1"){
return durationHoursString + " Hour", remainderTime
}
return durationHoursString + " Hours", remainderTime
}
weekUnix := int64(604800)
if (unixDuration < weekUnix){
numDays := unixDuration/dayUnix
remainderTime := unixDuration - (numDays * dayUnix)
if (remainderTime < hourUnix){
remainderTime = 0
}
durationDaysString := ConvertInt64ToString(numDays)
if (durationDaysString == "1"){
return durationDaysString + " Day", remainderTime
}
return durationDaysString + " Days", remainderTime
}
monthUnix := int64(2629743)
if (unixDuration < monthUnix){
numWeeks := unixDuration/weekUnix
remainderTime := unixDuration - (numWeeks * weekUnix)
if (remainderTime < dayUnix){
remainderTime = 0
}
durationWeeksString := ConvertInt64ToString(numWeeks)
if (durationWeeksString == "1"){
return durationWeeksString + " Week", remainderTime
}
return durationWeeksString + " Weeks", remainderTime
}
yearUnix := int64(31556926)
if (unixDuration < yearUnix) {
numMonths := unixDuration/monthUnix
remainderTime := unixDuration - (numMonths * monthUnix)
if (remainderTime < dayUnix){
remainderTime = 0
}
durationMonthsString := ConvertInt64ToString(numMonths)
if (durationMonthsString == "1"){
return durationMonthsString + " Month", remainderTime
}
return durationMonthsString + " Months", remainderTime
}
numYears := unixDuration/yearUnix
remainderTime := unixDuration - (numYears * yearUnix)
if (remainderTime < dayUnix || numYears > 1){
remainderTime = 0
}
durationYearsString := ConvertInt64ToString(numYears)
if (durationYearsString == "1"){
return durationYearsString + " Year", remainderTime
}
return durationYearsString + " Years", remainderTime
}
timeTranslated, remainder := getDurationWithRemainder()
if (showRemainder == false){
return timeTranslated, nil
}
if (remainder == 0){
return timeTranslated, nil
}
remainderString, err := ConvertUnixTimeDurationToUnitsTimeTranslated(remainder, false)
if (err != nil) { return "", err }
andTranslated := translation.TranslateTextFromEnglishToMyLanguage("and")
// Example of resultWithRemainder: "1 week and 5 days"
resultWithRemainder := timeTranslated + " " + andTranslated + " " + remainderString
return resultWithRemainder, nil
}
func ConvertUnixTimeToTimeAgoTranslated(inputUnixTime int64, showRemainderTime bool)(string, error){
currentTime := time.Now().Unix()
if (inputUnixTime > currentTime){
return "", errors.New("ConvertUnixTimeToTimeAgoTranslated called with invalid date: Time is not in the past.")
}
durationUnix := currentTime - inputUnixTime
durationString, err := ConvertUnixTimeDurationToUnitsTimeTranslated(durationUnix, showRemainderTime)
if (err != nil) { return "", err }
agoTranslated := translation.TranslateTextFromEnglishToMyLanguage("Ago")
durationStringAgo := durationString + " " + agoTranslated
return durationStringAgo, nil
}
func ConvertUnixTimeToTimeFromNowTranslated(inputUnixTime int64, showRemainderTime bool)(string, error){
currentTime := time.Now().Unix()
getDurationUnixAndSuffix := func()(int64, string){
if (currentTime >= inputUnixTime){
// inputUnixTime is in the past
durationUnix := currentTime - inputUnixTime
return durationUnix, "Ago"
}
// inputUnixTime is in the future
durationUnix := inputUnixTime - currentTime
return durationUnix, "In The Future"
}
durationUnix, suffix := getDurationUnixAndSuffix()
durationString, err := ConvertUnixTimeDurationToUnitsTimeTranslated(durationUnix, showRemainderTime)
if (err != nil) { return "", err }
suffixTranslated := translation.TranslateTextFromEnglishToMyLanguage(suffix)
timeFromNowTranslated := durationString + " " + suffixTranslated
return timeFromNowTranslated, nil
}
// This will return time in the the following format: February 14, 2023 at 2:14.
// It returns 24 hour time
func ConvertUnixTimeToTranslatedTime(inputTime int64)string{
timeObject := time.Unix(inputTime, 0)
timeYear, timeMonth, timeDay := timeObject.Date()
timeYearString := ConvertIntToString(timeYear)
timeMonthString := translation.TranslateTextFromEnglishToMyLanguage(timeMonth.String())
timeDayString := ConvertIntToString(timeDay)
atTranslated := translation.TranslateTextFromEnglishToMyLanguage("at")
timeHour, timeMinute, _ := timeObject.Clock()
timeHourString := ConvertIntToString(timeHour)
getTimeMinuteStringFormatted := func()string{
timeMinuteString := ConvertIntToString(timeMinute)
// We have to convert times such as 11:0 -> 11:00
if (timeMinute < 10){
result := "0" + timeMinuteString
return result
}
return timeMinuteString
}
timeMinuteStringFormatted := getTimeMinuteStringFormatted()
timeString := timeMonthString + " " + timeDayString + ", " + timeYearString + " " + atTranslated + " " + timeHourString + ":" + timeMinuteStringFormatted
return timeString
}
func ConvertFloat64ToRoundedStringWithTranslatedUnits(inputFloat float64)(string, error){
if (inputFloat < 0){
return "", errors.New("ConvertFloat64ToRoundedStringWithTranslatedUnits called with negative input.")
}
if (inputFloat < 1000){
result := ConvertInt64ToString(int64(inputFloat))
return result, nil
}
getUnits := func()(float64, string){
millionFloat := float64(1000000)
if (inputFloat < millionFloat){
divided := inputFloat / 1000
return divided, "thousand"
}
billionFloat := float64(1000000000)
if (inputFloat < billionFloat){
divided := inputFloat / millionFloat
return divided, "million"
}
trillionFloat := float64(1000000000000)
if (inputFloat < trillionFloat){
divided := inputFloat / billionFloat
return divided, "billion"
}
quadrillionFloat := float64(1000000000000000)
if (inputFloat < quadrillionFloat){
divided := inputFloat / trillionFloat
return divided, "trillion"
}
quintillionFloat := float64(1000000000000000000)
if (inputFloat < quintillionFloat){
divided := inputFloat / quadrillionFloat
return divided, "quadrillion"
}
sextillionFloat := float64(1000000000000000000000)
if (inputFloat < sextillionFloat){
divided := inputFloat / quintillionFloat
return divided, "quintillion"
}
septillionFloat := float64(1000000000000000000000000)
if (inputFloat < septillionFloat){
divided := inputFloat / sextillionFloat
return divided, "sextillion"
}
divided := inputFloat / septillionFloat
return divided, "septillion"
}
dividedResult, unitsString := getUnits()
unitsTranslated := translation.TranslateTextFromEnglishToMyLanguage(unitsString)
resultString := ConvertFloat64ToStringRounded(dividedResult, 1)
hasSuffix := strings.HasSuffix(resultString, ".0")
if (hasSuffix == true){
resultTrimmed := strings.TrimSuffix(resultString, ".0")
resultWithUnits := resultTrimmed + " " + unitsTranslated
return resultWithUnits, nil
}
resultWithUnits := resultString + " " + unitsTranslated
return resultWithUnits, nil
}
// This function takes an int and the min and max range of that int
// It returns an int scaled between a new min and max
func ScaleIntProportionally(ascending bool, input int, inputMin int, inputMax int, newMin int, newMax int)(int, error){
if (inputMin == inputMax) {
return inputMin, nil
}
if (inputMin > inputMax) {
return 0, errors.New("ScaleIntProportionally error: InputMin is greater than inputMax")
}
if (input < inputMin) {
return 0, errors.New("ScaleIntProportionally error: Input is less than inputMin")
}
if (input > inputMax) {
return 0, errors.New("ScaleIntProportionally error: Input is greater than inputMax")
}
if (newMin == newMax) {
return newMin, nil
}
if (newMin > newMin){
return 0, errors.New("ScaleIntProportionally error: newMin is greater than newMin.")
}
inputRangePortionLength := input - inputMin
inputRangeDistance := inputMax - inputMin
inputRangePortion := float64(inputRangePortionLength)/float64(inputRangeDistance)
// This represents the portion of our output range that we want to travel across
getOutputRangePortion := func()float64{
if (ascending == true){
return inputRangePortion
}
outputRangePortion := 1 - inputRangePortion
return outputRangePortion
}
outputRangePortion := getOutputRangePortion()
outputRangeDistance := newMax - newMin
outputRangePortionLength := float64(outputRangeDistance) * outputRangePortion
resultFloat64 := float64(newMin) + outputRangePortionLength
result := int(math.Floor(resultFloat64))
return result, nil
}
// This function takes an float64 and the min and max range of that float64
// It returns a float64 scaled between a new min and max
func ScaleFloat64Proportionally(ascending bool, input float64, inputMin float64, inputMax float64, newMin float64, newMax float64)(float64, error){
if (inputMin == inputMax) {
return inputMin, nil
}
if (inputMin > inputMax) {
return 0, errors.New("ScaleFloat64Proportionally error: InputMin is greater than inputMax")
}
if (input < inputMin) {
return 0, errors.New("ScaleFloat64Proportionally error: Input is less than inputMin")
}
if (input > inputMax) {
return 0, errors.New("ScaleFloat64Proportionally error: Input is greater than inputMax")
}
if (newMin == newMax) {
return newMin, nil
}
if (newMin > newMin){
return 0, errors.New("ScaleFloat64Proportionally error: newMin is greater than newMin.")
}
inputRangePortionLength := input - inputMin
inputRangeDistance := inputMax - inputMin
inputRangePortion := inputRangePortionLength/inputRangeDistance
// This represents the portion of our output range that we want to travel across
getOutputRangePortion := func()float64{
if (ascending == true){
return inputRangePortion
}
outputRangePortion := 1 - inputRangePortion
return outputRangePortion
}
outputRangePortion := getOutputRangePortion()
outputRangeDistance := newMax - newMin
outputRangePortionLength := outputRangeDistance * outputRangePortion
result := newMin + outputRangePortionLength
return result, nil
}
func XORTwo32ByteArrays(array1 [32]byte, array2 [32]byte)[32]byte{
var newArray [32]byte
for i := 0; i < 32; i++ {
newArray[i] = array1[i] ^ array2[i]
}
return newArray
}
func CheckIfStringContainsTabsOrNewlines(inputString string)bool{
for _, element := range inputString{
if (element == '\r' || element == '\n' || element == '\t') {
return true
}
}
return false
}
func JoinTwo16ByteArrays(inputArray1 [16]byte, inputArray2 [16]byte)[32]byte{
arraysJoinedSlice := slices.Concat(inputArray1[:], inputArray2[:])
arraysJoined := [32]byte(arraysJoinedSlice)
return arraysJoined
}
func Split32ByteArrayInHalf(inputArray [32]byte)([16]byte, [16]byte){
piece1 := [16]byte(inputArray[:16])
piece2 := [16]byte(inputArray[16:])
return piece1, piece2
}
// This function takes a list of ints and a target value, and returns the int in the list that is the closest to that value
// If there is a tie, the function returns the earliest item in the list of the tied elements
func GetClosestIntInList(inputList []int, targetValue int)(int, error){
if (len(inputList) == 0){
return 0, errors.New("GetClosestIntInList called with empty inputList.")
}
closestValue := 0
closestValueDistance := float64(0)
for index, element := range inputList{
if (element == targetValue){
return element, nil
}
distance := math.Abs(float64(element - targetValue))
if (index == 0 || distance < closestValueDistance){
closestValue = element
closestValueDistance = distance
}
}
return closestValue, nil
}
func CountMatchingElementsInSlice[E comparable](inputSlice []E, inputElement E)int{
counter := 0
for _, element := range inputSlice{
if (element == inputElement){
counter += 1
}
}
return counter
}
func CheckIfAllItemsInSliceAreIdentical[E comparable](inputSlice []E)bool{
if (len(inputSlice) <= 1){
return true
}
initialElement := inputSlice[0]
for index, element := range inputSlice{
if (index == 0){
continue
}
if (element != initialElement){
return false
}
}
return true
}