pain-by-number
This commit is contained in:
parent
3001618707
commit
9d03ff01dc
|
@ -0,0 +1,27 @@
|
||||||
|
{
|
||||||
|
"authors": [
|
||||||
|
"angelikatyborska"
|
||||||
|
],
|
||||||
|
"contributors": [
|
||||||
|
"neenjaw",
|
||||||
|
"meatball133",
|
||||||
|
"vaeng",
|
||||||
|
"glennj"
|
||||||
|
],
|
||||||
|
"files": {
|
||||||
|
"solution": [
|
||||||
|
"lib/paint_by_number.ex"
|
||||||
|
],
|
||||||
|
"test": [
|
||||||
|
"test/paint_by_number_test.exs"
|
||||||
|
],
|
||||||
|
"exemplar": [
|
||||||
|
".meta/exemplar.ex"
|
||||||
|
],
|
||||||
|
"editor": [
|
||||||
|
"lib/math.ex"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"language_versions": ">=1.10",
|
||||||
|
"blurb": "Learn about bitstrings by creating binary files with pictures for a paint-by-number app."
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
{"track":"elixir","exercise":"paint-by-number","id":"5739ec34143e406c9f072ea94450aa45","url":"https://exercism.org/tracks/elixir/exercises/paint-by-number","handle":"negrienko","is_requester":true,"auto_approve":false}
|
|
@ -0,0 +1,4 @@
|
||||||
|
# Used by "mix format"
|
||||||
|
[
|
||||||
|
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
|
||||||
|
]
|
|
@ -0,0 +1,24 @@
|
||||||
|
# The directory Mix will write compiled artifacts to.
|
||||||
|
/_build/
|
||||||
|
|
||||||
|
# If you run "mix test --cover", coverage assets end up here.
|
||||||
|
/cover/
|
||||||
|
|
||||||
|
# The directory Mix downloads your dependencies sources to.
|
||||||
|
/deps/
|
||||||
|
|
||||||
|
# Where third-party dependencies like ExDoc output generated docs.
|
||||||
|
/doc/
|
||||||
|
|
||||||
|
# Ignore .fetch files in case you like to edit your project deps locally.
|
||||||
|
/.fetch
|
||||||
|
|
||||||
|
# If the VM crashes, it generates a dump, let's ignore it too.
|
||||||
|
erl_crash.dump
|
||||||
|
|
||||||
|
# Also ignore archive artifacts (built via "mix archive.build").
|
||||||
|
*.ez
|
||||||
|
|
||||||
|
# Ignore package tarball (built via "mix hex.build").
|
||||||
|
paint-by-number-*.tar
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
# Help
|
||||||
|
|
||||||
|
## Running the tests
|
||||||
|
|
||||||
|
From the terminal, change to the base directory of the exercise then execute the tests with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ mix test
|
||||||
|
```
|
||||||
|
|
||||||
|
This will execute the test file found in the `test` subfolder -- a file ending in `_test.exs`
|
||||||
|
|
||||||
|
Documentation:
|
||||||
|
|
||||||
|
* [`mix test` - Elixir's test execution tool](https://hexdocs.pm/mix/Mix.Tasks.Test.html)
|
||||||
|
* [`ExUnit` - Elixir's unit test library](https://hexdocs.pm/ex_unit/ExUnit.html)
|
||||||
|
|
||||||
|
## Pending tests
|
||||||
|
|
||||||
|
In test suites of practice exercises, all but the first test have been tagged to be skipped.
|
||||||
|
|
||||||
|
Once you get a test passing, you can unskip the next one by commenting out the relevant `@tag :pending` with a `#` symbol.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
# @tag :pending
|
||||||
|
test "shouting" do
|
||||||
|
assert Bob.hey("WATCH OUT!") == "Whoa, chill out!"
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
If you wish to run all tests at once, you can include all skipped test by using the `--include` flag on the `mix test` command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ mix test --include pending
|
||||||
|
```
|
||||||
|
|
||||||
|
Or, you can enable all the tests by commenting out the `ExUnit.configure` line in the file `test/test_helper.exs`.
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
# ExUnit.configure(exclude: :pending, trace: true)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Useful `mix test` options
|
||||||
|
|
||||||
|
* `test/<FILE>.exs:LINENUM` - runs only a single test, the test from `<FILE>.exs` whose definition is on line `LINENUM`
|
||||||
|
* `--failed` - runs only tests that failed the last time they ran
|
||||||
|
* `--max-failures` - the suite stops evaluating tests when this number of test failures
|
||||||
|
is reached
|
||||||
|
* `--seed 0` - disables randomization so the tests in a single file will always be ran
|
||||||
|
in the same order they were defined in
|
||||||
|
|
||||||
|
## Submitting your solution
|
||||||
|
|
||||||
|
You can submit your solution using the `exercism submit lib/paint_by_number.ex` command.
|
||||||
|
This command will upload your solution to the Exercism website and print the solution page's URL.
|
||||||
|
|
||||||
|
It's possible to submit an incomplete solution which allows you to:
|
||||||
|
|
||||||
|
- See how others have completed the exercise
|
||||||
|
- Request help from a mentor
|
||||||
|
|
||||||
|
## Need to get help?
|
||||||
|
|
||||||
|
If you'd like help solving the exercise, check the following pages:
|
||||||
|
|
||||||
|
- The [Elixir track's documentation](https://exercism.org/docs/tracks/elixir)
|
||||||
|
- The [Elixir track's programming category on the forum](https://forum.exercism.org/c/programming/elixir)
|
||||||
|
- [Exercism's programming category on the forum](https://forum.exercism.org/c/programming/5)
|
||||||
|
- The [Frequently Asked Questions](https://exercism.org/docs/using/faqs)
|
||||||
|
|
||||||
|
Should those resources not suffice, you could submit your (incomplete) solution to request mentoring.
|
||||||
|
|
||||||
|
If you're stuck on something, it may help to look at some of the [available resources](https://exercism.org/docs/tracks/elixir/resources) out there where answers might be found.
|
|
@ -0,0 +1,57 @@
|
||||||
|
# Hints
|
||||||
|
|
||||||
|
## General
|
||||||
|
|
||||||
|
- Read about [bitstrings][bitstring] in the Getting Started guide.
|
||||||
|
- Review the documentation for the [bitstring special form][bitstring-form].
|
||||||
|
- Review [how to convert decimal numbers to binary numbers][decimal-to-binary-youtube].
|
||||||
|
- Elixir supports [different ways of writing integers][integer-literal], including binary notation.
|
||||||
|
|
||||||
|
## 1. Calculate palette bit size
|
||||||
|
|
||||||
|
- This task doesn't use the bitstring data type.
|
||||||
|
- Find the smallest positive number `n` such that 2 raised to the power of `n` is greater than or equal than the number of colors we need to represent.
|
||||||
|
- Create a recursive function for this.
|
||||||
|
- Choose a starting bit size of 1. If 2 raised to the power of the bit size is greater than or equal to the color count, we've found our bit size. If not, add 1 to the bit size and check again (recursion).
|
||||||
|
- Use [`Integer.pow/2`][integer-pow] to raise 2 to a given power.
|
||||||
|
|
||||||
|
## 2. Create an empty picture
|
||||||
|
|
||||||
|
- Use the [bitstring special form][bitstring-form] to create a new bitstring.
|
||||||
|
|
||||||
|
## 3. Create a test picture
|
||||||
|
|
||||||
|
- Use the [bitstring special form][bitstring-form] to create a new bitstring.
|
||||||
|
- Use the [type operator][type-operator] to specify the bit size of each fragment.
|
||||||
|
|
||||||
|
## 4. Prepend a pixel to a picture
|
||||||
|
|
||||||
|
- The [bitstring special form][bitstring-form] can be used to append a new fragment to an existing bitstring.
|
||||||
|
- Use the [type operator][type-operator] to specify the bit size of the new fragment as well as the existing bitstring.
|
||||||
|
- Use the previously implemented `PaintByNumber.palette_bit_size/1` function to get the bit size of the new fragment.
|
||||||
|
- Use the special `::bitstring` type to specify that the old fragment is of unknown size.
|
||||||
|
|
||||||
|
## 5. Get the first pixel from a picture
|
||||||
|
|
||||||
|
- The [bitstring special form][bitstring-form] can be used to [pattern match bitstrings][bitstring-matching].
|
||||||
|
- Use the previously implemented `PaintByNumber.palette_bit_size/1` function to get the bit size of one pixel.
|
||||||
|
- Use the special `::bitstring` type to specify that the rest of the bitstring is of unknown size.
|
||||||
|
|
||||||
|
## 6. Drop the first pixel from a picture
|
||||||
|
|
||||||
|
- The [bitstring special form][bitstring-form] can be used to [pattern match bitstrings][bitstring-matching].
|
||||||
|
- Use the previously implemented `PaintByNumber.palette_bit_size/1` function to get the bit size of one pixel.
|
||||||
|
- Use the special `::bitstring` type to specify that the rest of the bitstring is of unknown size.
|
||||||
|
|
||||||
|
## 7. Concatenate two pictures
|
||||||
|
|
||||||
|
- The [bitstring special form][bitstring-form] can be used to concatenate two bitstrings.
|
||||||
|
- Use the special `::bitstring` type to specify that each of the bitstring fragments is of unknown size.
|
||||||
|
|
||||||
|
[decimal-to-binary-youtube]: https://www.youtube.com/watch?v=gGiEu7QTi68
|
||||||
|
[integer-literal]: https://hexdocs.pm/elixir/syntax-reference.html#integers-in-other-bases-and-unicode-code-points
|
||||||
|
[bitstring]: https://elixir-lang.org/getting-started/binaries-strings-and-char-lists.html#bitstrings
|
||||||
|
[bitstring-form]: https://hexdocs.pm/elixir/Kernel.SpecialForms.html#%3C%3C%3E%3E/1
|
||||||
|
[bitstring-matching]: https://hexdocs.pm/elixir/Kernel.SpecialForms.html#%3C%3C%3E%3E/1-binary-bitstring-matching
|
||||||
|
[type-operator]: https://hexdocs.pm/elixir/Kernel.SpecialForms.html#::/2
|
||||||
|
[integer-pow]: https://hexdocs.pm/elixir/Integer.html#pow/2
|
|
@ -0,0 +1,226 @@
|
||||||
|
# Paint By Number
|
||||||
|
|
||||||
|
Welcome to Paint By Number on Exercism's Elixir Track.
|
||||||
|
If you need help running the tests or submitting your code, check out `HELP.md`.
|
||||||
|
If you get stuck on the exercise, check out `HINTS.md`, but try and solve it without using those first :)
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
## Bitstrings
|
||||||
|
|
||||||
|
Working with binary data is an important concept in any language, and Elixir provides an elegant syntax to write, match, and construct binary data.
|
||||||
|
|
||||||
|
In Elixir, binary data is referred to as the bitstring type. The binary data _type_ (not to be confused with binary data in general) is a specific form of a bitstring, which we will discuss in a later exercise.
|
||||||
|
|
||||||
|
Bitstring literals are defined using the bitstring special form `<<>>`. When defining a bitstring literal, it is defined in segments. Each segment has a value and type, separated by the `::` operator. The type specifies how many bits will be used to encode the value. The type can be omitted completely, which will default to a 8-bit integer value.
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
# This defines a bitstring with three segments of a single bit each
|
||||||
|
<<0::1, 1::1, 0::1>>
|
||||||
|
```
|
||||||
|
|
||||||
|
Specifying the type as `::1` is a shorthand for writing `::size(1)`. You need to use the longer syntax if the bit size comes from a variable.
|
||||||
|
|
||||||
|
### Binary
|
||||||
|
|
||||||
|
When writing binary integer literals, we can write them directly in base-2 notation by prefixing the literal with `0b`. Note that they will be anyway displayed as decimal numbers when printed in tests results or when using iex.
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
<<0b1011::4>> == <<11::4>>
|
||||||
|
# => true
|
||||||
|
```
|
||||||
|
|
||||||
|
### Truncating
|
||||||
|
|
||||||
|
If the value of the segment overflows the capacity of the segment's type, it will be truncated from the left.
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
<<0b1011::3>> == <<0b0011::3>>
|
||||||
|
# => true
|
||||||
|
```
|
||||||
|
|
||||||
|
### Prepending and appending
|
||||||
|
|
||||||
|
You can both prepend and append to an existing bitstring using the special form. The `::bitstring` type must be used on the existing bitstring if it's of unknown size.
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
value = <<0b110::3, 0b001::3>>
|
||||||
|
new_value = <<0b011::3, value::bitstring, 0b000::3>>
|
||||||
|
# => <<120, 8::size(4)>>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Concatenating
|
||||||
|
|
||||||
|
We can concatenate bitstrings stored in variables using the special form. The `::bitstring` type must be used when concatenating two bitstrings of unknown sizes.
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
first = <<0b110::3>>
|
||||||
|
second = <<0b001::3>>
|
||||||
|
concatenated = <<first::bitstring, second::bitstring>>
|
||||||
|
# => <<49::size(6)>>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pattern matching
|
||||||
|
|
||||||
|
Pattern matching can also be done to obtain values from the special form. You have to know the number of bits for each fragment you want to capture, with one exception: the `::bitstring` type can be used to pattern match on a bitstring of an unknown size, but this can only be used for the last fragment.
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
<<value::4, rest::bitstring>> = <<0b01101001::8>>
|
||||||
|
value == 0b0110
|
||||||
|
# => true
|
||||||
|
```
|
||||||
|
|
||||||
|
### Inspecting bitstrings
|
||||||
|
|
||||||
|
~~~~exercism/note
|
||||||
|
Bitstrings might be printed (by the test runner or in iex) in a
|
||||||
|
different format than the format that was used to create them. This often
|
||||||
|
causes confusion when learning bitstrings.
|
||||||
|
~~~~
|
||||||
|
|
||||||
|
By default, bitstrings are displayed in fragments of 8 bits (a byte), even if you created them with fragments of a different size.
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
<<2011::11>>
|
||||||
|
# => <<251, 3::size(3)>>
|
||||||
|
```
|
||||||
|
|
||||||
|
If you create a bitstring that represents a printable UTF-8 encoded string, it gets displayed as a string.
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
<<>>
|
||||||
|
# => ""
|
||||||
|
|
||||||
|
<<65, 66, 67>>
|
||||||
|
# => "ABC"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
[Paint by number][paint-by-number] (also known as _color by number_)
|
||||||
|
are black-and-white pictures meant for coloring.
|
||||||
|
The different areas of the picture are annotated with different numbers,
|
||||||
|
the numbers correspond to specific colors in a predefined color palette.
|
||||||
|
The goal is to fill the areas with the right colors,
|
||||||
|
revealing a beautiful colorful picture at the end.
|
||||||
|
It's a relaxing activity for both kids and adults.
|
||||||
|
|
||||||
|
You have been tasked with writing a paint-by-number app in Elixir.
|
||||||
|
You want your app to be able to import and export pictures in a custom data format.
|
||||||
|
You have decided to use [binary files][binary-file] to store your picture data.
|
||||||
|
|
||||||
|
~~~~exercism/note
|
||||||
|
This exercise assumes that you're familiar with [binary numbers](https://en.wikipedia.org/wiki/Binary_number)
|
||||||
|
and understand the principles behind changing binary numbers to decimal numbers
|
||||||
|
and decimal numbers to binary numbers.
|
||||||
|
~~~~
|
||||||
|
|
||||||
|
Let's imagine you have a picture of a smiley, like the one shown below.
|
||||||
|
The picture has a white background. The smiley has a black border and a yellow fill color.
|
||||||
|
|
||||||
|
This picture uses 3 colors.
|
||||||
|
Let's say we assign indices to those colors:
|
||||||
|
- `0` (binary: `0b00`) for white,
|
||||||
|
- `1` (binary: `0b01`) for black,
|
||||||
|
- `2` (binary: `0b10`) for yellow.
|
||||||
|
|
||||||
|
We can now use those color indices to represent the color of each pixel.
|
||||||
|
|
||||||
|
| Smiley | Smiley with color indices |
|
||||||
|
|-------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------|
|
||||||
|
| ![](https://assets.exercism.org/images/exercises/paint-by-number/smiley.png) | ![](https://assets.exercism.org/images/exercises/paint-by-number/smiley-numbers.png) |
|
||||||
|
|
||||||
|
## 1. Calculate palette bit size
|
||||||
|
|
||||||
|
Implement the `PaintByNumber.palette_bit_size/1` function. It should take the count of colors in the palette and return how many bits are necessary to represent that many color indices as binary numbers. Color indices always start at 0 and are continuous ascending integers.
|
||||||
|
|
||||||
|
For example, representing 13 different colors require 4 bits. 4 bits can store up to 16 color indices (2^4). 3 bits would not be enough because 3 bits can only store up to 8 color indices (2^3).
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
PaintByNumber.palette_bit_size(13)
|
||||||
|
# => 4
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: there is no `log2` function in the Elixir standard library. You will later learn how to use [Erlang libraries][erlang-libraries] from Elixir where you can find this function. Now, solve this task with recursion and the [`Integer.pow/2`][integer-pow] function instead. If you're solving this exercise on your own computer using an older Elixir version (1.11 or lower) that doesn't have `Integer.pow/2` function, use the `Math.pow/2` function we provided in the `lib/math.ex` file for this exercise.
|
||||||
|
|
||||||
|
## 2. Create an empty picture
|
||||||
|
|
||||||
|
Implement the `PaintByNumber.empty_picture/0` function. It should return an empty bitstring.
|
||||||
|
|
||||||
|
## 3. Create a test picture
|
||||||
|
|
||||||
|
A predefined test picture will be used for manual testing of basic features of your app.
|
||||||
|
The test picture consists of 4 pixels with 4 different colors.
|
||||||
|
|
||||||
|
Implement the `PaintByNumber.test_picture/0` function. It should return a bitstring that consists of 4 segments.
|
||||||
|
Each segment should have a bit size of 2. The segments should have values 0, 1, 2, and 3.
|
||||||
|
|
||||||
|
## 4. Prepend a pixel to a picture
|
||||||
|
|
||||||
|
Implement the `PaintByNumber.prepend_pixel/3` function. It should take three arguments: a bitstring with the picture to which we're prepending, the count of colors in the palette, and the index of the color for the new pixel. It should return a bitstring with a picture with the new pixel added to the beginning.
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
picture = <<2::4, 0::4>>
|
||||||
|
color_count = 13
|
||||||
|
pixel_color_index = 11
|
||||||
|
|
||||||
|
PaintByNumber.prepend_pixel(picture, color_count, pixel_color_index)
|
||||||
|
# => <<178, 0::size(4)>>
|
||||||
|
# (which is equal to <<11::4, 2::4, 0::4>>)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. Get the first pixel from a picture
|
||||||
|
|
||||||
|
Implement the `PaintByNumber.get_first_pixel/2` function. It should take two arguments: a bitstring with the picture from which we're reading, and the count of colors in the palette. It should return the color index of the first pixel in the given picture. When given an empty picture, it should return `nil`.
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
picture = <<19::5, 2::5, 18::5>>
|
||||||
|
color_count = 20
|
||||||
|
|
||||||
|
PaintByNumber.get_first_pixel(picture, color_count)
|
||||||
|
# => 19
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. Drop the first pixel from a picture
|
||||||
|
|
||||||
|
Implement the `PaintByNumber.drop_first_pixel/2` function. It should take two arguments: a bitstring with the picture from which we're removing a pixel, and the count of colors in the palette. It should return the picture without the first pixel. When given an empty picture, it should return an empty picture.
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
picture = <<2::3, 5::3, 5::3, 0::3>>
|
||||||
|
color_count = 6
|
||||||
|
|
||||||
|
PaintByNumber.drop_first_pixel(picture, color_count)
|
||||||
|
# => <<180, 0::size(1)>>
|
||||||
|
# (which is equal to <<5::3, 5::3, 0::3>>)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 7. Concatenate two pictures
|
||||||
|
|
||||||
|
Implement the `PaintByNumber.concat_pictures/2` function. It should take two arguments, two bitstrings. It should return a bitstrings that is the result of prepending the first argument to the second argument.
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
picture1 = <<52::6, 51::6>>
|
||||||
|
picture2 = <<0::6, 34::6, 12::6>>
|
||||||
|
|
||||||
|
PaintByNumber.concat_pictures(picture1, picture2)
|
||||||
|
# => <<211, 48, 34, 12::size(6)>>
|
||||||
|
# (which is equal to <<52::6, 51::6, 0::6, 34::6, 12::6>>)
|
||||||
|
```
|
||||||
|
|
||||||
|
[paint-by-number]: https://en.wikipedia.org/wiki/Paint_by_number
|
||||||
|
[binary-file]: https://en.wikipedia.org/wiki/Binary_file
|
||||||
|
[erlang-libraries]: https://exercism.org/tracks/elixir/concepts/erlang-libraries
|
||||||
|
[integer-pow]: https://hexdocs.pm/elixir/Integer.html#pow/2
|
||||||
|
|
||||||
|
## Source
|
||||||
|
|
||||||
|
### Created by
|
||||||
|
|
||||||
|
- @angelikatyborska
|
||||||
|
|
||||||
|
### Contributed to by
|
||||||
|
|
||||||
|
- @neenjaw
|
||||||
|
- @meatball133
|
||||||
|
- @vaeng
|
||||||
|
- @glennj
|
|
@ -0,0 +1,18 @@
|
||||||
|
# You can ignore this file if you're solving this exercise in the web editor,
|
||||||
|
# or on your own computer if you have Elixir version 1.12 or higher.
|
||||||
|
# You can check which Elixir version you have by running `elixir -v` in your terminal.
|
||||||
|
defmodule Math do
|
||||||
|
import Bitwise
|
||||||
|
|
||||||
|
# copied from https://github.com/elixir-lang/elixir/blob/v1.12.0/lib/elixir/lib/integer.ex#L103-L114
|
||||||
|
def pow(base, exponent) when is_integer(base) and is_integer(exponent) do
|
||||||
|
if exponent < 0, do: raise("exponent cannot be negative")
|
||||||
|
guarded_pow(base, exponent)
|
||||||
|
end
|
||||||
|
|
||||||
|
# https://en.wikipedia.org/wiki/Exponentiation_by_squaring
|
||||||
|
defp guarded_pow(_, 0), do: 1
|
||||||
|
defp guarded_pow(b, 1), do: b
|
||||||
|
defp guarded_pow(b, e) when (e &&& 1) == 0, do: guarded_pow(b * b, e >>> 1)
|
||||||
|
defp guarded_pow(b, e), do: b * guarded_pow(b * b, e >>> 1)
|
||||||
|
end
|
|
@ -0,0 +1,41 @@
|
||||||
|
defmodule PaintByNumber do
|
||||||
|
def palette_bit_size(color_count, pow \\ 1) do
|
||||||
|
if color_count <= 2 ** pow do
|
||||||
|
pow
|
||||||
|
else
|
||||||
|
palette_bit_size(color_count, pow + 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def empty_picture() do
|
||||||
|
<<>>
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_picture() do
|
||||||
|
<<0::2, 1::2, 2::2, 3::2>>
|
||||||
|
end
|
||||||
|
|
||||||
|
def prepend_pixel(picture, color_count, pixel_color_index) do
|
||||||
|
size = palette_bit_size(color_count)
|
||||||
|
pixel = <<pixel_color_index::size(size)>>
|
||||||
|
<<pixel::bitstring, picture::bitstring>>
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_first_pixel(<<>>, _color_count), do: nil
|
||||||
|
def get_first_pixel(picture, color_count) do
|
||||||
|
size = palette_bit_size(color_count)
|
||||||
|
<<value::size(size), _rest::bitstring>> = picture
|
||||||
|
value
|
||||||
|
end
|
||||||
|
|
||||||
|
def drop_first_pixel(<<>>, _color_count), do: <<>>
|
||||||
|
def drop_first_pixel(picture, color_count) do
|
||||||
|
size = palette_bit_size(color_count)
|
||||||
|
<<_value::size(size), rest::bitstring>> = picture
|
||||||
|
rest
|
||||||
|
end
|
||||||
|
|
||||||
|
def concat_pictures(picture1, picture2) do
|
||||||
|
<<picture1::bitstring, picture2::bitstring>>
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,28 @@
|
||||||
|
defmodule PaintByNumber.MixProject do
|
||||||
|
use Mix.Project
|
||||||
|
|
||||||
|
def project do
|
||||||
|
[
|
||||||
|
app: :paint_by_number,
|
||||||
|
version: "0.1.0",
|
||||||
|
# elixir: "~> 1.10",
|
||||||
|
start_permanent: Mix.env() == :prod,
|
||||||
|
deps: deps()
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
# Run "mix help compile.app" to learn about applications.
|
||||||
|
def application do
|
||||||
|
[
|
||||||
|
extra_applications: [:logger]
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
# Run "mix help deps" to learn about dependencies.
|
||||||
|
defp deps do
|
||||||
|
[
|
||||||
|
# {:dep_from_hexpm, "~> 0.3.0"},
|
||||||
|
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
|
||||||
|
]
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,196 @@
|
||||||
|
defmodule PaintByNumberTest do
|
||||||
|
use ExUnit.Case
|
||||||
|
|
||||||
|
describe "palette_bit_size/1" do
|
||||||
|
@tag task_id: 1
|
||||||
|
test "needs 1 bit to encode two colors" do
|
||||||
|
color_count = 2
|
||||||
|
assert PaintByNumber.palette_bit_size(color_count) == 1
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag task_id: 1
|
||||||
|
test "needs 2 bits to encode three colors" do
|
||||||
|
color_count = 3
|
||||||
|
assert PaintByNumber.palette_bit_size(color_count) == 2
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag task_id: 1
|
||||||
|
test "needs 2 bits to encode four colors" do
|
||||||
|
color_count = 4
|
||||||
|
assert PaintByNumber.palette_bit_size(color_count) == 2
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag task_id: 1
|
||||||
|
test "needs 3 bits to encode seven colors" do
|
||||||
|
color_count = 7
|
||||||
|
assert PaintByNumber.palette_bit_size(color_count) == 3
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag task_id: 1
|
||||||
|
test "needs 3 bits to encode eight colors" do
|
||||||
|
color_count = 8
|
||||||
|
assert PaintByNumber.palette_bit_size(color_count) == 3
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag task_id: 1
|
||||||
|
test "needs 4 bits to encode nine colors" do
|
||||||
|
color_count = 9
|
||||||
|
assert PaintByNumber.palette_bit_size(color_count) == 4
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag task_id: 1
|
||||||
|
test "needs 4 bits to encode fourteen colors" do
|
||||||
|
color_count = 14
|
||||||
|
assert PaintByNumber.palette_bit_size(color_count) == 4
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag task_id: 1
|
||||||
|
test "needs 6 bits to encode fifty colors" do
|
||||||
|
color_count = 50
|
||||||
|
assert PaintByNumber.palette_bit_size(color_count) == 6
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag task_id: 1
|
||||||
|
test "needs 20 bits to encode 1 million colors" do
|
||||||
|
color_count = 1_000_000
|
||||||
|
assert PaintByNumber.palette_bit_size(color_count) == 20
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "empty_picture/0" do
|
||||||
|
@tag task_id: 2
|
||||||
|
test "returns an empty bitstring" do
|
||||||
|
assert PaintByNumber.empty_picture() == <<>>
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "test_picture/0" do
|
||||||
|
@tag task_id: 3
|
||||||
|
test "returns the numbers 0, 1, 2, and 3 as fragments of size 2" do
|
||||||
|
assert PaintByNumber.test_picture() == <<0::2, 1::2, 2::2, 3::2>>
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "prepend_pixel/3" do
|
||||||
|
@tag task_id: 4
|
||||||
|
test "works with an empty picture" do
|
||||||
|
picture = <<>>
|
||||||
|
color_count = 16
|
||||||
|
pixel_color_index = 1
|
||||||
|
assert PaintByNumber.prepend_pixel(picture, color_count, pixel_color_index) == <<1::4>>
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag task_id: 4
|
||||||
|
test "works with a non-empty picture" do
|
||||||
|
picture = <<3::3, 2::3, 2::3>>
|
||||||
|
color_count = 7
|
||||||
|
pixel_color_index = 0
|
||||||
|
|
||||||
|
assert PaintByNumber.prepend_pixel(picture, color_count, pixel_color_index) ==
|
||||||
|
<<0::3, 3::3, 2::3, 2::3>>
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag task_id: 4
|
||||||
|
test "pixel color overflows if it doesn't fit in palette size" do
|
||||||
|
picture = <<3::6>>
|
||||||
|
color_count = 64
|
||||||
|
pixel_color_index = 64
|
||||||
|
|
||||||
|
assert PaintByNumber.prepend_pixel(picture, color_count, pixel_color_index) ==
|
||||||
|
<<0::6, 3::6>>
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "get_first_pixel/2" do
|
||||||
|
@tag task_id: 5
|
||||||
|
test "returns nil if empty picture" do
|
||||||
|
picture = <<>>
|
||||||
|
color_count = 16
|
||||||
|
assert PaintByNumber.get_first_pixel(picture, color_count) == nil
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag task_id: 5
|
||||||
|
test "works with a non-empty picture" do
|
||||||
|
picture = <<1::2, 0::2, 0::2, 2::2>>
|
||||||
|
color_count = 3
|
||||||
|
assert PaintByNumber.get_first_pixel(picture, color_count) == 1
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag task_id: 5
|
||||||
|
test "can reinterpret picture data with a different color count" do
|
||||||
|
picture = <<0b01::2, 0b10::2, 0b00::2, 0b10::2>>
|
||||||
|
# Color count of 8 means 3 bits.
|
||||||
|
color_count = 8
|
||||||
|
# We take bits from segments until we have 3 bits.
|
||||||
|
# First, we take `01` from the first segment. Then, `1` from the second segment.
|
||||||
|
# This gives us the binary number `011`, which is equal to the decimal number 5.
|
||||||
|
assert PaintByNumber.get_first_pixel(picture, color_count) == 0b011
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "drop_first_pixel/2" do
|
||||||
|
@tag task_id: 6
|
||||||
|
test "returns empty picture if empty picture" do
|
||||||
|
picture = <<>>
|
||||||
|
color_count = 5
|
||||||
|
assert PaintByNumber.drop_first_pixel(picture, color_count) == <<>>
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag task_id: 6
|
||||||
|
test "works with a non-empty picture" do
|
||||||
|
picture = <<23::5, 21::5, 15::5, 3::5>>
|
||||||
|
color_count = 32
|
||||||
|
assert PaintByNumber.drop_first_pixel(picture, color_count) == <<21::5, 15::5, 3::5>>
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag task_id: 6
|
||||||
|
test "can reinterpret picture data with a different color count" do
|
||||||
|
picture = <<0b011011::6, 0b110001::6>>
|
||||||
|
# Color count of 4 means 2 bits.
|
||||||
|
color_count = 4
|
||||||
|
# We remove the first 2 bits from the first segment.
|
||||||
|
assert PaintByNumber.drop_first_pixel(picture, color_count) == <<0b1011::4, 0b110001::6>>
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "concat_pictures/2" do
|
||||||
|
@tag task_id: 7
|
||||||
|
test "two empty pictures result in an empty picture" do
|
||||||
|
picture1 = <<>>
|
||||||
|
picture2 = <<>>
|
||||||
|
assert PaintByNumber.concat_pictures(picture1, picture2) == <<>>
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag task_id: 7
|
||||||
|
test "a non-picture with an empty picture" do
|
||||||
|
picture1 = <<5::3, 2::3, 2::3, 4::3>>
|
||||||
|
picture2 = <<>>
|
||||||
|
assert PaintByNumber.concat_pictures(picture1, picture2) == picture1
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag task_id: 7
|
||||||
|
test "an empty picture with a non-empty picture" do
|
||||||
|
picture1 = <<>>
|
||||||
|
picture2 = <<13::4, 11::4, 0::4>>
|
||||||
|
assert PaintByNumber.concat_pictures(picture1, picture2) == picture2
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag task_id: 7
|
||||||
|
test "two non-empty pictures" do
|
||||||
|
picture1 = <<2::4, 2::4, 1::4, 14::4>>
|
||||||
|
picture2 = <<15::4, 14::4>>
|
||||||
|
|
||||||
|
assert PaintByNumber.concat_pictures(picture1, picture2) ==
|
||||||
|
<<2::4, 2::4, 1::4, 14::4, 15::4, 14::4>>
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag task_id: 7
|
||||||
|
test "two non-empty pictures with different palette bit sizes" do
|
||||||
|
picture1 = <<0b00::2, 0b01::2, 0b11::2, 0b01::2>>
|
||||||
|
picture2 = <<0b10101::5, 0b10011::5>>
|
||||||
|
|
||||||
|
assert PaintByNumber.concat_pictures(picture1, picture2) ==
|
||||||
|
<<0b00011101::8, 0b10101100::8, 0b11::2>>
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,2 @@
|
||||||
|
ExUnit.start()
|
||||||
|
ExUnit.configure(exclude: :pending, trace: true, seed: 0)
|
Loading…
Reference in New Issue