I need to convert HSL color values to RGB, or to be more precise HSL-values to a System.Drawing.Color object with Powershell.
There are a few solutions in other prog.-languages out there (like LINK). But while it looks simple, I dont get it converted it into Powershell.
Function HSLtoRGB ($H,$S,$L) {
$H = [double]($H / 360)
$S = [double]($S / 100)
$L = [double]($L / 100)
if ($s -eq 0) {
$r = $g = $b = $l
}
else {
if ($l -lt 0.5){
$q = $l * (1 + $s)
}
else {
$q = $l + $s - $l * $s
}
$p = (2 * $L) - $q
$r = (Hue2rgb $p $q ($h + 1/3))
$g = (Hue2rgb $p $q $h )
$b = (Hue2rgb $p $q ($h - 1/3))
}
$r = [Math]::Round($r * 255)
$g = [Math]::Round($g * 255)
$b = [Math]::Round($b * 255)
return ($r,$g,$b)
}
function Hue2rgb ($p, $q, $t) {
if ($t -lt 0) { $t++ }
if ($t -gt 0) { $t-- }
if ($t -lt 1/6) { return ( $p + ($q + $p) * 6 * $t ) }
if ($t -lt 1/2) { return $q }
if ($t -lt 2/3) { return ($p + ($q - $p) * (2/3 - $t) * 6 ) }
return $p
}
HSLtoRGB 63 45 40 # result should be R 145 G 148 B 56
Let's start with the line you're having trouble with translating:
$q = l < 0.5 ? l * (1 + s) : l + s - l * s; #could not translate this line
This construct:
statement ? someValue : anotherValue;
is known as a ternary operation. It basically means:
if(statement){
someValue
} else {
anotherValue
}
So in PowerShell that becomes:
$q = if($l -lt 0.5){
$l * (1 + $s)
} else {
$l + $s - $l * $s
}
Your translation of the inline Hue2Rgb function has two typos that greatly change the calculation:
function Hue2rgb ($p, $q, $t) {
if ($t -lt 0) { $t++ }
if ($t -gt 0) { $t-- } # This condition should be ($t -gt 1)
if ($t -lt 1/6) { return ( $p + ($q + $p) * 6 * $t ) } # The innermost calculation should be ($q - $p) not ($q + $p)
if ($t -lt 1/2) { return $q }
if ($t -lt 2/3) { return ($p + ($q - $p) * (2/3 - $t) * 6 ) }
return $p
}
Regarding the input values, if you take a look at the comments in the original script:
* Assumes h, s, and l are contained in the set [0, 1] and
* returns r, g, and b in the set [0, 255].
So if you want to pass your input values as degrees (hue) and percentages (saturation + luminance), you'll have to handle a conversion to a relative value between 0 and 1:
Function HSLtoRGB ($H,$S,$L) {
$H = [double]($H / 360)
$S = [double]($S / 100)
$L = [double]($L / 100)
# rest of script
}
Lastly, you can use Color.FromArgb() to return an actual Color object:
$r = [Math]::Round($r * 255)
$g = [Math]::Round($g * 255)
$b = [Math]::Round($b * 255)
return [System.Drawing.Color]:FromArgb($r,$g,$b)
Related
I want to convert Roman Numbers into Decimal Numbers Like:
"MCMVII" = 1907;
"MMCDXLVII" = 2447;
"MMCMLXXXIV" = 2984;
"MXCVI" = 1096;
"MCMIV" = 1904;
"MMDII" = 2502;
"M" = 1000;
"MMDLXXIX" = 2579;
"MMMLXXXVIII" = 3088;
"MMDCCXCIX" = 2799;
"MMDVI" = 2506;
"MMMDCCLVII" = 3757;
"MMMCCLXXXIII" = 3283;
"MCDXL" = 1440;
"MMD" = 2500;
"DCLI" = 651;
Can someone help me with this? I am struggling from where to start??
There are good hints in the comments, but neither bluuf's helpful recipe nor the implementations pointed to Lee_Dailey amount to robust implementations.
A robust implementation that rules out syntactically invalid Roman numerals is surprisingly complex - find function ConvertFrom-RomanNumeral in the bottom section.
Applied to your sample values, you'll get the corresponding decimal numbers as shown in your question:
#(
"MCMVII"
"MMCDXLVII"
"MMCMLXXXIV"
"MXCVI"
"MCMIV"
"MMDII"
"M"
"MMDLXXIX"
"MMMLXXXVIII"
"MMDCCXCIX"
"MMDVI"
"MMMDCCLVII"
"MMMCCLXXXIII"
"MCDXL"
"MMD"
"DCLI"
) | ConvertFrom-RomanNumeral
A helpful online resource is the Roman Numerals Converter, which offers conversion both to and from Roman numerals.
ConvertFrom-RomanNumeral source code:
Any syntactically invalid Roman numeral results in a non-terminating error - do let us know if I missed any cases.
No limit is placed on the number of M digits (each representing 1000) in a row.
function ConvertFrom-RomanNumeral {
param(
[Parameter(Mandatory, ValueFromPipeline)]
[string] $RomanNumeral
)
begin {
function toValue {
param ([string] $RomanNumeral, [int] $i)
if ($i -ge $RomanNumeral.Length) { return $null }
$value = #{
[char] 'i' = 1
[char] 'v' = 5
[char] 'x' = 10
[char] 'l' = 50
[char] 'c' = 100
[char] 'd' = 500
[char] 'm' = 1000
}[[char]::ToLower($RomanNumeral[$i])]
if (-not $value) {
throw "Cannot convert '$RomanNumeral': not a Roman digit: '$($RomanNumeral[$i])'"
}
$value # output
}
}
process {
$num = 0; $maxDigitSoFar = 1001; $prevValue = 0
for ($i = 0; $i -lt $RomanNumeral.Length; ++$i) {
try {
$value = toValue $RomanNumeral $i
if ($isMultiDigitRun = $value -eq $prevValue) { # Check for too many digits in a row.
++$runCount
if ($value -ne 1000 -and $runCount -gt 3) { throw "Cannot convert '$RomanNumeral': no more than 3 digits other than M allowed in sequence." }
if ($value -in 5, 50, 500) { throw "Cannot convert '$RomanNumeral': V, L, and D cannot be repeated." }
} else {
$runCount = 1
}
if (($nextValue = toValue $RomanNumeral ($i + 1)) -gt $value) { # A subtraction, such as IV
$isValid = switch ($value) { # Verify that the next digit is valid; V, L, D cannot be used in subtractions.
1 { $nextValue -in 5, 10; break } # IV, IX
10 { $nextValue -in 50, 100; break } # XL, XC
100 { $nextValue -in 500, 1000; break } # CD, CM
}
if (-not $isValid) { throw "Cannot convert '$RomanNumeral': invalid subtraction sequence: '$($RomanNumeral[$i])$($RomanNumeral[$i+1])'" }
$isValid = $nextValue -le $maxDigitSoFar # Also check if the digit is too high.
++$i # Skip the next digit, given that we've just processed it.
$prevValue = 0 # Signal that the next digit is by definition not part of a run.
$maxDigitSoFar = $value # Signal that this digit must not be used again later, except in another subtraction.
$value = $nextValue - $value
}
else { # A stand-alone digit.
$isValid = $isMultiDigitRun -or $value -lt $maxDigitSoFar
$prevValue = $value
$maxDigitSoFar = $value
}
if (-not $isValid) { throw "Cannot convert '$RomanNumeral': digits not in expected order." }
$num += $value
}
catch {
Write-Error "$_"; return
}
}
$num # output
}
}
To complement the answer from #mklement0 and taking the complexity away by seeing each decimal number as a roman numeral pattern that represent the numbers 1 to 9. Note here that (as some other ancient number systems) roman numerals don't have/require zeros (M000 ➔ M equals 1000) as each roman number is unique to its own bound value:
function ConvertFrom-RomanNumeral {
param(
[Parameter(Mandatory, ValueFromPipeline)]
[string] $RomanNumeral
)
begin {
$1 = 'i', 'ii', 'iii', 'iv', 'v', 'vi', 'vii', 'viii', 'ix'
$10 = 'x', 'xx', 'xxx', 'xl', 'l', 'lx', 'lxx', 'lxxx', 'xc'
$100 = 'c', 'cc', 'ccc', 'cd', 'd', 'dc', 'dcc', 'dccc', 'cm'
$Pattern = "^(m*)($($100 -Join '|'))?($($10 -Join '|'))?($($1 -Join '|'))?$"
}
process {
if ($_ -Match $Pattern) {
$Value = if ($Matches[1]) { 1000 * $Matches[1].Length }
if ($Matches[2]) { $Value += 100 * $100.IndexOf($Matches[2].ToLower()) + 100 }
if ($Matches[3]) { $Value += 10 * $10.IndexOf($Matches[3].ToLower()) + 10 }
if ($Matches[4]) { $Value += $1.IndexOf($Matches[4].ToLower()) + 1 }
$Value
} else { throw "Cannot convert $_" }
}
}
I have seen this fun little command going around on social media, which outputs pi to 10000 decimal places. I'm wondering what is the Windows Powershell equivalent to this?
echo "scale=10000; 4*a(1)" | bc -l
I know I can use the Powershell Math library to do the basic math formula, but how to set the number of decimal places to 10000?
PS C:\Users\Me> [Math]::Atan(1)*4
3.14159265358979
The [Math] class only works with double so obviously you can't get more than 53 bits of precision. A few of its methods also support decimal so you'll get a little bit more digits but obviously no way near 10000 digits. There's no arbitrary precision floating point type in .NET so you're completely on your own to calculate the digits if you don't want to retrieve them from some storage space
There's BigInteger (PowerShell type accelerator name: [bigint]) though, which helps a lot since you won't need to do arbitrary precision math yourself. For example to calculate 10000 digits of π you can calculate 10000π and does the operations in integer or fixed point math
There are many algorithms to do that such as this one: How does this code calculate pi with high precision?
Fortunately Rosetta Code has a sample snippet to calculate π. After the below function has been declared just call Get-Pi 10000 to get the desired output
Function Get-Pi ( $Digits )
{
$Big = [bigint[]](0..10)
$ndigits = 0
$Output = ""
$q = $t = $k = $Big[1]
$r = $Big[0]
$l = $n = $Big[3]
# Calculate first digit
$nr = ( $Big[2] * $q + $r ) * $l
$nn = ( $q * ( $Big[7] * $k + $Big[2] ) + $r * $l ) / ( $t * $l )
$q *= $k
$t *= $l
$l += $Big[2]
$k = $k + $Big[1]
$n = $nn
$r = $nr
$Output += [string]$n + '.'
$ndigits++
$nr = $Big[10] * ( $r - $n * $t )
$n = ( ( $Big[10] * ( 3 * $q + $r ) ) / $t ) - 10 * $n
$q *= $Big[10]
$r = $nr
While ( $ndigits -lt $Digits )
{
While ( $ndigits % 100 -ne 0 -or -not $Output )
{
If ( $Big[4] * $q + $r - $t -lt $n * $t )
{
$Output += [string]$n
$ndigits++
$nr = $Big[10] * ( $r - $n * $t )
$n = ( ( $Big[10] * ( 3 * $q + $r ) ) / $t ) - 10 * $n
$q *= $Big[10]
$r = $nr
}
Else
{
$nr = ( $Big[2] * $q + $r ) * $l
$nn = ( $q * ( $Big[7] * $k + $Big[2] ) + $r * $l ) / ( $t * $l )
$q *= $k
$t *= $l
$l += $Big[2]
$k = $k + $Big[1]
$n = $nn
$r = $nr
}
}
$Output
$Output = ""
}
}
when i enter 55 i want the function to print the fibonacci sequence 55 times but the sequence stops at 55
function Get-Fibonacci ($n) {
$current = 0 ;
$previous = 1;
while ($current -lt $n) {
$current;
$current,$previous = ($current+$previous),$current
}
}
function Get-Fibonacci ($n) {
$current = 0 ;
$previous = 1;
for ($i=0; $i -lt $n; $i++) {
$current;
$current,$previous = ($current+$previous),$current
}
}
Here's an answer which doesn't rewrite using a for loop:
function Get-Fibonacci ($n) {
$current = 0 ;
$previous = 1;
$position=0
while ($position -lt $n) {
$position++
'{0} : {1}' -f $position,$current
$current,$previous = ($current+$previous),$current
}
}
How can we center text in PowerShell? WindowWidth doesn't exist apparently, so is there a way somehow to keep the text centered?
We want this output :
*
***
*****
*******
So I wrote
for ($i=1; $i -le 7; $i+=2)
{
write-host("*" * $i)
}
But we get
*
***
*****
*******
function Write-HostCenter { param($Message) Write-Host ("{0}{1}" -f (' ' * (([Math]::Max(0, $Host.UI.RawUI.BufferSize.Width / 2) - [Math]::Floor($Message.Length / 2)))), $Message) }
Write-HostCenter '*'
Write-HostCenter '***'
Write-HostCenter '*****'
I had a bit of fun and wrote some code based on this, that makes a box and center the text inside.
Im sure someone can make a cleaner version, but this do the job just fine :)
# ----------------------------------------------------------------------------------
#
# Script functions
#
# ----------------------------------------------------------------------------------
function MakeTopAndButtom
{
$string = "# "
for($i = 0; $i -lt $Host.UI.RawUI.BufferSize.Width - 4; $i++)
{
$string = $string + "-"
}
$string = $string + " #"
return $string
}
function MakeSpaces
{
$string = "# "
for($i = 0; $i -lt $Host.UI.RawUI.BufferSize.Width - 4; $i++)
{
$string = $string + " "
}
$string = $string + " #"
return $string
}
function CenterText
{
param($Message)
$string = "# "
for($i = 0; $i -lt (([Math]::Max(0, $Host.UI.RawUI.BufferSize.Width / 2) - [Math]::Max(0, $Message.Length / 2))) - 4; $i++)
{
$string = $string + " "
}
$string = $string + $Message
for($i = 0; $i -lt ($Host.UI.RawUI.BufferSize.Width - ((([Math]::Max(0, $Host.UI.RawUI.BufferSize.Width / 2) - [Math]::Max(0, $Message.Length / 2))) - 2 + $Message.Length)) - 2; $i++)
{
$string = $string + " "
}
$string = $string + " #"
return $string
}
function LinesOfCodeInCorrentFolder
{
return (gci -include *.ps1 -recurse | select-string .).Count
}
$MakeTopAndButtom = MakeTopAndButtom
$MakeSpaces = MakeSpaces
$lines = LinesOfCodeInCorrentFolder
# ----------------------------------------------------------------------------------
#
# Run
#
# ----------------------------------------------------------------------------------
$MakeTopAndButtom
$MakeSpaces
$MakeSpaces
$MakeSpaces
$MakeSpaces
CenterText "Lines of .ps1 code in this folder: $($lines)"
CenterText "Press any key to exit"
$MakeSpaces
$MakeSpaces
$MakeSpaces
$MakeSpaces
$MakeTopAndButtom
Read-Host
This gives an output like this:
# ---------------------------------------------------------------------------------------- #
# #
# #
# #
# #
# Lines of .ps1 code in this folder: 6524 #
# Press any key to exit #
# #
# #
# #
# #
# ---------------------------------------------------------------------------------------- #
You can calculate the spaces you need to add and then include them as follows:
$Width = 3
for ($i=1; $i -le 7; $i+=2)
{
Write-Host (' ' * ($width - [math]::floor($i / 2))) ('*' * $i)
}
I am working on something for learning purposes where I have tackled Collatz using recursion. If you see below I make use of #_ and $_ to keep the for alive.
#!/usr/bin/env perl
sub collatz {
my ($num, $count) = #_;
$count++;
if ($num == 1) {
return $count;
} elsif ($num % 2 == 0) {
return collatz($num/2, $count);
} else {
return collatz($num*3 + 1, $count);
}
}
my $max = 0;
my $saved = 0;
for (1..1000) {
my $length = collatz($_, 0);
print "Num: " . $_ . " Length: " . $length . "\n";
if ($length > $max) {
$max = $length;
$saved = $_;
}
}
print "The longest sequence starts with " . $saved . "\n";
I am trying to use iteration instead of recursion but I just can't think of how to tackle this. I am not after the code in the question, I just want some tips / hints on how to tackle this to get the same result.
I suspect I will need to use a while or an until field.
Any help would be appreciated, again I don't want the exact answer.
Update
Here is my second attempt, which is giving me an error of
Can't return outside a subroutine at answer2.pl line 38.
my $number = 0;
my $counter = 0;
while ($number != 1000) {
$counter++;
if ($number == 1) {
return $counter;
}
elsif ($number % 2 == 0) {
return ($number / 2, $counter);
}
else {
return ($number * 3 + 1, $counter);
}
$number++;
}
print "number" . $number . "counter" . $counter . "\n";
Basically you have tail recursion, which is nice and simple to eliminate.
Instead of collatz calling itself to generate the next step in the sequence, you simply change the variables in-place and loop back to the top.
In its crudest form this would be
sub collatz2 {
my ($num, $count) = #_;
NEXT:
$count++;
if ($num == 1) {
return $count;
}
elsif ($num % 2 == 0) {
$num = $num / 2;
}
else {
$num = $num * 3 + 1;
}
goto NEXT;
}
but it should be written much more nicely than that.
I ended up with this
sub collatz {
my ($num) = #_;
my $count = 1;
while ($num > 1) {
$num = $num % 2 ? $num * 3 + 1 : $num / 2;
++$count;
}
$count;
}
Consider adding the logic that returns when the condition is met in the while.
Spoiler:
my $iter = 0;
while($num != 1){ #do stuff; $iter++ }
Just use a for or while loop with the end condition that your number == 1.
Spoiler:
use strict;
use warnings;
my $max_num = 0;
my $max_steps = 0;
for my $num (1..1000) {
my $steps = 0;
for (my $i = $num; $i != 1; $steps++) {
$i = $i % 2 ? 3 * $i + 1 : $i / 2;
}
print "Num: " . $num . " Length: " . $steps . "\n";
if ($steps > $max_steps) {
$max_num = $num;
$max_steps = $steps;
}
}
print "The longest sequence starts with " . $max_num . "\n";