Skip to content
This repository has been archived by the owner on Oct 15, 2022. It is now read-only.

Resistor color codes! #133

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
174 changes: 174 additions & 0 deletions lib/DDG/Goodie/ResistorColors.pm
@@ -0,0 +1,174 @@
package DDG::Goodie::ResistorColors;
# ABSTRACT: Convert a resistor value into color codes.
# e.g. "4.7K ohms" -> "yellow purple black brown"

# Ideas for future improvements
# - reverse query (e.g. "yellow purple black brown" -> "4.7K ohms"
# - show 4 and 5 band markings
# - show surface mount resistor markings
# - detect if query contains tolerance (e.g. 10%) and use appropriate color

use DDG::Goodie;
use Math::Round;
use POSIX qw(abs floor log10 pow);

# \x{2126} is the unicode ohm symbol
triggers query_nowhitespace => qr/^(.*)(ohm|ohms|\x{2126})/i;

zci is_cached => 1;
zci answer_type => 'resistor_colors';

primary_example_queries '4.7k ohm';
secondary_example_queries '1\x{2126}';
description 'find resistor color bands';
name 'ResistorColors';
code_url 'https://github.com/duckduckgo/zeroclickinfo-goodies/blob/master/lib/DDG/Goodie/ResistorColors.pm';
category 'conversions';
topics 'science';

attribution twitter => 'joewalnes',
web => ['http://joewalnes.com', 'joewalnes.com'],
email => ['joe@walnes.com', 'Joe Walnes'];

# These hex codes came from
# http://en.wikipedia.org/wiki/Electronic_color_code
my %digits_to_colors = (
-2 => { hex => '#c0c0c0', label => '#000', name => 'silver', multiplier => '0.01' , tolerance => '10%' },
-1 => { hex => '#cfb53b', label => '#000', name => 'gold' , multiplier => '0.1' , tolerance => '5%' },
0 => { hex => '#000000', label => '#fff', name => 'black' , multiplier => '1' , tolerance => undef },
1 => { hex => '#964b00', label => '#fff', name => 'brown' , multiplier => '10' , tolerance => '1%' },
2 => { hex => '#ff0000', label => '#fff', name => 'red' , multiplier => '100' , tolerance => '2%' },
3 => { hex => '#ffa500', label => '#000', name => 'orange', multiplier => '1K' , tolerance => undef },
4 => { hex => '#ffff00', label => '#000', name => 'yellow', multiplier => '10K' , tolerance => undef },
5 => { hex => '#9acd32', label => '#000', name => 'green' , multiplier => '100K' , tolerance => '0.5%' },
6 => { hex => '#6495ed', label => '#000', name => 'blue' , multiplier => '1M' , tolerance => '0.25%' },
7 => { hex => '#ee82ee', label => '#000', name => 'purple', multiplier => '10M' , tolerance => '0.1%' },
8 => { hex => '#a0a0a0', label => '#000', name => 'gray' , multiplier => '100M' , tolerance => '0.05%' },
9 => { hex => '#ffffff', label => '#000', name => 'white' , multiplier => '1000M', tolerance => undef },
);

my $default_tolerance = -1; # 5% / gold

handle matches => sub {
my $input = shift;
my $value = parse_value($input);
if (defined $value && ($value == 0 || ($value <= 99900000000 && $value >= 1))) {
$value = round_to_significant_places($value, 2);
my $tolerance = $default_tolerance; # Currently always 5%.
my @digits = number_to_color_digits($value, $tolerance);
return render($value, \@digits);
}
return;
};

# Given ohm rating as string (e.g. 3.2m or 3M2), return
# integer value (e.g. 3200000).
sub parse_value {
my $input = shift;
if ($input =~ m/^(\d+)(\.(\d+))?([km])?$/i) {
# e.g. 123, 1.23, 1M, 1.23M
my $multiplier = 1;
if ($4) {
$multiplier = 1000 if $4 eq 'k' || $4 eq 'K';
$multiplier = 1000000 if $4 eq 'm' || $4 eq 'M';
}
return ("$1." . ($3 || 0)) * $multiplier;
} elsif ($input =~ m/^(\d+)([km])(\d+)$/i) {
# e.g. 12K3
my $multiplier = 1;
$multiplier = 1000 if $2 eq 'k' || $2 eq 'K';
$multiplier = 1000000 if $2 eq 'm' || $2 eq 'M';
return ("$1." . ($3 || 0)) * $multiplier;
} else {
return;
}
};

# Round a value to significant places.
# e.g. (123456789, 3) -> 123000000)
# (0.0045678, 3) -> 0.00457)
sub round_to_significant_places {
my $value = shift;
my $significant = shift;
if ($value == 0) {
return 0;
}
return nearest(pow(10, int(floor(log10(abs($value))) - ($significant - 1))), $value);
}

# Given ohm rating as integer, and tolerance digit (e.g. 470000, -1), return
# array of color digits (e.g. 4, 7, 0, 3, -1). See %digits_to_colors.
sub number_to_color_digits {
my ($value, $tolerance) = @_;
return (0, 0, 0, $tolerance) if $value == 0; # special case
my @value_digits = split(//, $value * 100);
return (
$value_digits[0] || 0,
$value_digits[1] || 0,
scalar(@value_digits) - 4,
$tolerance);
};

# Given a numeric value, format it like '3.2M' etc.
sub format_value {
my $value = shift;
if ($value >= 1000000) {
return ($value / 1000000) . 'M';
} elsif ($value >= 1000) {
return ($value / 1000) . 'K';
} else {
return $value;
}
};

# Given array of color digits (e.g. 3, 3, 1, 4),
# return text and html representation of colors.
sub render {
my ($value, $digits) = @_;
my $formatted_value = format_value($value);
my $ohms = $formatted_value eq '1' ? 'ohm' : 'ohms';
my $text = "$formatted_value\x{2126} ($ohms) resistor colors:";
my $html = "$formatted_value&#x2126; ($ohms) resistor colors:";

while (my ($index, $digit) = each @$digits) {
if (exists $digits_to_colors{$digit}) {
my $name = $digits_to_colors{$digit}{name};
my $hex = $digits_to_colors{$digit}{hex};
my $label = $digits_to_colors{$digit}{label};
my $style = "display:inline-block;background-color:$hex;color:$label;"
. "border:1px solid #c8c8c8;margin-top:-1px;padding:0px 4px;"
. "border-radius:4px;-webkit-border-radius:4px;-moz-border-radius:4px;";
my ($text_prefix, $html_prefix, $display_digit);
if ($index == scalar(@$digits) - 2) {
# multiplier digit
$text_prefix = "\x{00D7}";
$html_prefix = '&times;';
$display_digit = $digits_to_colors{$digit}{multiplier};
} elsif ($index == scalar(@$digits) - 1) {
# tolerance digit
$text_prefix = "\x{00B1}";
$html_prefix = '&plusmn;';
$display_digit = $digits_to_colors{$digit}{tolerance};
} else {
# numeric digits
$text_prefix = '';
$html_prefix = '';
$display_digit = $digit;
}
$text .= " $name ($text_prefix$display_digit)";
if ($index != scalar(@$digits - 1)) {
$text .= ','; # Comma delimit all but last
}
$html .= " <span style='$style'>$name ($html_prefix$display_digit)</span>";
} else {
return;
}
}
$html .= "<br/>"
. "<a href='http://resisto.rs/#$formatted_value' style='font-size:92.8%'>"
. "More at resisto.rs</a>";

return $text, html => $html;
};

1;
101 changes: 101 additions & 0 deletions t/ResistorColors.t
@@ -0,0 +1,101 @@
#!/usr/bin/env perl

use strict;
use warnings;
use Test::More;
use DDG::Test::Goodie;

zci answer_type => "resistor_colors";
zci is_cached => 1;

ddg_goodie_test(
[qw(
DDG::Goodie::ResistorColors
)],

# Check trigger kicks in.
"330 ohms" => test_zci("330\x{2126} (ohms) resistor colors: orange (3), orange (3), brown (\x{00D7}10), gold (\x{00B1}5%)", html => qr/./),
"330 ohm" => test_zci("330\x{2126} (ohms) resistor colors: orange (3), orange (3), brown (\x{00D7}10), gold (\x{00B1}5%)", html => qr/./),
"330 \x{2126}" => test_zci("330\x{2126} (ohms) resistor colors: orange (3), orange (3), brown (\x{00D7}10), gold (\x{00B1}5%)", html => qr/./),
"330ohms" => test_zci("330\x{2126} (ohms) resistor colors: orange (3), orange (3), brown (\x{00D7}10), gold (\x{00B1}5%)", html => qr/./),
"330ohm" => test_zci("330\x{2126} (ohms) resistor colors: orange (3), orange (3), brown (\x{00D7}10), gold (\x{00B1}5%)", html => qr/./),
"330\x{2126}" => test_zci("330\x{2126} (ohms) resistor colors: orange (3), orange (3), brown (\x{00D7}10), gold (\x{00B1}5%)", html => qr/./),

# Various multipliers
"472000 ohms" => test_zci("470K\x{2126} (ohms) resistor colors: yellow (4), purple (7), yellow (\x{00D7}10K), gold (\x{00B1}5%)", html => qr/./),
"400000 ohms" => test_zci("400K\x{2126} (ohms) resistor colors: yellow (4), black (0), yellow (\x{00D7}10K), gold (\x{00B1}5%)", html => qr/./),
"12300 ohms" => test_zci("12K\x{2126} (ohms) resistor colors: brown (1), red (2), orange (\x{00D7}1K), gold (\x{00B1}5%)", html => qr/./),

# Rounding
"1.2345 ohms" => test_zci("1.2\x{2126} (ohms) resistor colors: brown (1), red (2), gold (\x{00D7}0.1), gold (\x{00B1}5%)", html => qr/./),
"1.2555 ohms" => test_zci("1.3\x{2126} (ohms) resistor colors: brown (1), orange (3), gold (\x{00D7}0.1), gold (\x{00B1}5%)", html => qr/./),
"12.345 ohms" => test_zci("12\x{2126} (ohms) resistor colors: brown (1), red (2), black (\x{00D7}1), gold (\x{00B1}5%)", html => qr/./),
"12.555 ohms" => test_zci("13\x{2126} (ohms) resistor colors: brown (1), orange (3), black (\x{00D7}1), gold (\x{00B1}5%)", html => qr/./),
"123.45 ohms" => test_zci("120\x{2126} (ohms) resistor colors: brown (1), red (2), brown (\x{00D7}10), gold (\x{00B1}5%)", html => qr/./),
"125.55 ohms" => test_zci("130\x{2126} (ohms) resistor colors: brown (1), orange (3), brown (\x{00D7}10), gold (\x{00B1}5%)", html => qr/./),
"1234.5 ohms" => test_zci("1.2K\x{2126} (ohms) resistor colors: brown (1), red (2), red (\x{00D7}100), gold (\x{00B1}5%)", html => qr/./),
"1255.5 ohms" => test_zci("1.3K\x{2126} (ohms) resistor colors: brown (1), orange (3), red (\x{00D7}100), gold (\x{00B1}5%)", html => qr/./),
"12345 ohms" => test_zci("12K\x{2126} (ohms) resistor colors: brown (1), red (2), orange (\x{00D7}1K), gold (\x{00B1}5%)", html => qr/./),
"12555 ohms" => test_zci("13K\x{2126} (ohms) resistor colors: brown (1), orange (3), orange (\x{00D7}1K), gold (\x{00B1}5%)", html => qr/./),
"123450 ohms" => test_zci("120K\x{2126} (ohms) resistor colors: brown (1), red (2), yellow (\x{00D7}10K), gold (\x{00B1}5%)", html => qr/./),
"125550 ohms" => test_zci("130K\x{2126} (ohms) resistor colors: brown (1), orange (3), yellow (\x{00D7}10K), gold (\x{00B1}5%)", html => qr/./),
"1234500 ohms" => test_zci("1.2M\x{2126} (ohms) resistor colors: brown (1), red (2), green (\x{00D7}100K), gold (\x{00B1}5%)", html => qr/./),
"1255500 ohms" => test_zci("1.3M\x{2126} (ohms) resistor colors: brown (1), orange (3), green (\x{00D7}100K), gold (\x{00B1}5%)", html => qr/./),
"12345000 ohms" => test_zci("12M\x{2126} (ohms) resistor colors: brown (1), red (2), blue (\x{00D7}1M), gold (\x{00B1}5%)", html => qr/./),
"12555000 ohms" => test_zci("13M\x{2126} (ohms) resistor colors: brown (1), orange (3), blue (\x{00D7}1M), gold (\x{00B1}5%)", html => qr/./),
"123450000 ohms" => test_zci("120M\x{2126} (ohms) resistor colors: brown (1), red (2), purple (\x{00D7}10M), gold (\x{00B1}5%)", html => qr/./),
"125550000 ohms" => test_zci("130M\x{2126} (ohms) resistor colors: brown (1), orange (3), purple (\x{00D7}10M), gold (\x{00B1}5%)", html => qr/./),
"1234500000 ohms" => test_zci("1200M\x{2126} (ohms) resistor colors: brown (1), red (2), gray (\x{00D7}100M), gold (\x{00B1}5%)", html => qr/./),
"1255500000 ohms" => test_zci("1300M\x{2126} (ohms) resistor colors: brown (1), orange (3), gray (\x{00D7}100M), gold (\x{00B1}5%)", html => qr/./),

# kilo and mega multipliers
"27kohm" => test_zci("27K\x{2126} (ohms) resistor colors: red (2), purple (7), orange (\x{00D7}1K), gold (\x{00B1}5%)", html => qr/./),
"27Kohm" => test_zci("27K\x{2126} (ohms) resistor colors: red (2), purple (7), orange (\x{00D7}1K), gold (\x{00B1}5%)", html => qr/./),
"27 K ohm" => test_zci("27K\x{2126} (ohms) resistor colors: red (2), purple (7), orange (\x{00D7}1K), gold (\x{00B1}5%)", html => qr/./),
"4K2 ohm" => test_zci("4.2K\x{2126} (ohms) resistor colors: yellow (4), red (2), red (\x{00D7}100), gold (\x{00B1}5%)", html => qr/./),
"4.2K ohm" => test_zci("4.2K\x{2126} (ohms) resistor colors: yellow (4), red (2), red (\x{00D7}100), gold (\x{00B1}5%)", html => qr/./),

# Decimal points
"2.9ohm" => test_zci("2.9\x{2126} (ohms) resistor colors: red (2), white (9), gold (\x{00D7}0.1), gold (\x{00B1}5%)", html => qr/./),

# Negative multipliers
"1 ohm" => test_zci("1\x{2126} (ohm) resistor colors: brown (1), black (0), gold (\x{00D7}0.1), gold (\x{00B1}5%)", html => qr/./),
"29 ohms" => test_zci("29\x{2126} (ohms) resistor colors: red (2), white (9), black (\x{00D7}1), gold (\x{00B1}5%)", html => qr/./),

# Zero special case
"0 ohms" => test_zci("0\x{2126} (ohms) resistor colors: black (0), black (0), black (\x{00D7}1), gold (\x{00B1}5%)", html => qr/./),

# Range
"99000M ohms" => test_zci("99000M\x{2126} (ohms) resistor colors: white (9), white (9), white (\x{00D7}1000M), gold (\x{00B1}5%)", html => qr/./),
"99500M ohms" => undef,
"1.1 ohms" => test_zci("1.1\x{2126} (ohms) resistor colors: brown (1), brown (1), gold (\x{00D7}0.1), gold (\x{00B1}5%)", html => qr/./),
"1 ohms" => test_zci("1\x{2126} (ohm) resistor colors: brown (1), black (0), gold (\x{00D7}0.1), gold (\x{00B1}5%)", html => qr/./),
"0.9 ohms" => undef,
"-10 ohms" => undef,

# Don't trigger
"343.3.3.3 ohms" => undef,
"chicken ohms" => undef,
"what is ohms law" => undef,
"ohm ma darling" => undef,

# Check the HTML. Just once.
"1.5m ohm" => test_zci("1.5M\x{2126} (ohms) resistor colors: brown (1), green (5), green (\x{00D7}100K), gold (\x{00B1}5%)", html =>
"1.5M&#x2126; (ohms) resistor colors: "
. "<span style='display:inline-block;background-color:#964b00;"
. "color:#fff;border:1px solid #c8c8c8;margin-top:-1px;padding:0px 4px;border-radius:4px;"
. "-webkit-border-radius:4px;-moz-border-radius:4px;'>brown (1)</span> "
. "<span style='display:inline-block;background-color:#9acd32;"
. "color:#000;border:1px solid #c8c8c8;margin-top:-1px;padding:0px 4px;border-radius:4px;"
. "-webkit-border-radius:4px;-moz-border-radius:4px;'>green (5)</span> "
. "<span style='display:inline-block;background-color:#9acd32;"
. "color:#000;border:1px solid #c8c8c8;margin-top:-1px;padding:0px 4px;border-radius:4px;"
. "-webkit-border-radius:4px;-moz-border-radius:4px;'>green (&times;100K)</span> "
. "<span style='display:inline-block;background-color:#cfb53b;"
. "color:#000;border:1px solid #c8c8c8;margin-top:-1px;padding:0px 4px;border-radius:4px;"
. "-webkit-border-radius:4px;-moz-border-radius:4px;'>gold (&plusmn;5%)</span>"
. "<br/><a href='http://resisto.rs/#1.5M' style='font-size:92.8%'>More at resisto.rs</a>"),

);

done_testing;