diff --git a/concepts/bitwise-operations/.meta/config.json b/concepts/bitwise-operations/.meta/config.json new file mode 100644 index 00000000..de93ebb4 --- /dev/null +++ b/concepts/bitwise-operations/.meta/config.json @@ -0,0 +1,7 @@ +{ + "authors": [ + "stewartmurrie" + ], + "contributors": ["jiegillet"], + "blurb": "Learn how to use integer bitwise operations in Elm" +} diff --git a/concepts/bitwise-operations/about.md b/concepts/bitwise-operations/about.md new file mode 100644 index 00000000..fb478c0d --- /dev/null +++ b/concepts/bitwise-operations/about.md @@ -0,0 +1,117 @@ +# About + +## Bitwise operations + +Bitwise operations allow us to manipulate individual digits within binary numbers. +These operations are fundamental in computing, providing an efficient way to perform low-level tasks, such as controlling specific bits in memory, optimizing mathematical calculations, encryption, communications, and encoding/decoding data. + +### Understanding 32-bit Integers in Elm + +In Elm, integers are stored as 32-bit signed integers using [two's complement](https://en.wikipedia.org/wiki/Two%27s_complement) representation, meaning they use 32 binary digits (_bits_) to store each integer value. +This bit limit affects the range of integers that Elm can represent directly: + +Positive Range: 0 to 231 - 1 (or 0 to 2,147,483,647) +Negative Range: -231 to -1 (or -2,147,483,648 to -1). + +For example, the integer `5` is represented in binary as `00000000000000000000000000000101`, although usually we ignore the leading zeros and just say `101`. + +### Bitwise operations + +Elm provides several bitwise operators in its [Bitwise module](https://package.elm-lang.org/packages/elm/core/latest/Bitwise) + +### Basic operations + +Modifying individual bits of a number is called _masking_. +A _mask_ is a number where specific bits have been set in a particular way to manipulate another number using bitwise operators such as `and`, `or`, and `xor`. + +#### and + +`and` combines two numbers by keeping only the bits that are `1` in both. +This is useful for checking to see if an individual bit is set. +For example, to check if the 4th bit of a number is set to `1`, `and` it with a mask of `01000` (`8` in decimal) and see if the result is non-zero: + +````elm +Bitwise.and 13 8 --> 8 +-- 13 = 01101 +-- 8 = 01000 +-- and = 01000 = 8 + + +### or + +`or` combines two numbers by setting each bit to `1` if it is `1` in either or both numbers. +This is useful for setting a specific bit to `1`. +For example, to set the 2nd bit in `10101`, `or` it with the mask `00010`: + +```elm +Bitwise.or 21 2 --> 23 +-- 21 = 10101 +-- 2 = 00010 +-- or = 10111 = 23 +```` + +### Exclusive-or (xor) + +`xor` combines two numbers by setting each bit to `1` if it is `1` in one number but `0` in the other. +This is useful for flipping a bit to its opposite value: + +````elm +Bitwise.xor 21 4 --> 17 +-- 21 = 10101 +-- 4 = 00100 +-- xor = 10001 = 17 + +#### Complement + +`complement` inverts each bit of a number (`0` becomes `1`, `1` becomes `0`). + +Note that this will result in positive numbers becoming negative, and negative numbers becoming positive. +This is because negative numbers in binary are represented with `1` in the left-most position. + +```elm +Bitwise.complement 21 --> -22 +-- 21 = 00000000000000000000000000010101 +-- complement = 11111111111111111111111111101010 = -22 +```` + +### Bit shifting + +The following operators move bits left or right by a specified number of positions, effectively multiplying or dividing by powers of 2. + +`shiftLeftBy` moves bits to the left, filling in with `0` from the right-hand side. +For example, to shift `21` left by 3 places: + +```elm +Bitwise.shiftLeftBy 3 21 --> 168 +-- 21 = 10101 +-- shiftLeftBy 3 = 10101000 = 168 +``` + +This is the same as saying `21 * 2^3 = 21 * 2 * 2 * 2 = 168` + +`shiftRightBy`: Moves bits to the right: + +```elm +Bitwise.shiftRightBy 2 21 --> 5 +-- 21 = 10101 +-- shiftRightBy 2 = 00101 = 5 +``` + +Shifting to the right by 2 places is the same as integer division by 4. + +Note that this function duplicates whatever value is in the leftmost bit. +So, negative numbers will stay negative: + +```elm +Bitwise.shiftRightBy 3 -21 --> -6 +-- -21 = 111...10101 +-- shiftRightBy 3 = 111...11101 = -6 +``` + +If you want to shift right and fill in with zeros, use `shiftRightZfBy`: + +```elm +Bitwise.shiftRightZfBy 3 -21 --> 1073741818 +-- -21 = 111...10101 +-- shiftRightZfBy 3 = 00111...11101 = 1073741818 +``` diff --git a/concepts/bitwise-operations/introduction.md b/concepts/bitwise-operations/introduction.md new file mode 100644 index 00000000..9580c794 --- /dev/null +++ b/concepts/bitwise-operations/introduction.md @@ -0,0 +1,104 @@ +# Introduction + +Bitwise operations allow us to manipulate individual digits within binary numbers. + +## Bitwise operations + +Elm provides several bitwise operators in its [Bitwise module](https://package.elm-lang.org/packages/elm/core/latest/Bitwise) + +## Basic operations + +Modifying individual bits of a number is called _masking_. +A _mask_ is a number where specific bits have been set in a particular way to manipulate another number using bitwise operators such as `and`, `or`, and `xor`. + +### and + +`and` combines two numbers by keeping only the bits that are `1` in both. +This is useful for checking to see if an individual bit is set. +For example, to check if the 4th bit of a number is set to `1`, `and` it with a mask of `01000` (`8` in decimal) and see if the result is non-zero: + +````elm +Bitwise.and 13 8 --> 8 +-- 13 = 01101 +-- 8 = 01000 +-- and = 01000 = 8 + + +### or + +`or` combines two numbers by setting each bit to `1` if it is `1` in either or both numbers. +This is useful for setting a specific bit to `1`. +For example, to set the 2nd bit in `10101`, `or` it with the mask `00010`: + +```elm +Bitwise.or 21 2 --> 23 +-- 21 = 10101 +-- 2 = 00010 +-- or = 10111 = 23 +```` + +### Exclusive-or (xor) + +`xor` combines two numbers by setting each bit to `1` if it is `1` in one number but `0` in the other. +This is useful for flipping a bit to its opposite value: + +````elm +Bitwise.xor 21 4 --> 17 +-- 21 = 10101 +-- 4 = 00100 +-- xor = 10001 = 17 + +### Complement + +`complement` inverts each bit of a number (`0` becomes `1`, `1` becomes `0`). + +Note that this will result in positive numbers becoming negative, and negative numbers becoming positive. +This is because negative numbers in binary are represented with `1` in the left-most position. + +```elm +Bitwise.complement 21 --> -22 +-- 21 = 00000000000000000000000000010101 +-- complement = 11111111111111111111111111101010 = -22 +```` + +## Bit shifting + +The following operators move bits left or right by a specified number of positions, effectively multiplying or dividing by powers of 2. + +`shiftLeftBy` moves bits to the left, filling in with `0` from the right-hand side. +For example, to shift `21` left by 3 places: + +```elm +Bitwise.shiftLeftBy 3 21 --> 168 +-- 21 = 10101 +-- shiftLeftBy 3 = 10101000 = 168 +``` + +This is the same as saying `21 * 2^3 = 21 * 2 * 2 * 2 = 168` + +`shiftRightBy`: Moves bits to the right: + +```elm +Bitwise.shiftRightBy 2 21 --> 5 +-- 21 = 10101 +-- shiftRightBy 2 = 00101 = 5 +``` + +Shifting to the right by 2 places is the same as integer division by 4. + +Note that this function duplicates whatever value is in the leftmost bit. +So, negative numbers will stay negative: + +```elm +Bitwise.shiftRightBy 3 -21 --> -6 +-- -21 = 111...10101 +-- shiftRightBy 3 = 111...11101 = -6 +``` + +If you want to shift right and fill in with zeros, use `shiftRightZfBy`: + +```elm +Bitwise.shiftRightZfBy 3 -21 --> 1073741818 +-- -21 = 111...10101 +-- shiftRightZfBy 3 = 00111...11101 = 1073741818 +``` diff --git a/concepts/bitwise-operations/links.json b/concepts/bitwise-operations/links.json new file mode 100644 index 00000000..f3c37daa --- /dev/null +++ b/concepts/bitwise-operations/links.json @@ -0,0 +1,10 @@ +[ + { + "url": "https://package.elm-lang.org/packages/elm/core/latest/Bitwise", + "description": "Bitwise module" + }, + { + "url": "https://guide.elm-lang.org/appendix/types_as_bits", + "description": "Documentation on how Elm represents types as bits" + } +] diff --git a/config.json b/config.json index 823ab69c..f1f4d154 100644 --- a/config.json +++ b/config.json @@ -339,6 +339,18 @@ "booleans" ], "status": "beta" + }, + { + "slug": "secrets", + "name": "Secrets", + "uuid": "89a899b1-8cb2-4099-98c9-468723728a28", + "concepts": [ + "bitwise-operations" + ], + "prerequisites": [ + "basics-1", + "basics-2" + ] } ], "practice": [ @@ -503,11 +515,14 @@ "slug": "allergies", "name": "Allergies", "uuid": "29b5a28a-417a-4cee-ba6f-9dd942ceffaa", - "practices": [], - "prerequisites": [], + "practices": [ + "bitwise-operations" + ], + "prerequisites": [ + "bitwise-operations" + ], "difficulty": 4, "topics": [ - "bitwise_operations", "enumerations", "filtering" ] @@ -1255,8 +1270,11 @@ "slug": "secret-handshake", "name": "Secret Handshake", "uuid": "6c7a96ec-0bfb-4284-bd9f-2aa6989c3bb5", - "practices": [], + "practices": [ + "bitwise-operations" + ], "prerequisites": [ + "bitwise-operations", "custom-types", "pattern-matching", "maybe", @@ -1505,6 +1523,11 @@ "uuid": "357ffb6e-5a73-49b6-8b69-98ecd3c74c33", "slug": "random", "name": "Random" + }, + { + "uuid": "8737961b-c96f-4313-8737-6b88034c66d8", + "slug": "bitwise-operations", + "name": "Bitwise Operations" } ], "key_features": [ diff --git a/exercises/concept/secrets/.docs/hints.md b/exercises/concept/secrets/.docs/hints.md new file mode 100644 index 00000000..1c38b6aa --- /dev/null +++ b/exercises/concept/secrets/.docs/hints.md @@ -0,0 +1,38 @@ +# Hints + +- The documentation for the `Bitwise` package can be found [here][bitwise-docs]. + +## 1. Shift back the bits + +- There are two functions for shifting bits to the right, but [only one][bitwise-shiftRightZfBy] will always insert a 0. + +## 2. Set some bits + +- [One of the bitwise functions][bitwise-or] will always set a bit to 1 where the bits in both values are 1. + +## 3. Flip specific bits + +- There is [a bitwise function][bitwise-xor] that will flip a bit where the mask is 1. + +## 4. Clear specific bits + +- [One of the bitwise functions][bitwise-and] clears bits where the bit in the mask is 0. +- But, you may need to combine it with [another function][bitwise-complement] to clear bits where the mask is 1. + +## 5. Decrypt a message + +- Apply the other functions you wrote to the input in the following order, taking the output of one and using it as the input to the next one: + +1. `setBits` +2. `flipBits` +3. `shiftBack` +4. `clearBits` + +For step 4, you'll need to convert the binary number with the 1st and 5th bits set (10001) to decimal. + +[bitwise-docs]: https://package.elm-lang.org/packages/elm/core/latest/Bitwise +[bitwise-shiftRightZfBy]: https://package.elm-lang.org/packages/elm/core/latest/Bitwise#shiftRightZfBy +[bitwise-or]: https://package.elm-lang.org/packages/elm/core/latest/Bitwise#or +[bitwise-xor]: https://package.elm-lang.org/packages/elm/core/latest/Bitwise#xor +[bitwise-complement]: https://package.elm-lang.org/packages/elm/core/latest/Bitwise#complement +[bitwise-and]: https://package.elm-lang.org/packages/elm/core/latest/Bitwise#and diff --git a/exercises/concept/secrets/.docs/instructions.md b/exercises/concept/secrets/.docs/instructions.md new file mode 100644 index 00000000..c7f2d684 --- /dev/null +++ b/exercises/concept/secrets/.docs/instructions.md @@ -0,0 +1,68 @@ +# Instructions + +Your friend has just sent you a message with an important secret. +Not wanting to make it easy for others to read it, the message was encrypted by performing a series of bit manipulations. +You will need to write the functions to help decrypt the message. + +## 1. Shift back the bits + +The first step in decrypting the message is to undo the shifting from the encryption process by shifting the bits back to the right. +There will be further steps in the decryption process that assume `0`s are inserted from the left hand side. + +Implement the `shiftBack` function that takes a number of places to shift by and a value and peforms the shift. + +```elm +shiftBack 2 42 --> 10 +``` + +## 2. Set some bits + +Next, there are some bits that need to be set to `1`. + +Implement the `setBits` function that takes a mask and value and returns the result of setting the bits in value to `1`. +A bit from value should be set to `1` where the bit in the mask is also `1`. +All other bits should be kept unchanged. + +```elm +setBits 66 212 --> 64 +``` + +## 3. Flip specific bits + +Some bits are flipped during encryption. +They will need to be flipped back to decrypt the message. + +Implement the `flipBits` function that takes a mask and a value. +The mask indicates which bits in the value to flip. +If the bit is `1` in mask, the bit is flipped in the value. +All other bits are kept unchanged. + +```elm +flipBits 23 157 --> 138 +``` + +## 4. Clear specific bits + +There are also certain bits that always decrypt to 0. + +Implement the `clearBits` function that takes a mask and a value. +The bits in the `value` should be set to 0 where the bit in the mask is 1. +All other bits should be kept unchanged. + +```elm +clearBits 2 15 --> 13 +``` + +## 5. Decrypt a message + +Now that you have all the functions you need, you can decode your friend's message. +Implement the `decrypt` function that performs the following operations: + +1. Set the bits from the year your friend was born (1996) +2. Flip the result with the year that you first met (2009) +3. Shift the bits back by the number of classes you take together (5) +4. Clear the first and fifth bit. + +```elm +decrypt 380182 --> 11840 +``` diff --git a/exercises/concept/secrets/.docs/introduction.md b/exercises/concept/secrets/.docs/introduction.md new file mode 100644 index 00000000..6fc4f9b3 --- /dev/null +++ b/exercises/concept/secrets/.docs/introduction.md @@ -0,0 +1,104 @@ +# Introduction + +Bitwise operations allow us to manipulate individual digits within binary numbers. + +## Bitwise operations + +Elm provides several bitwise operators in its [Bitwise module](https://package.elm-lang.org/packages/elm/core/latest/Bitwise) + +### Basic operations + +Modifying individual bits of a number is called _masking_. +A _mask_ is a number where specific bits have been set in a particular way to manipulate another number using bitwise operators such as `and`, `or`, and `xor`. + +#### and + +`and` combines two numbers by keeping only the bits that are `1` in both. +This is useful for checking to see if an individual bit is set. +For example, to check if the 4th bit of a number is set to `1`, `and` it with a mask of `01000` (`8` in decimal) and see if the result is non-zero: + +````elm +Bitwise.and 13 8 --> 8 +-- 13 = 01101 +-- 8 = 01000 +-- and = 01000 = 8 + + +### or + +`or` combines two numbers by setting each bit to `1` if it is `1` in either or both numbers. +This is useful for setting a specific bit to `1`. +For example, to set the 2nd bit in `10101`, `or` it with the mask `00010`: + +```elm +Bitwise.or 21 2 --> 23 +-- 21 = 10101 +-- 2 = 00010 +-- or = 10111 = 23 +```` + +### Exclusive-or (xor) + +`xor` combines two numbers by setting each bit to `1` if it is `1` in one number but `0` in the other. +This is useful for flipping a bit to its opposite value: + +````elm +Bitwise.xor 21 4 --> 17 +-- 21 = 10101 +-- 4 = 00100 +-- xor = 10001 = 17 + +#### Complement + +`complement` inverts each bit of a number (`0` becomes `1`, `1` becomes `0`). + +Note that this will result in positive numbers becoming negative, and negative numbers becoming positive. +This is because negative numbers in binary are represented with `1` in the left-most position. + +```elm +Bitwise.complement 21 --> -22 +-- 21 = 00000000000000000000000000010101 +-- complement = 11111111111111111111111111101010 = -22 +```` + +### Bit shifting + +The following operators move bits left or right by a specified number of positions, effectively multiplying or dividing by powers of 2. + +`shiftLeftBy` moves bits to the left, filling in with `0` from the right-hand side. +For example, to shift `21` left by 3 places: + +```elm +Bitwise.shiftLeftBy 3 21 --> 168 +-- 21 = 10101 +-- shiftLeftBy 3 = 10101000 = 168 +``` + +This is the same as saying `21 * 2^3 = 21 * 2 * 2 * 2 = 168` + +`shiftRightBy`: Moves bits to the right: + +```elm +Bitwise.shiftRightBy 2 21 --> 5 +-- 21 = 10101 +-- shiftRightBy 2 = 00101 = 5 +``` + +Shifting to the right by 2 places is the same as integer division by 4. + +Note that this function duplicates whatever value is in the leftmost bit. +So, negative numbers will stay negative: + +```elm +Bitwise.shiftRightBy 3 -21 --> -6 +-- -21 = 111...10101 +-- shiftRightBy 3 = 111...11101 = -6 +``` + +If you want to shift right and fill in with zeros, use `shiftRightZfBy`: + +```elm +Bitwise.shiftRightZfBy 3 -21 --> 1073741818 +-- -21 = 111...10101 +-- shiftRightZfBy 3 = 00111...11101 = 1073741818 +``` diff --git a/exercises/concept/secrets/.docs/introduction.md.tpl b/exercises/concept/secrets/.docs/introduction.md.tpl new file mode 100644 index 00000000..41cf343b --- /dev/null +++ b/exercises/concept/secrets/.docs/introduction.md.tpl @@ -0,0 +1,3 @@ +# Introduction + +%{concept: bitwise-operations} diff --git a/exercises/concept/secrets/.meta/Exemplar.elm b/exercises/concept/secrets/.meta/Exemplar.elm new file mode 100644 index 00000000..e20f47cb --- /dev/null +++ b/exercises/concept/secrets/.meta/Exemplar.elm @@ -0,0 +1,29 @@ +module Secrets exposing (clearBits, decrypt, flipBits, setBits, shiftBack) + +import Bitwise + + +shiftBack amount value = + Bitwise.shiftRightZfBy amount value + + +setBits mask value = + Bitwise.or mask value + + +flipBits mask value = + Bitwise.xor mask value + + +clearBits mask value = + mask + |> Bitwise.complement + |> Bitwise.and value + + +decrypt secret = + secret + |> setBits 1996 + |> flipBits 2009 + |> shiftBack 5 + |> clearBits 17 diff --git a/exercises/concept/secrets/.meta/config.json b/exercises/concept/secrets/.meta/config.json new file mode 100644 index 00000000..42e03920 --- /dev/null +++ b/exercises/concept/secrets/.meta/config.json @@ -0,0 +1,20 @@ +{ + "authors": [ + "stewartmurrie" + ], + "files": { + "solution": [ + "src/Secrets.elm" + ], + "test": [ + "tests/Tests.elm" + ], + "exemplar": [ + ".meta/Exemplar.elm" + ] + }, + "forked_from": [ + "java/secrets" + ], + "blurb": "Learn about bit manipulation by writing the software for an encryption device." +} diff --git a/exercises/concept/secrets/elm.json b/exercises/concept/secrets/elm.json new file mode 100644 index 00000000..22c9e137 --- /dev/null +++ b/exercises/concept/secrets/elm.json @@ -0,0 +1,29 @@ +{ + "type": "application", + "source-directories": [ + "src" + ], + "elm-version": "0.19.1", + "dependencies": { + "direct": { + "elm/core": "1.0.5", + "elm/json": "1.1.3", + "elm/parser": "1.1.0", + "elm/random": "1.0.0", + "elm/regex": "1.0.0", + "elm/time": "1.0.0" + }, + "indirect": {} + }, + "test-dependencies": { + "direct": { + "elm-explorations/test": "2.1.0", + "rtfeldman/elm-iso8601-date-strings": "1.1.4" + }, + "indirect": { + "elm/bytes": "1.0.8", + "elm/html": "1.0.0", + "elm/virtual-dom": "1.0.3" + } + } +} diff --git a/exercises/concept/secrets/src/Secrets.elm b/exercises/concept/secrets/src/Secrets.elm new file mode 100644 index 00000000..46f37243 --- /dev/null +++ b/exercises/concept/secrets/src/Secrets.elm @@ -0,0 +1,21 @@ +module Secrets exposing (clearBits, decrypt, flipBits, setBits, shiftBack) + + +shiftBack amount value = + Debug.todo "Please implement shiftBack" + + +setBits mask value = + Debug.todo "Please implement setBits" + + +flipBits mask value = + Debug.todo "Please implement flipBits" + + +clearBits mask value = + Debug.todo "Please implement clearBits" + + +decrypt secret = + Debug.todo "Please implement decrypt" diff --git a/exercises/concept/secrets/tests/Tests.elm b/exercises/concept/secrets/tests/Tests.elm new file mode 100644 index 00000000..5d5090fd --- /dev/null +++ b/exercises/concept/secrets/tests/Tests.elm @@ -0,0 +1,61 @@ +module Tests exposing (tests) + +import Expect +import Secrets +import Test exposing (Test, describe, test) + + +tests : Test +tests = + describe "Secrets" + [ describe "1" + [ test "Shift 8 right by 2" <| + \_ -> + Secrets.shiftBack 2 8 + |> Expect.equal 2 + , test "Shift -8 right by 2" <| + \_ -> + Secrets.shiftBack 2 -8 + |> Expect.equal 1073741822 + ] + , describe "2" + [ test "Set bits in 5" <| + \_ -> + Secrets.setBits 3 5 + |> Expect.equal 7 + , test "Set bits in 5,652" <| + \_ -> + Secrets.setBits 26150 5652 + |> Expect.equal 30262 + ] + , describe "3" + [ test "Flip bits in 5" <| + \_ -> + Secrets.flipBits 11 5 + |> Expect.equal 14 + , test "Flip bits in 38460" <| + \_ -> + Secrets.flipBits 15471 38460 + |> Expect.equal 43603 + ] + , describe "4" + [ test "Clear bits from 5" <| + \_ -> + Secrets.clearBits 11 5 + |> Expect.equal 4 + , test "Clear bits from 90" <| + \_ -> + Secrets.clearBits 240 90 + |> Expect.equal 10 + ] + , describe "5" + [ test "Decrypt 12345" <| + \_ -> + Secrets.decrypt 12345 + |> Expect.equal 384 + , test "Decrypt 123456789" <| + \_ -> + Secrets.decrypt 123456789 + |> Expect.equal 3857984 + ] + ]