diff --git a/.gitattributes b/.gitattributes index e69de29..4414986 100644 --- a/.gitattributes +++ b/.gitattributes @@ -0,0 +1,2 @@ +jekyll_site/ru/** linguist-language=Java +jekyll_site/en/** linguist-language=Java diff --git a/.gitignore b/.gitignore index c38fa4e..42e9bb7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ .idea *.iml +*.zip +_site* +.repo_*.sh diff --git a/DIRECTORY-TREE.md b/DIRECTORY-TREE.md new file mode 100644 index 0000000..45e61ec --- /dev/null +++ b/DIRECTORY-TREE.md @@ -0,0 +1,57 @@ +## Дерево каталогов + +
+.
+├─ jekyll_site
+│  ├─ _includes
+│  │  ├─ counters_body.html
+│  │  └─ counters_head.html
+│  ├─ en
+│  │  ├─ 2021
+│  │  │  ├─ 09
+│  │  │  │  ├─ 07
+│  │  │  │  │  └─ cartesian-product-of-sets.md
+│  │  │  │  ├─ 10
+│  │  │  │  │  └─ pascals-triangle-in-java.md
+│  │  │  │  ├─ 14
+│  │  │  │  │  └─ combinations-by-columns.md
+│  │  │  │  └─ 22
+│  │  │  │     └─ combinations-of-sequence-elements.md
+│  │  │  └─ 10
+│  │  │     └─ 05
+│  │  │        └─ cartesian-product-parallel-streams.md
+│  │  └─ index.md
+│  ├─ img
+│  │  ├─ arrangement-formula.svg
+│  │  ├─ arrangements-quantity.gif
+│  │  └─ three-elements-sequence.svg
+│  ├─ ru
+│  │  ├─ 2021
+│  │  │  ├─ 09
+│  │  │  │  ├─ 06
+│  │  │  │  │  └─ cartesian-product-of-sets.md
+│  │  │  │  ├─ 09
+│  │  │  │  │  └─ pascals-triangle-in-java.md
+│  │  │  │  ├─ 13
+│  │  │  │  │  └─ combinations-by-columns.md
+│  │  │  │  └─ 20
+│  │  │  │     └─ combinations-of-sequence-elements.md
+│  │  │  └─ 10
+│  │  │     └─ 04
+│  │  │        └─ cartesian-product-parallel-streams.md
+│  │  └─ index.md
+│  ├─ Gemfile_color
+│  ├─ Gemfile_older
+│  ├─ _config_color.yml
+│  ├─ _config_older.yml
+│  └─ robots.txt
+├─ CONTRIBUTING.md
+├─ DIRECTORY-TREE.md
+├─ LICENSE.md
+├─ OPEN_LICENSE.txt
+├─ README.en.md
+├─ README.md
+├─ build.sh
+├─ package.sh
+└─ serve.sh
+
diff --git a/README.en.md b/README.en.md new file mode 100644 index 0000000..aef0be5 --- /dev/null +++ b/README.en.md @@ -0,0 +1,17 @@ +## Website pages + +- [Cartesian product in parallel streams](https://pomodoro2.mircloud.ru/en/2021/10/05/cartesian-product-parallel-streams.html) — 05.10.2021. +- [Combinations of sequence elements](https://pomodoro2.mircloud.ru/en/2021/09/22/combinations-of-sequence-elements.html) — 22.09.2021. +- [Combinations of elements by columns](https://pomodoro2.mircloud.ru/en/2021/09/14/combinations-by-columns.html) — 14.09.2021. +- [Pascal's Triangle in Java](https://pomodoro2.mircloud.ru/en/2021/09/10/pascals-triangle-in-java.html) — 10.09.2021. +- [Cartesian product of sets](https://pomodoro2.mircloud.ru/en/2021/09/07/cartesian-product-of-sets.html) — 07.09.2021. + +## [Source texts](README.md) + +- Series of the static websites [«Pomodori»](https://hub.mos.ru/golovin.gg/pomodoro/blob/master/README.en.md). +- Used formats — Markdown, Liquid, YAML. +- Build tool — Jekyll with tomato design themes. +- Automation of processes — Bash scripts for command line. +- [build.sh](build.sh) — Building a site in two tomato themes and optimizing the results. +- [serve.sh](serve.sh) — Local deployment to verify the correctness of the build. +- [package.sh](package.sh) — Preparing an archive for subsequent deployment. diff --git a/README.md b/README.md new file mode 100644 index 0000000..0efd66f --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +## Страницы вёб-сайта + +- [Декартово произведение в параллельных потоках](https://pomodoro2.mircloud.ru/ru/2021/10/04/cartesian-product-parallel-streams.html) — 04.10.2021. +- [Комбинации элементов последовательности](https://pomodoro2.mircloud.ru/ru/2021/09/20/combinations-of-sequence-elements.html) — 20.09.2021. +- [Комбинации элементов по столбцам](https://pomodoro2.mircloud.ru/ru/2021/09/13/combinations-by-columns.html) — 13.09.2021. +- [Треугольник Паскаля на Java](https://pomodoro2.mircloud.ru/ru/2021/09/09/pascals-triangle-in-java.html) — 09.09.2021. +- [Декартово произведение множеств](https://pomodoro2.mircloud.ru/ru/2021/09/06/cartesian-product-of-sets.html) — 06.09.2021. + +## [Исходные тексты](README.en.md) + +- Серия статических вёб-сайтов [«Помидоры»](https://hub.mos.ru/golovin.gg/pomodoro/blob/master/README.md). +- Используемые форматы — Markdown, Liquid, YAML. +- Инструмент сборки — Jekyll с помидорными темами оформления. +- Автоматизация процессов — Bash скрипты для командной строки. +- [build.sh](build.sh) — Сборка сайта в двух помидорных темах и оптимизация результатов. +- [serve.sh](serve.sh) — Локальное развёртывание для проверки корректности сборки. +- [package.sh](package.sh) — Подготовка архива для последующего развёртывания. diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..265203a --- /dev/null +++ b/build.sh @@ -0,0 +1,51 @@ +#!/bin/bash +echo "Сборка сайта в двух помидорных темах и оптимизация результатов." +milliseconds=$(date '+%s%3N') +rm -rf _site +rm -rf _site_older +rm -rf _site_color +echo "Сборка старого помидора." +mkdir -p _site_older +cp -r jekyll_site/_includes _site_older +cp -r jekyll_site/ru _site_older +cp -r jekyll_site/en _site_older +cp -r jekyll_site/ru/index.md _site_older +cp -r jekyll_site/_config_older.yml _site_older/_config.yml +cp -r jekyll_site/Gemfile_older _site_older/Gemfile +cd _site_older || exit +jekyll build +cp -r _site .. +cd .. +echo "Сборка цветного помидора." +mkdir -p _site_color +cp -r jekyll_site/_includes _site_color +cp -r jekyll_site/ru _site_color +cp -r jekyll_site/en _site_color +cp -r jekyll_site/ru/index.md _site_color +cp -r jekyll_site/_config_color.yml _site_color/_config.yml +cp -r jekyll_site/Gemfile_color _site_color/Gemfile +cd _site_color || exit +jekyll build +cp -r _site ../_site/color +cd .. +echo "Копирование без сборки." +cp -r jekyll_site/img _site +cp -r jekyll_site/robots.txt _site +echo "Оптимизация собранного контента." +cd _site || exit +cp -r assets/* . +rm -r assets +rm -r color/assets/favicon.ico +cp -r color/assets/* . +rm -r color/assets +rm -r color/404.html +find . -type f -name '*.html' | sort -r | while read -r file; do + sed -i 's/layout-padding=""/layout-padding/g' "$file" + sed -i 's/ class="language-plaintext highlighter-rouge"//g' "$file" + sed -i 's/ class="language-java highlighter-rouge"//g' "$file" + sed -i 's/
/
/g' "$file"
+  sed -i 's/<\/code><\/pre><\/div><\/div>/<\/code><\/pre><\/div>/g' "$file"
+  sed -i 's/
/
/g' "$file" + sed -i -r 's///g' "$file" +done +echo "Время выполнения сборки: $(("$(date '+%s%3N')" - "$milliseconds")) мс." diff --git a/jekyll_site/Gemfile_color b/jekyll_site/Gemfile_color new file mode 100644 index 0000000..136ccb7 --- /dev/null +++ b/jekyll_site/Gemfile_color @@ -0,0 +1,3 @@ +source "https://rubygems.org" +gem "jekyll" +gem "color-tomato-theme" diff --git a/jekyll_site/Gemfile_older b/jekyll_site/Gemfile_older new file mode 100644 index 0000000..5e19e6f --- /dev/null +++ b/jekyll_site/Gemfile_older @@ -0,0 +1,3 @@ +source "https://rubygems.org" +gem "jekyll" +gem "older-tomato-theme" diff --git a/jekyll_site/_config_color.yml b/jekyll_site/_config_color.yml new file mode 100644 index 0000000..074fc98 --- /dev/null +++ b/jekyll_site/_config_color.yml @@ -0,0 +1,20 @@ +# site parameters +name: "Код с комментариями" +name_translated: "Code with comments" +url: "https://pomodoro2.mircloud.ru" +baseurl: "/color" +homepage_url: "https://git.org.ru/pomodoro/2" +homepage_name: "GIT.ORG.RU" +older_tomato_baseurl: "" +timezone: "Europe/Moscow" +author: "Головин Г.Г." +author_translated: "Golovin G.G." +translation_caption: "translation from Russian" +# build parameters +disable_disk_cache: true +theme: color-tomato-theme +defaults: + - scope: + path: "" + values: + layout: default diff --git a/jekyll_site/_config_older.yml b/jekyll_site/_config_older.yml new file mode 100644 index 0000000..0e84dd4 --- /dev/null +++ b/jekyll_site/_config_older.yml @@ -0,0 +1,20 @@ +# site parameters +name: "Код с комментариями" +name_translated: "Code with comments" +url: "https://pomodoro2.mircloud.ru" +baseurl: "" +homepage_url: "https://git.org.ru/pomodoro/2" +homepage_name: "GIT.ORG.RU" +color_tomato_baseurl: "/color" +timezone: "Europe/Moscow" +author: "Головин Г.Г." +author_translated: "Golovin G.G." +translation_caption: "translation from Russian" +# build parameters +disable_disk_cache: true +theme: older-tomato-theme +defaults: + - scope: + path: "" + values: + layout: default diff --git a/jekyll_site/_includes/counters_body.html b/jekyll_site/_includes/counters_body.html new file mode 100644 index 0000000..5c9093d --- /dev/null +++ b/jekyll_site/_includes/counters_body.html @@ -0,0 +1,2 @@ + + diff --git a/jekyll_site/_includes/counters_head.html b/jekyll_site/_includes/counters_head.html new file mode 100644 index 0000000..2607f89 --- /dev/null +++ b/jekyll_site/_includes/counters_head.html @@ -0,0 +1,16 @@ + + + + + diff --git a/jekyll_site/en/2021/09/07/cartesian-product-of-sets.md b/jekyll_site/en/2021/09/07/cartesian-product-of-sets.md new file mode 100644 index 0000000..b0f181d --- /dev/null +++ b/jekyll_site/en/2021/09/07/cartesian-product-of-sets.md @@ -0,0 +1,213 @@ +--- +title: Cartesian product of sets +description: Consider an algorithm for obtaining a Cartesian product of multiple sets using three nested loops. The number of sets and their elements can be arbitrary. +sections: [Combinatorics,Direct product] +tags: [java,combinatorics,algorithms,cartesian product,combinations,set,list,array,loops,nested loops] +canonical_url: /en/2021/09/07/cartesian-product-of-sets.html +url_translated: /ru/2021/09/06/cartesian-product-of-sets.html +title_translated: Декартово произведение множеств +date: 2021.09.07 +lang: en +--- + +Consider an algorithm for obtaining a *Cartesian product* of multiple sets using three nested +loops. The number of sets and their elements can be arbitrary. We multiply the sets sequentially +and accumulate the result. The order does not matter, since the product does not change due to the +permutation of the multipliers. As a result, the order will be different, but the combinations will +be the same. + +For clarity, let's take several *ordered* sets: + +``` +a = [A,B,C,D] +b = [E,F,G] +c = [H,I] +d = [J] +``` + +The number of possible combinations is the product of the number of elements of all sets: + +``` +4 * 3 * 2 * 1 = 24 +``` + +Let's write a method in Java to solve such issues, we will use three nested `for` loops. + +First, we prepare a *list of lists* `List>`, filled with one empty value. +We will use it as an intermediate result and as a final result. + +Then we pass through the received lists and sequentially supplement the intermediate +result with their elements. At each step, we get a new intermediate result and move on. +Thus, we consistently multiply pairs of lists and step-by-step accumulate the result. + +Schematically, this process looks like this: + +``` +result0: [[]] + list1: [A,B,C,D] +-------- +result1: [[A],[B],[C],[D]] + list2: [E,F,G] +-------- +result2: [[A,E],[A,F],[A,G],[B,E],[B,F],...[D,G]] + list3: [H,I] +-------- +result3: [[A,E,H],[A,E,I],[A,F,H],[A,F,I],[A,G,H],...[D,G,I]] + list4: [J] +-------- +result4: [[A,E,H,J],[A,E,I,J],[A,F,H,J],[A,F,I,J],[A,G,H,J],...[D,G,I,J]] +``` + +### Combinations by columns {#combinations-by-columns} + +``` +Number of combinations: 24 + [A, E, H, J] [B, E, H, J] [C, E, H, J] [D, E, H, J] + [A, E, I, J] [B, E, I, J] [C, E, I, J] [D, E, I, J] + [A, F, H, J] [B, F, H, J] [C, F, H, J] [D, F, H, J] + [A, F, I, J] [B, F, I, J] [C, F, I, J] [D, F, I, J] + [A, G, H, J] [B, G, H, J] [C, G, H, J] [D, G, H, J] + [A, G, I, J] [B, G, I, J] [C, G, I, J] [D, G, I, J] +``` + +## Cartesian product of lists {#cartesian-product-of-lists} + +The list can contain a *modifiable number* of elements. This simplifies the code. + +```java +/** + * @param lists an arbitrary number of lists + * @param the type of elements in lists + * @return the Cartesian product of the passed lists + */ +public static List> cartesianProduct(List> lists) { + // incoming data is not null + if (lists == null) return Collections.emptyList(); + // intermediate result, contains one empty value + List> cp = Collections.singletonList(Collections.emptyList()); + // pass through the received lists + for (List list : lists) { + // non-null and non-empty lists + if (list == null || list.size() == 0) continue; + // next intermediate result + List> next = new ArrayList<>(); + // rows of the current intermediate result + for (List row : cp) { + // elements of the current list + for (T el : list) { + // a new row for the next intermediate + // result, copy of the current row + List nRow = new ArrayList<>(row); + // add the current element + nRow.add(el); + // add to the next intermediate result + next.add(nRow); + } + } + // update the intermediate result + cp = next; + } + // the final result + return cp; +} +``` + +```java +// start the program and output the result +public static void main(String[] args) { + // an arbitrary number of lists and their elements + List a = Arrays.asList("A", "B", "C", "D"); + List b = Arrays.asList("E", "F", "G"); + List c = Arrays.asList("H", "I"); + List d = Arrays.asList("J"); + // cartesian product + List> cp = cartesianProduct(Arrays.asList(a, b, c, d)); + // output + System.out.println("Number of combinations: " + cp.size()); + // combinations by columns + int rows = 6; + for (int i = 0; i < rows; i++) { + for (int j = 0; j < cp.size(); j++) + if (j % rows == i) + System.out.print(" " + cp.get(j)); + System.out.println(); + } +} +``` + +The output for this and the following code is the same, see above: [combinations by columns](#combinations-by-columns). + +## Cartesian product of arrays {#cartesian-product-of-arrays} + +The array contains a *fixed number* of elements. The code looks a little more complicated. + +```java +/** + * @param arrays an arbitrary number of arrays + * @param clazz the class of elements in arrays + * @param the type of elements in arrays + * @return the Cartesian product of the passed arrays + */ +@SuppressWarnings("unchecked") +public static T[][] cartesianProduct(Class clazz, T[]... arrays) { + // incoming data is not null + if (clazz == null || arrays == null) + return (T[][]) Array.newInstance(clazz, 0, 0); + // intermediate result, contains one empty value + T[][] cp = (T[][]) Array.newInstance(clazz, 1, 0); + // pass through the received arrays + for (int a = 0; a < arrays.length; a++) { + // current array + T[] arr = arrays[a]; + // non-null and non-empty array + if (arr == null || arr.length == 0) continue; + // next intermediate result, specify the number of rows + T[][] next = (T[][]) Array.newInstance(clazz,cp.length*arr.length,0); + // rows of the current intermediate result + for (int r = 0; r < cp.length; r++) { + // current row + T[] row = cp[r]; + // elements of the current array + for (int e = 0; e < arr.length; e++) { + // current element + T el = arr[e]; + // copy the current row into the new array [length + 1] + T[] nRow = Arrays.copyOf(row, row.length + 1); + // add the current element + nRow[row.length] = el; + // add to the next intermediate result + next[r * arr.length + e] = nRow; + } + } + // update the intermediate result + cp = next; + } + // the final result + return cp; +} +``` + +```java +// start the program and output the result +public static void main(String[] args) { + // an arbitrary number of arrays and their elements + String[] a = {"A", "B", "C", "D"}; + String[] b = {"E", "F", "G"}; + String[] c = {"H", "I"}; + String[] d = {"J"}; + // cartesian product + String[][] cp = cartesianProduct(String.class, a, b, c, d); + // output + System.out.println("Number of combinations: " + cp.length); + // combinations by columns + int rows = 6; + for (int i = 0; i < rows; i++) { + for (int j = 0; j < cp.length; j++) + if (j % rows == i) + System.out.print(" " + Arrays.toString(cp[j])); + System.out.println(); + } +} +``` + +The output for this and the previous code is the same, see above: [combinations by columns](#combinations-by-columns). diff --git a/jekyll_site/en/2021/09/10/pascals-triangle-in-java.md b/jekyll_site/en/2021/09/10/pascals-triangle-in-java.md new file mode 100644 index 0000000..02f5bf0 --- /dev/null +++ b/jekyll_site/en/2021/09/10/pascals-triangle-in-java.md @@ -0,0 +1,72 @@ +--- +title: Pascal's Triangle in Java +description: Consider a variant of the implementation of Pascal's triangle in Java. For the simplicity of storing and processing data, we represent the triangle as... +sections: [Two-dimensional array,Binomial coefficients] +tags: [java,algorithms,array,two-dimensional array,loops,nested loops] +canonical_url: /en/2021/09/10/pascals-triangle-in-java.html +url_translated: /ru/2021/09/09/pascals-triangle-in-java.html +title_translated: Треугольник Паскаля на Java +date: 2021.09.10 +lang: en +--- + +Consider a variant of the implementation of *Pascal's triangle* in Java. For the simplicity +of storing and processing data, we represent the triangle as a two-dimensional array, in which +the elements of the first row and column are equal to one, and all other elements are the sum +of the two previous elements in the row and column. + +``` +A[i][j] = A[i][j-1] + A[i-1][j]; +``` + +For example, if {`n = 8`} then the array will look like this: + +``` + 1 1 1 1 1 1 1 1 + 1 2 3 4 5 6 7 + 1 3 6 10 15 21 + 1 4 10 20 35 + 1 5 15 35 + 1 6 21 + 1 7 + 1 +``` + +Create and fill a two-dimensional array with decreasing row length: + +```java +int n = 8; +// array of 'n' rows +int[][] arr = new int[n][]; +// iterate through the rows of the array +for (int i = 0; i < n; i++) { + // row of 'n-i' elements + arr[i] = new int[n - i]; + // iterate through the elements of the row + for (int j = 0; j < n - i; j++) { + if (i == 0 || j == 0) { + // elements of the first row + // and column are equal to one + arr[i][j] = 1; + } else { + // all other elements are the sum of the two + // previous elements in the row and column + arr[i][j] = arr[i][j - 1] + arr[i - 1][j]; + } + } +} +``` + +Output the array row-wise: + +```java +// iterate through the array rows +for (int[] row : arr) { + // iterate through the row elements + for (int el : row) + // space and two-digit number + System.out.printf(" %2d", el); + // line separator + System.out.println(); +} +``` diff --git a/jekyll_site/en/2021/09/14/combinations-by-columns.md b/jekyll_site/en/2021/09/14/combinations-by-columns.md new file mode 100644 index 0000000..11c6ba0 --- /dev/null +++ b/jekyll_site/en/2021/09/14/combinations-by-columns.md @@ -0,0 +1,155 @@ +--- +title: Combinations of elements by columns +description: In a two-dimensional array, data is stored row-wise. Consider an algorithm for obtaining a Cartesian product by columns using three nested loops. The... +sections: [Combinatorics,Direct product] +tags: [java,combinatorics,algorithms,cartesian product,combinations,columns,set,list,array,loops,nested loops] +canonical_url: /en/2021/09/14/combinations-by-columns.html +url_translated: /ru/2021/09/13/combinations-by-columns.html +title_translated: Комбинации элементов по столбцам +date: 2021.09.14 +lang: en +--- + +In a two-dimensional array, data is stored row-wise. Consider an algorithm for obtaining +a *Cartesian product* by columns using three nested loops. The number of rows and columns +of the table can be arbitrary. We multiply the columns sequentially and accumulate the +result. The values do not necessarily have to be populated — we discard the null elements. + +For example, let's take a partially filled *jagged two-dimensional array*: + +```java + | col1 | col2 | col3 | col4 +-----|------|------|------|------ +row1 | "A1" | "B1" | "C1" | "D1" +row2 | "A2" | "B2" | null | "D2" +row3 | "A3" | null | null | "D3" +row4 | "A4" | null | +``` + +The number of possible combinations is the product of the number of elements of all columns: + +``` +4 * 2 * 1 * 3 = 24 +``` + +Let's write a method in Java to solve such issues, we will use three nested `for` loops. + +First, we prepare a *list of lists* `List>`, filled with one empty value. We will +use it as an intermediate result and as a final result. + +Then we pass through the table columns, while they are present, — we consider the last +column is the one after which all the elements are null. At each step, we supplement the +intermediate result with the elements of the current column and get a new intermediate +result. Thus, we consistently multiply columns and step-by-step accumulate the result. + +Schematically, this process looks like this: + +``` +res0 * col1 = res1 * col2 = res2 * col3 = res3 * col4 = res4 +-----|------|------|------|-------|------|----------|------|------------ +[] * A1 = A1 * B1 = A1,B1 * C1 = A1,B1,C1 * D1 = A1,B1,C1,D1 + * A2 = A2 * B2 = A1,B2 * = A1,B2,C1 * D2 = A1,B1,C1,D2 + * A3 = A3 * = A2,B1 * = A2,B1,C1 * D3 = A1,B1,C1,D3 + * A4 = A4 * = A2,B2 * = A2,B2,C1 * = A1,B2,C1,D1 + * = * = A3,B1 * = A3,B1,C1 * = A1,B2,C1,D2 + * = * = A3,B2 * = A3,B2,C1 * = A1,B2,C1,D3 + * = * = A4,B1 * = A4,B1,C1 * = A2,B1,C1,D1 + * = * = A4,B2 * = A4,B2,C1 * = A2,B1,C1,D2 + * = * = * = * = A2,B1,C1,D3 + * = * = * = * = A2,B2,C1,D1 + * = * = * = * = ........... + * = * = * = * = ........... + * = * = * = * = A4,B2,C1,D3 +-----|------|------|------|-------|------|----------|------|------------ +1 * 4 = 4 * 2 = 8 * 1 = 8 * 3 = 24 +``` + +### Combinations by columns {#combinations-by-columns} + +``` +Number of combinations: 24 + [A1, B1, C1, D1] [A2, B1, C1, D1] [A3, B1, C1, D1] [A4, B1, C1, D1] + [A1, B1, C1, D2] [A2, B1, C1, D2] [A3, B1, C1, D2] [A4, B1, C1, D2] + [A1, B1, C1, D3] [A2, B1, C1, D3] [A3, B1, C1, D3] [A4, B1, C1, D3] + [A1, B2, C1, D1] [A2, B2, C1, D1] [A3, B2, C1, D1] [A4, B2, C1, D1] + [A1, B2, C1, D2] [A2, B2, C1, D2] [A3, B2, C1, D2] [A4, B2, C1, D2] + [A1, B2, C1, D3] [A2, B2, C1, D3] [A3, B2, C1, D3] [A4, B2, C1, D3] +``` + +## Cartesian product by columns {#cartesian-product-by-columns} + +The code will look simpler if you first *transpose* an array of arrays, but if +this cannot be done, then in the outer loop, pass through the table columns as +long as they are still exist. + +```java +/** + * @param table the two-dimensional list + * @param the type of elements in list + * @return the Cartesian product of the elements by columns + */ +public static List> cartesianProduct(List> table) { + // incoming data is not null + if (table == null) return Collections.emptyList(); + // intermediate result, contains one empty value + List> cp = Collections.singletonList(Collections.emptyList()); + // columns are still present + boolean notLast = true; + // pass through the table columns, while they are present + for (int i = 0; notLast; i++) { + // there are no more columns + notLast = false; + // next intermediate result + List> next = new ArrayList<>(); + // pass through the combinations of the current intermediate result + for (List comb : cp) { + // pass through the rows of the table + for (List row : table) { + // if the column is still present and its value is also present + if (row != null && i < row.size() && row.get(i) != null) { + // a new combination, copy the current combination + List nComb = new ArrayList<>(comb); + // add the value from the column + nComb.add(row.get(i)); + // add to the next intermediate result + next.add(nComb); + // columns are still present + notLast = true; + } + } + } + // if the columns are still present, then we update the intermediate + // result and go to the next iteration, otherwise we exit the loop + if (notLast) cp = next; + } + // the final result + return cp; +} +``` + +```java +// start the program and output the result +public static void main(String[] args) { + // an arbitrary number of rows and their elements + List row1 = Arrays.asList("A1", "B1", "C1", "D1"); + List row2 = Arrays.asList("A2", "B2", null, "D2"); + List row3 = Arrays.asList("A3", null, null, "D3"); + List row4 = Arrays.asList("A4", null); + // jagged two-dimensional list + List> table = Arrays.asList(row1, row2, row3, row4); + // cartesian product + List> cp = cartesianProduct(table); + // output + System.out.println("Number of combinations: " + cp.size()); + // combinations by columns + int rows = 6; + for (int i = 0; i < rows; i++) { + for (int j = 0; j < cp.size(); j++) + if (j % rows == i) + System.out.print(" " + cp.get(j)); + System.out.println(); + } +} +``` + +The output for this code see above: [combinations by columns](#combinations-by-columns). diff --git a/jekyll_site/en/2021/09/22/combinations-of-sequence-elements.md b/jekyll_site/en/2021/09/22/combinations-of-sequence-elements.md new file mode 100644 index 0000000..ef86f5b --- /dev/null +++ b/jekyll_site/en/2021/09/22/combinations-of-sequence-elements.md @@ -0,0 +1,240 @@ +--- +title: Combinations of sequence elements +description: Consider a problem where you need to get all possible combinations of sequence elements, in which the number of elements in the combination does not exceed... +sections: [Combinatorics,Arrangements,Permutations] +tags: [java,combinatorics,algorithms,arrangement,permutation,combinations,sequence,set,list,loops,nested loops] +canonical_url: /en/2021/09/22/combinations-of-sequence-elements.html +url_translated: /ru/2021/09/20/combinations-of-sequence-elements.html +title_translated: Комбинации элементов последовательности +date: 2021.09.22 +lang: en +--- + +Consider a problem where you need to get all possible combinations of sequence elements, in which +the number of elements in the combination does not exceed a given maximum, and let's write a method +in Java with the appropriate filter for the minimum and maximum number of elements. + +*[Table setting problem](#table-setting-problem) • [Method for solving in Java](#combinations-of-length)* + +An *arrangement* is an ordered set of {`k`} distinct elements from a set of distinct {`n`} elements, +where {k ≤ n}. If {k = n}, then this ordered set +is a *permutation*. For the universality of the solution, we'll also consider *permutations* as +a special case of *arrangement*. + +## Number of possible combinations {#number-of-possible-combinations} + +For clarity, we'll take a sequence of three elements {`XYZ`}, draw all possible subsets +of this set, add permutations to them and calculate the number of combinations. + +{% include picture.html src="/img/arrangements-quantity.gif" size100=true background=true +alt="We get the number of all possible arrangements of the sequence elements" +caption="We get the number of all possible arrangements of the sequence elements" %} + +Formula for the number of arrangements: + +{% include image_svg.html src="/img/arrangement-formula.svg" style="width: 83pt; height: 46pt;" +alt="\sum_{k=0}^{n} {n! \over (n-k)!}" %} + +Sequence of three elements: + +{% include image_svg.html src="/img/three-elements-sequence.svg" style="width: 375pt; height: 48pt;" +alt="\sum_{k=0}^{3} {3! \over (3-k)!} = {3! \over 3!} + {3! \over 2!} + {3! \over 1!} + {3! \over 0!} = 1+3+6+6 = 16." %} + +Implementation in Java: + +```java +public static void main(String[] args) { + // number of sequence elements + int n = 3; + // number of possible combinations + int sum = 0; + // bypass the possible numbers of elements + for (int k = 0; k <= n; k++) + // add up the numbers of arrangements + sum += factorial(n) / factorial(n - k); + // output + System.out.println(sum); // 16 +} +// factorial of a number +static int factorial(int n) { + int fact = 1; + for (int i = 2; i <= n; i++) + fact *= i; + return fact; +} +``` + +## Combinations of three elements {#combinations-of-elements} + +Let's compare two sequences of three elements: digits {`123`} and letters {`XYZ`}. +The type of elements is different — combinations are the same, because the sequence +numbers of the elements are the same. + +``` +number of variants: 16 +[][1][2][3][12][13][21][23][31][32][123][132][213][231][312][321] +[][X][Y][Z][XY][XZ][YX][YZ][ZX][ZY][XYZ][XZY][YXZ][YZX][ZXY][ZYX] +``` + +Let's compare sequences of [`1..12`] elements: the number of variants grows rapidly and +soon goes beyond the `Integer`, then for {n > 12} you will need +to use `Long`, and when {n > 20} — already `BigInteger`. + +``` +n number of variants +1 2 +2 5 +3 16 +4 65 +5 326 +6 1957 +7 13700 +8 109601 +9 986410 +10 9864101 +11 108505112 +12 1302061345 +``` + +Consider a problem where you need to limit the possible variants +by the maximum number of elements contained in them. + +## Table setting problem {#table-setting-problem} + +Four guests are invited to dinner {n = 4}, of whom it is known +that no more than two people will arrive {k = 2}, and the order +of their appearance is important, since the table setting depends on it. It is also +known that when the first one arrives, this person will say who will be the second and +whether this person will arrive. Calculate the possible variants for table setting. + +### Solution approach {#solution-approach} + +Let's write a method in Java for solving such problems, which will take three +parameters as input: sequence size, minimum and maximum number of elements in +a combination. The method will return a list of combinations of specified length. + +```java +static List> combinationsOfElements(int size, int min, int max) +``` + +We call the method with the selection {min=0; max=2} +and get a list of combinations of specified length. + +```java +// table setting problem +public static void main(String[] args) { + // four guests are invited + int[] arr = {1, 2, 3, 4}; + // n - number of elements in a sequence + // k - maximum number of elements in a combination + int n = 4, k = 2; + // combinations of elements of specified length [0..2] + List> list = combinationsOfElements(n, 0, k); + // output + System.out.println("number of variants: " + list.size()); + for (Set set : list) { + System.out.print("["); + for (int i : set) + System.out.print(arr[i]); + System.out.print("]"); + } +} +``` + +Output: + +``` +number of variants: 17 +[][1][2][3][4][12][13][14][21][23][24][31][32][34][41][42][43] +``` + +## Combinations of elements of specified length {#combinations-of-length} + +We write a method in Java using three nested `for` loops. Next, to check, we +call this method without selection {min=0; max=size} and get +all possible combinations. For example, let's take two sequences of three +elements: digits {`123`} and letters {`XYZ`}. + +### Method description {#method-description} + +We prepare two lists of combinations: the resulting list and the current list. +In the current list, the number of elements in all combinations will be the same. +The maximum number of elements in a combination is the size of the sequence. +We start with one empty combination and gradually increase the number of elements. + +We bypass the possible number of elements and supplement the current combinations +with those indices that are not yet included. At each step, we increase the current +number of elements in the combinations by one and, if it gets into selection, then +we add these combinations to the result. + +```java +/** + * @param size the sequence size (0 ≤ min ≤ max ≤ size) + * @param min minimum number of elements in a combination + * @param max maximum number of elements in a combination + * @return combinations of elements of specified length + */ +static List> combinationsOfElements(int size, int min, int max) { + // invalid incoming data, return an empty list of combinations + if (0 > min || min > max || max > size) return Collections.emptyList(); + // resulting list of combinations + List> result = new ArrayList<>(); + // current list of combinations, number of elements in all + // combinations is the same, start with one empty combination + List> sublist = Arrays.asList(Collections.emptySet()); + // bypass the possible number of elements in a combination + for (int l = 0; l < Math.min(size, max); l++) { + // if current number of elements gets into selection, + // then add these combinations to the result + if (l >= min) result.addAll(sublist); + // next list of combinations + List> nSublist = new ArrayList<>(); + // bypass the current list of combinations + for (Set comb : sublist) { + // bypass the indexes of the sequence elements + for (int i = 0; i < size; i++) { + // skip those indexes that already present + if (comb.contains(i)) continue; + // new combination, copy the current one + Set nComb = new LinkedHashSet<>(comb); + // adding the current index + nComb.add(i); + // add in a new list of combinations + nSublist.add(nComb); + } + } + // update the current list of combinations + sublist = nSublist; + } + // add the current list of combinations to the result + result.addAll(sublist); + // return the final result + return result; +} +``` + +```java +// start the program and output the result +public static void main(String[] args) { + // two sequences of three elements + Integer[] arr1 = {1, 2, 3}; + String[] arr2 = {"X", "Y", "Z"}; + // number of sequence elements + int n = 3; + // all possible combinations of elements [0..n] + List> list = combinationsOfElements(n, 0, n); + // output + System.out.println("number of variants: " + list.size()); + for (Object[] arr : Arrays.asList(arr1, arr2)) { + StringBuilder sb = new StringBuilder(); + for (Set set : list) { + sb.append("["); + for (int i : set) sb.append(arr[i]); + sb.append("]"); + } + System.out.println(sb); + } +} +``` + +The output for this code see above: [combinations of three elements](#combinations-of-elements). diff --git a/jekyll_site/en/2021/10/05/cartesian-product-parallel-streams.md b/jekyll_site/en/2021/10/05/cartesian-product-parallel-streams.md new file mode 100644 index 0000000..e982e76 --- /dev/null +++ b/jekyll_site/en/2021/10/05/cartesian-product-parallel-streams.md @@ -0,0 +1,173 @@ +--- +title: Cartesian product in parallel streams +description: Consider an algorithm for obtaining a Cartesian product using Java Streams. This solution is similar to three nested for loops, with the difference that... +sections: [Multithreading,Combinatorics,Direct product] +tags: [java,multithreading,combinatorics,algorithms,cartesian product] +canonical_url: /en/2021/10/05/cartesian-product-parallel-streams.html +url_translated: /ru/2021/10/04/cartesian-product-parallel-streams.html +title_translated: Декартово произведение в параллельных потоках +date: 2021.10.05 +lang: en +--- + +Consider an algorithm for obtaining a *Cartesian product* using Java Streams. This solution is similar +to three nested `for` loops, with the difference that the outer loop is replaced with a stream for the +convenience of subsequent parallelization. We’ll use the `reduce` method with three parameters: +`identity`, `accumulator` and `combiner`. + +*Algorithm with three nested loops: +[Cartesian product of sets]({{ '/en/2021/09/07/cartesian-product-of-sets.html' | relative_url }}).* + +The incoming data is an arbitrary number of lists and their elements. + +``` +a = [A1,B1,C1,D1] +b = [A2,B2,C2] +c = [A3,B3] +d = [A4] +``` + +The number of possible combinations is the product of the number of elements in all lists: + +``` +4 * 3 * 2 * 1 = 24 +``` + +We get a *stream of lists* `Stream>` from the input data and call the `reduce` method: + +{% capture md_list_into_div %} +- `identity` — in our case it is a *list of lists* `List>`, filled with +one empty value. We will use it as an intermediate result and as a final result. + +- `accumulator` — for each step of *reduction*, we define a method with two parameters: +intermediate result and current list. We supplement the intermediate result with elements +from the current list. + +- `combiner` — is used in parallel mode, combines the results of the work of streams, +obtains the *Cartesian product* of the results. +{% endcapture %} +
+{{- md_list_into_div | markdownify -}} +
+ +The step-by-step process of accumulating a *Cartesian product* looks like this: + +``` +result0: [[]] + list1: [A1,B1,C1,D1] +-------- +result1: [[A1],[B1],[C1],[D1]] + list2: [A2,B2,C2] +-------- +result2: [[A1,A2],[A1,B2],[A1,C2],[B1,A2],[B1,B2],...[D1,C2]] + list3: [A3,B3] +-------- +result3: [[A1,A2,A3],[A1,A2,B3],[A1,B2,A3],[A1,B2,B3],[A1,C2,A3],...[D1,C2,B3]] + list4: [A4] +-------- +result4: [[A1,A2,A3,A4],[A1,A2,B3,A4],[A1,B2,A3,A4],[A1,B2,B3,A4],...[D1,C2,B3,A4]] +``` + +### Combinations by columns {#combinations-by-columns} + +``` +Number of combinations: 24 +[A1, A2, A3, A4] [B1, A2, A3, A4] [C1, A2, A3, A4] [D1, A2, A3, A4] +[A1, A2, B3, A4] [B1, A2, B3, A4] [C1, A2, B3, A4] [D1, A2, B3, A4] +[A1, B2, A3, A4] [B1, B2, A3, A4] [C1, B2, A3, A4] [D1, B2, A3, A4] +[A1, B2, B3, A4] [B1, B2, B3, A4] [C1, B2, B3, A4] [D1, B2, B3, A4] +[A1, C2, A3, A4] [B1, C2, A3, A4] [C1, C2, A3, A4] [D1, C2, A3, A4] +[A1, C2, B3, A4] [B1, C2, B3, A4] [C1, C2, B3, A4] [D1, C2, B3, A4] +``` + +## Combinations of elements in parallel streams {#combinations-of-elements-in-parallel-streams} + +In parallel mode, the speed of the algorithm increases when multiplying a *large number of small lists*, +for example, 20 lists of 2 elements or 15 lists of 3 elements. The computation time reduces by *one and +a half to two* times. In other cases, the computation time is about the same as for three nested `for` +loops. + +```java +/** + * @param lists an arbitrary number of lists + * @param the type of elements in lists + * @return the Cartesian product of the passed lists + */ +public static List> cartesianProduct(List> lists) { + // incoming data is not null + if (lists == null) return Collections.emptyList(); + // stream of lists Stream> + return lists.stream() + // enable parallel mode + .parallel() + // discard null and empty lists + .filter(list -> list != null && list.size() > 0) + // reduce the stream of lists into one list, get a Cartesian product + // reduce(identity, accumulator, combiner) + .reduce( // intermediate result, contains one empty value + Collections.singletonList(Collections.emptyList()), + // bypass the received lists and supplement the intermediate result + // with their elements, at each step obtain a new intermediate result + (result, list) -> { + // next intermediate result + List> nResult = new ArrayList<>(result.size() * list.size()); + // rows of the current intermediate result + for (List row : result) { + // elements of the current list + for (T el : list) { + // a new row for the next intermediate result + List nRow = new ArrayList<>(row.size() + 1); + // add the current row + nRow.addAll(row); + // add the current element + nRow.add(el); + // add to the next intermediate result + nResult.add(nRow); + } + } + // pass to the next iteration + return nResult; + }, + // is used in parallel mode, combines the results of the work + // of streams, obtains the Cartesian product of the results + (result1, result2) -> { + // combined result + List> result = new ArrayList<>(result1.size() * result2.size()); + // bypass the results + for (List comb1 : result1) { + for (List comb2 : result2) { + // add up the combinations + List comb = new ArrayList<>(comb1.size() + comb2.size()); + comb.addAll(comb1); + comb.addAll(comb2); + result.add(comb); + } + } + return result; + }); +} +``` + +```java +// start the program and output the result +public static void main(String[] args) { + // an arbitrary number of lists and their elements + List a = Arrays.asList("A1", "B1", "C1", "D1"); + List b = Arrays.asList("A2", "B2", "C2"); + List c = Arrays.asList("A3", "B3"); + List d = Collections.singletonList("A4"); + // cartesian product + List> cp = cartesianProduct(Arrays.asList(a, b, c, d)); + // output + System.out.println("Number of combinations: " + cp.size()); + // combinations by columns + int rows = 6; + IntStream.range(0, rows).forEach(i -> System.out.println( + IntStream.range(0, cp.size()) + .filter(j -> j % rows == i) + .mapToObj(j -> cp.get(j).toString()) + .collect(Collectors.joining(" ")))); +} +``` + +The output for this code see above: [combinations by columns](#combinations-by-columns). diff --git a/jekyll_site/en/index.md b/jekyll_site/en/index.md new file mode 100644 index 0000000..745c0a2 --- /dev/null +++ b/jekyll_site/en/index.md @@ -0,0 +1,63 @@ +--- +title: Code with comments +description: Notes about programming with code snippets and comments. Problem solutions and solution descriptions. +sections: [Problem solutions and solution descriptions] +tags: [java,combinatorics,algorithms,implementation,sets,subsets,lists,arrays,loops,nested loops] +canonical_url: /en/ +url_translated: /ru/ +title_translated: Код с комментариями +lang: en +--- + +{%- assign articles = "" | split: "" %} +{%- assign articles = articles | push: "Cartesian product in parallel streams" %} +{%- capture article_brief %} +Consider an algorithm for obtaining a *Cartesian product* using Java Streams. This solution is similar +to three nested `for` loops, with the difference that the outer loop is replaced with a stream for the +convenience of subsequent parallelization. We’ll use the `reduce` method with three parameters: +`identity`, `accumulator` and `combiner`. + +In parallel mode, the speed of the algorithm increases when multiplying a *large number of small lists*, +for example, 20 lists of 2 elements or 15 lists of 3 elements. The computation time reduces by *one and +a half to two* times. In other cases, the computation time is about the same as for three nested `for` +loops. +{%- endcapture %} +{%- assign articles = articles | push: article_brief %} +{%- assign articles = articles | push: "Combinations of sequence elements" %} +{%- capture article_brief %} +Consider a problem where you need to get all possible combinations of sequence elements, in which +the number of elements in the combination does not exceed a given maximum, and let's write a method +in Java with the appropriate filter for the minimum and maximum number of elements. + +An *arrangement* is an ordered set of {`k`} distinct elements from a set of distinct {`n`} elements, +where {k ≤ n}. If {k = n}, then this ordered set +is a *permutation*. For the universality of the solution, we'll also consider *permutations* as +a special case of *arrangement*. +{%- endcapture %} +{%- assign articles = articles | push: article_brief %} +{%- assign articles = articles | push: "Combinations of elements by columns" %} +{%- capture article_brief %} +In a two-dimensional array, data is stored row-wise. Consider an algorithm for obtaining +a *Cartesian product* by columns using three nested loops. The number of rows and columns +of the table can be arbitrary. We multiply the columns sequentially and accumulate the +result. The values do not necessarily have to be populated — we discard the null elements. +{%- endcapture %} +{%- assign articles = articles | push: article_brief %} +{%- assign articles = articles | push: "Pascal's Triangle in Java" %} +{%- capture article_brief %} +Consider a variant of the implementation of *Pascal's triangle* in Java. For the simplicity +of storing and processing data, we represent the triangle as a two-dimensional array, in which +the elements of the first row and column are equal to one, and all other elements are the sum +of the two previous elements in the row and column. +{%- endcapture %} +{%- assign articles = articles | push: article_brief %} +{%- assign articles = articles | push: "Cartesian product of sets" %} +{%- capture article_brief %} +Consider an algorithm for obtaining a *Cartesian product* of multiple sets using three nested +loops. The number of sets and their elements can be arbitrary. We multiply the sets sequentially +and accumulate the result. The order does not matter, since the product does not change due to the +permutation of the multipliers. As a result, the order will be different, but the combinations will +be the same. +{%- endcapture %} +{%- assign articles = articles | push: article_brief %} +{%- include main_page.html articles = articles -%} diff --git a/jekyll_site/img/arrangement-formula.svg b/jekyll_site/img/arrangement-formula.svg new file mode 100644 index 0000000..2320929 --- /dev/null +++ b/jekyll_site/img/arrangement-formula.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jekyll_site/img/arrangements-quantity.gif b/jekyll_site/img/arrangements-quantity.gif new file mode 100644 index 0000000..96de878 Binary files /dev/null and b/jekyll_site/img/arrangements-quantity.gif differ diff --git a/jekyll_site/img/three-elements-sequence.svg b/jekyll_site/img/three-elements-sequence.svg new file mode 100644 index 0000000..704a566 --- /dev/null +++ b/jekyll_site/img/three-elements-sequence.svg @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jekyll_site/robots.txt b/jekyll_site/robots.txt new file mode 100644 index 0000000..062a3cc --- /dev/null +++ b/jekyll_site/robots.txt @@ -0,0 +1,7 @@ +User-agent: * +Disallow: *404* + +Sitemap: https://pomodoro2.mircloud.ru/pagesmap.xml +Sitemap: https://pomodoro2.mircloud.ru/color/pagesmap.xml + +Host: https://pomodoro2.mircloud.ru diff --git a/jekyll_site/ru/2021/09/06/cartesian-product-of-sets.md b/jekyll_site/ru/2021/09/06/cartesian-product-of-sets.md new file mode 100644 index 0000000..751e595 --- /dev/null +++ b/jekyll_site/ru/2021/09/06/cartesian-product-of-sets.md @@ -0,0 +1,212 @@ +--- +title: Декартово произведение множеств +description: Рассмотрим алгоритм получения декартова произведения нескольких множеств с использованием трёх вложенных циклов. Количество множеств и их элементов может... +sections: [Комбинаторика,Прямое произведение] +tags: [java,комбинаторика,алгоритмы,декартово произведение,комбинации,множество,список,массив,циклы,вложенные циклы] +canonical_url: /ru/2021/09/06/cartesian-product-of-sets.html +url_translated: /en/2021/09/07/cartesian-product-of-sets.html +title_translated: Cartesian product of sets +date: 2021.09.06 +--- + +Рассмотрим алгоритм получения *декартова произведения* нескольких множеств с использованием +трёх вложенных циклов. Количество множеств и их элементов может быть произвольным. Последовательно +перемножаем множества и накапливаем результат. Порядок значения не имеет, так как от перестановки +множителей произведение не меняется. В результате порядок будет отличаться, но комбинации будут +те же самые. + +Для наглядности возьмём несколько *упорядоченных* множеств: + +``` +a = [A,B,C,D] +b = [E,F,G] +c = [H,I] +d = [J] +``` + +Количество возможных комбинаций — это произведение количеств элементов всех множеств: + +``` +4 * 3 * 2 * 1 = 24 +``` + +Напишем метод на Java для решения подобных задач, будем использовать три вложенных цикла `for`. + +Сначала подготавливаем *список списков* `List>`, заполненный одним пустым значением. +Будем использовать его как промежуточный результат и как финальный результат. + +Затем обходим переданные списки и последовательно дополняем промежуточный результат +их элементами. На каждом шаге получаем новый промежуточный результат и двигаемся дальше. +Таким образом последовательно перемножаем пары списков и постепенно накапливаем результат. + +Схематически этот процесс выглядит следующим образом: + +``` +result0: [[]] + list1: [A,B,C,D] +-------- +result1: [[A],[B],[C],[D]] + list2: [E,F,G] +-------- +result2: [[A,E],[A,F],[A,G],[B,E],[B,F],...[D,G]] + list3: [H,I] +-------- +result3: [[A,E,H],[A,E,I],[A,F,H],[A,F,I],[A,G,H],...[D,G,I]] + list4: [J] +-------- +result4: [[A,E,H,J],[A,E,I,J],[A,F,H,J],[A,F,I,J],[A,G,H,J],...[D,G,I,J]] +``` + +### Комбинации по столбцам {#combinations-by-columns} + +``` +Количество комбинаций: 24 + [A, E, H, J] [B, E, H, J] [C, E, H, J] [D, E, H, J] + [A, E, I, J] [B, E, I, J] [C, E, I, J] [D, E, I, J] + [A, F, H, J] [B, F, H, J] [C, F, H, J] [D, F, H, J] + [A, F, I, J] [B, F, I, J] [C, F, I, J] [D, F, I, J] + [A, G, H, J] [B, G, H, J] [C, G, H, J] [D, G, H, J] + [A, G, I, J] [B, G, I, J] [C, G, I, J] [D, G, I, J] +``` + +## Декартово произведение списков {#cartesian-product-of-lists} + +Список может содержать *изменяемое количество* элементов. Это упрощает код. + +```java +/** + * @param lists произвольное количество списков + * @param тип элементов списков + * @return декартово произведение переданных списков + */ +public static List> cartesianProduct(List> lists) { + // входящие данные не равны null + if (lists == null) return Collections.emptyList(); + // промежуточный результат, содержит одно пустое значение + List> cp = Collections.singletonList(Collections.emptyList()); + // обходим переданные списки + for (List list : lists) { + // ненулевые и непустые списки + if (list == null || list.size() == 0) continue; + // следующий промежуточный результат + List> next = new ArrayList<>(); + // строки текущего промежуточного результата + for (List row : cp) { + // элементы текущего списка + for (T el : list) { + // новая строка для следующего промежуточного + // результата, копируем текущую строку + List nRow = new ArrayList<>(row); + // добавляем текущий элемент + nRow.add(el); + // помещаем в следующий промежуточный результат + next.add(nRow); + } + } + // обновляем промежуточный результат + cp = next; + } + // возвращаем итоговый результат + return cp; +} +``` + +```java +// запускаем программу и выводим результат +public static void main(String[] args) { + // произвольное количество списков и их элементов + List a = Arrays.asList("A", "B", "C", "D"); + List b = Arrays.asList("E", "F", "G"); + List c = Arrays.asList("H", "I"); + List d = Arrays.asList("J"); + // декартово произведение + List> cp = cartesianProduct(Arrays.asList(a, b, c, d)); + // вывод + System.out.println("Количество комбинаций: " + cp.size()); + // комбинации по столбцам + int rows = 6; + for (int i = 0; i < rows; i++) { + for (int j = 0; j < cp.size(); j++) + if (j % rows == i) + System.out.print(" " + cp.get(j)); + System.out.println(); + } +} +``` + +Вывод для этого и следующего кода одинаковый, см. выше: [комбинации по столбцам](#combinations-by-columns). + +## Декартово произведение массивов {#cartesian-product-of-arrays} + +Массив содержит *фиксированное количество* элементов. Код выглядит немного сложнее. + +```java +/** + * @param arrays произвольное количество массивов + * @param clazz класс элементов массивов + * @param тип элементов массивов + * @return декартово произведение переданных массивов + */ +@SuppressWarnings("unchecked") +public static T[][] cartesianProduct(Class clazz, T[]... arrays) { + // входящие данные не равны null + if (clazz == null || arrays == null) + return (T[][]) Array.newInstance(clazz, 0, 0); + // промежуточный результат, содержит одну пустую строку + T[][] cp = (T[][]) Array.newInstance(clazz, 1, 0); + // обходим переданные массивы + for (int a = 0; a < arrays.length; a++) { + // текущий массив + T[] arr = arrays[a]; + // ненулевой и непустой массив + if (arr == null || arr.length == 0) continue; + // следующий промежуточный результат, указываем количество строк + T[][] next = (T[][]) Array.newInstance(clazz,cp.length*arr.length,0); + // строки текущего промежуточного результата + for (int r = 0; r < cp.length; r++) { + // текущая строка + T[] row = cp[r]; + // элементы текущего массива + for (int e = 0; e < arr.length; e++) { + // текущий элемент + T el = arr[e]; + // копируем текущую строку в новый массив [длина + 1] + T[] nRow = Arrays.copyOf(row, row.length + 1); + // добавляем текущий элемент + nRow[row.length] = el; + // помещаем в следующий промежуточный результат + next[r * arr.length + e] = nRow; + } + } + // обновляем промежуточный результат + cp = next; + } + // возвращаем итоговый результат + return cp; +} +``` + +```java +// запускаем программу и выводим результат +public static void main(String[] args) { + // произвольное количество массивов и их элементов + String[] a = {"A", "B", "C", "D"}; + String[] b = {"E", "F", "G"}; + String[] c = {"H", "I"}; + String[] d = {"J"}; + // декартово произведение + String[][] cp = cartesianProduct(String.class, a, b, c, d); + // вывод + System.out.println("Количество комбинаций: " + cp.length); + // комбинации по столбцам + int rows = 6; + for (int i = 0; i < rows; i++) { + for (int j = 0; j < cp.length; j++) + if (j % rows == i) + System.out.print(" " + Arrays.toString(cp[j])); + System.out.println(); + } +} +``` + +Вывод для этого и предыдущего кода одинаковый, см. выше: [комбинации по столбцам](#combinations-by-columns). diff --git a/jekyll_site/ru/2021/09/09/pascals-triangle-in-java.md b/jekyll_site/ru/2021/09/09/pascals-triangle-in-java.md new file mode 100644 index 0000000..caf5b2c --- /dev/null +++ b/jekyll_site/ru/2021/09/09/pascals-triangle-in-java.md @@ -0,0 +1,70 @@ +--- +title: Треугольник Паскаля на Java +description: Рассмотрим вариант реализации треугольника Паскаля на Java. Для простоты хранения и обработки данных представим треугольник в виде двухмерного массива... +sections: [Двухмерный массив,Биномиальные коэффициенты] +tags: [java,алгоритмы,массив,двухмерный массив,циклы,вложенные циклы] +canonical_url: /ru/2021/09/09/pascals-triangle-in-java.html +url_translated: /en/2021/09/10/pascals-triangle-in-java.html +title_translated: Pascal's Triangle in Java +date: 2021.09.09 +--- + +Рассмотрим вариант реализации *треугольника Паскаля* на Java. Для простоты хранения и обработки +данных представим треугольник в виде двухмерного массива, в котором элементы первой строки и колонки +равны единице, а все остальные элементы — есть сумма двух предыдущих элементов в строке и в колонке. + +``` +A[i][j] = A[i][j-1] + A[i-1][j]; +``` + +Например, при {`n = 8`} массив будет выглядеть следующим образом: + +``` + 1 1 1 1 1 1 1 1 + 1 2 3 4 5 6 7 + 1 3 6 10 15 21 + 1 4 10 20 35 + 1 5 15 35 + 1 6 21 + 1 7 + 1 +``` + +Создаём и заполняем двухмерный массив с убывающей длиной строки: + +```java +int n = 8; +// массив из 'n' строк +int[][] arr = new int[n][]; +// обходим строки массива +for (int i = 0; i < n; i++) { + // строка из 'n-i' элементов + arr[i] = new int[n - i]; + // обходим элементы строки + for (int j = 0; j < n - i; j++) { + if (i == 0 || j == 0) { + // элементы первой строки + // и колонки равны единице + arr[i][j] = 1; + } else { + // все остальные элементы — есть сумма двух + // предыдущих элементов в строке и в колонке + arr[i][j] = arr[i][j - 1] + arr[i - 1][j]; + } + } +} +``` + +Выводим массив построчно: + +```java +// обходим строки массива +for (int[] row : arr) { + // обходим элементы строки + for (int el : row) + // пробел и двузначное число + System.out.printf(" %2d", el); + // переход на новую строку + System.out.println(); +} +``` diff --git a/jekyll_site/ru/2021/09/13/combinations-by-columns.md b/jekyll_site/ru/2021/09/13/combinations-by-columns.md new file mode 100644 index 0000000..2ad8fb3 --- /dev/null +++ b/jekyll_site/ru/2021/09/13/combinations-by-columns.md @@ -0,0 +1,153 @@ +--- +title: Комбинации элементов по столбцам +description: В двухмерном массиве данные хранятся построчно. Рассмотрим алгоритм получения декартова произведения по столбцам с использованием трёх вложенных циклов. +sections: [Комбинаторика,Прямое произведение] +tags: [java,комбинаторика,алгоритмы,декартово произведение,комбинации,колонки,множество,список,массив,циклы,вложенные циклы] +canonical_url: /ru/2021/09/13/combinations-by-columns.html +url_translated: /en/2021/09/14/combinations-by-columns.html +title_translated: Combinations of elements by columns +date: 2021.09.13 +--- + +В двухмерном массиве данные хранятся построчно. Рассмотрим алгоритм получения *декартова +произведения* по столбцам с использованием трёх вложенных циклов. Количество строк и +колонок таблицы может быть произвольным. Последовательно перемножаем колонки и накапливаем +результат. Значения необязательно должны быть заполнены — нулевые элементы отбрасываем. + +Для примера возьмём частично заполненный *зубчатый двухмерный массив*: + +```java + | col1 | col2 | col3 | col4 +-----|------|------|------|------ +row1 | "A1" | "B1" | "C1" | "D1" +row2 | "A2" | "B2" | null | "D2" +row3 | "A3" | null | null | "D3" +row4 | "A4" | null | +``` + +Количество возможных комбинаций — это произведение количеств элементов во всех колонках: + +``` +4 * 2 * 1 * 3 = 24 +``` + +Напишем метод на Java для решения подобных задач, будем использовать три вложенных цикла `for`. + +Сначала подготавливаем *список списков* `List>`, заполненный одним пустым значением. +Будем использовать его как промежуточный результат и как финальный результат. + +Затем обходим колонки таблицы, пока они ещё есть, — последней считаем ту колонку, за которой +все элементы нулевые. На каждом шаге дополняем промежуточный результат элементами текущей +колонки и получаем новый промежуточный результат. Таким образом последовательно перемножаем +колонки и постепенно накапливаем результат. + +Схематически этот процесс выглядит следующим образом: + +``` +res0 * col1 = res1 * col2 = res2 * col3 = res3 * col4 = res4 +-----|------|------|------|-------|------|----------|------|------------ +[] * A1 = A1 * B1 = A1,B1 * C1 = A1,B1,C1 * D1 = A1,B1,C1,D1 + * A2 = A2 * B2 = A1,B2 * = A1,B2,C1 * D2 = A1,B1,C1,D2 + * A3 = A3 * = A2,B1 * = A2,B1,C1 * D3 = A1,B1,C1,D3 + * A4 = A4 * = A2,B2 * = A2,B2,C1 * = A1,B2,C1,D1 + * = * = A3,B1 * = A3,B1,C1 * = A1,B2,C1,D2 + * = * = A3,B2 * = A3,B2,C1 * = A1,B2,C1,D3 + * = * = A4,B1 * = A4,B1,C1 * = A2,B1,C1,D1 + * = * = A4,B2 * = A4,B2,C1 * = A2,B1,C1,D2 + * = * = * = * = A2,B1,C1,D3 + * = * = * = * = A2,B2,C1,D1 + * = * = * = * = ........... + * = * = * = * = ........... + * = * = * = * = A4,B2,C1,D3 +-----|------|------|------|-------|------|----------|------|------------ +1 * 4 = 4 * 2 = 8 * 1 = 8 * 3 = 24 +``` + +### Комбинации по столбцам {#combinations-by-columns} + +``` +Количество комбинаций: 24 + [A1, B1, C1, D1] [A2, B1, C1, D1] [A3, B1, C1, D1] [A4, B1, C1, D1] + [A1, B1, C1, D2] [A2, B1, C1, D2] [A3, B1, C1, D2] [A4, B1, C1, D2] + [A1, B1, C1, D3] [A2, B1, C1, D3] [A3, B1, C1, D3] [A4, B1, C1, D3] + [A1, B2, C1, D1] [A2, B2, C1, D1] [A3, B2, C1, D1] [A4, B2, C1, D1] + [A1, B2, C1, D2] [A2, B2, C1, D2] [A3, B2, C1, D2] [A4, B2, C1, D2] + [A1, B2, C1, D3] [A2, B2, C1, D3] [A3, B2, C1, D3] [A4, B2, C1, D3] +``` + +## Декартово произведение по столбцам {#cartesian-product-by-columns} + +Код будет выглядеть проще, если предварительно *транспонировать* массив массивов, но если этого +нельзя сделать, тогда во внешнем цикле обходим колонки массива до тех пор, пока они ещё есть. + +```java +/** + * @param table двухмерный список + * @param тип элементов списка + * @return декартово произведение элементов по столбцам + */ +public static List> cartesianProduct(List> table) { + // входящие данные не равны null + if (table == null) return Collections.emptyList(); + // промежуточный результат, содержит одно пустое значение + List> cp = Collections.singletonList(Collections.emptyList()); + // колонки ещё есть + boolean notLast = true; + // обходим колонки таблицы, пока они ещё есть + for (int i = 0; notLast; i++) { + // колонок больше нет + notLast = false; + // следующий промежуточный результат + List> next = new ArrayList<>(); + // обходим комбинации текущего промежуточного результата + for (List comb : cp) { + // обходим строки таблицы + for (List row : table) { + // если колонка ещё есть и значение в ней тоже есть + if (row != null && i < row.size() && row.get(i) != null) { + // новая комбинация, копируем текущую комбинацию + List nComb = new ArrayList<>(comb); + // добавляем значение из колонки + nComb.add(row.get(i)); + // помещаем в новый промежуточный результат + next.add(nComb); + // колонки ещё есть + notLast = true; + } + } + } + // если колонки ещё есть, то обновляем промежуточный результат + // и переходим на следующую итерацию, иначе выходим из цикла + if (notLast) cp = next; + } + // возвращаем итоговый результат + return cp; +} +``` + +```java +// запускаем программу и выводим результат +public static void main(String[] args) { + // произвольное количество строк и их элементов + List row1 = Arrays.asList("A1", "B1", "C1", "D1"); + List row2 = Arrays.asList("A2", "B2", null, "D2"); + List row3 = Arrays.asList("A3", null, null, "D3"); + List row4 = Arrays.asList("A4", null); + // зубчатый двухмерный список + List> table = Arrays.asList(row1, row2, row3, row4); + // декартово произведение + List> cp = cartesianProduct(table); + // вывод + System.out.println("Количество комбинаций: " + cp.size()); + // комбинации по столбцам + int rows = 6; + for (int i = 0; i < rows; i++) { + for (int j = 0; j < cp.size(); j++) + if (j % rows == i) + System.out.print(" " + cp.get(j)); + System.out.println(); + } +} +``` + +Вывод для этого кода см. выше: [комбинации по столбцам](#combinations-by-columns). diff --git a/jekyll_site/ru/2021/09/20/combinations-of-sequence-elements.md b/jekyll_site/ru/2021/09/20/combinations-of-sequence-elements.md new file mode 100644 index 0000000..9d5fcaa --- /dev/null +++ b/jekyll_site/ru/2021/09/20/combinations-of-sequence-elements.md @@ -0,0 +1,235 @@ +--- +title: Комбинации элементов последовательности +description: Рассмотрим задачу, в которой нужно получить все возможные комбинации элементов последовательности, где количество элементов в комбинации не превышает... +sections: [Комбинаторика,Размещения,Перестановки] +tags: [java,комбинаторика,алгоритмы,размещения,перестановки,комбинации,последовательность,множество,список,циклы,вложенные циклы] +canonical_url: /ru/2021/09/20/combinations-of-sequence-elements.html +url_translated: /en/2021/09/22/combinations-of-sequence-elements.html +title_translated: Combinations of sequence elements +date: 2021.09.20 +--- + +Рассмотрим задачу, в которой нужно получить все возможные комбинации элементов последовательности, +где количество элементов в комбинации не превышает заданного максимума, и напишем метод на Java +с соответствующим отбором по минимальному и максимальному количеству элементов. + +*[Задача о сервировке стола](#table-setting-problem) • [Метод для решения на Java](#combinations-of-length)* + +*Размещением* называется упорядоченный набор {`k`} различных элементов из множества различных +{`n`} элементов, где {k ≤ n}. Если {k = n}, то +такой упорядоченный набор называется *перестановкой*. Для универсальности решения *перестановки* +тоже будем учитывать как частный случай *размещения*. + +## Количество возможных комбинаций {#number-of-possible-combinations} + +Для наглядности возьмём последовательность из трёх элементов {`XYZ`}, нарисуем все возможные +подмножества этого множества, добавим к ним перестановки и подсчитаем количество комбинаций. + +{% include picture.html src="/img/arrangements-quantity.gif" size100=true background=true +alt="Получаем количество всех возможных размещений элементов последовательности" +caption="Получаем количество всех возможных размещений элементов последовательности" %} + +Формула количества размещений: + +{% include image_svg.html src="/img/arrangement-formula.svg" style="width: 83pt; height: 46pt;" +alt="\sum_{k=0}^{n} {n! \over (n-k)!}" %} + +Последовательность из трёх элементов: + +{% include image_svg.html src="/img/three-elements-sequence.svg" style="width: 375pt; height: 48pt;" +alt="\sum_{k=0}^{3} {3! \over (3-k)!} = {3! \over 3!} + {3! \over 2!} + {3! \over 1!} + {3! \over 0!} = 1+3+6+6 = 16." %} + +Способ реализации на Java: + +```java +public static void main(String[] args) { + // количество элементов последовательности + int n = 3; + // количество возможных комбинаций + int sum = 0; + // обходим возможные количества элементов + for (int k = 0; k <= n; k++) + // складываем количества размещений + sum += factorial(n) / factorial(n - k); + // вывод + System.out.println(sum); // 16 +} +// получаем факториал числа +static int factorial(int n) { + int fact = 1; + for (int i = 2; i <= n; i++) + fact *= i; + return fact; +} +``` + +## Комбинации из трёх элементов {#combinations-of-elements} + +Сравним две последовательности из трёх элементов: цифр {`123`} и букв {`XYZ`}. Тип элементов +разный — комбинации одинаковые, потому что порядковые номера у элементов те же самые. + +``` +Количество вариантов: 16 +[][1][2][3][12][13][21][23][31][32][123][132][213][231][312][321] +[][X][Y][Z][XY][XZ][YX][YZ][ZX][ZY][XYZ][XZY][YXZ][YZX][ZXY][ZYX] +``` + +Сравним последовательности из [`1..12`] элементов: количество вариантов быстро растёт +и вскоре выходит за пределы `Integer`, далее при {n > 12} нужно +будет использовать `Long`, а при {n > 20} — уже `BigInteger`. + +``` +n Кол-во вариантов +1 2 +2 5 +3 16 +4 65 +5 326 +6 1957 +7 13700 +8 109601 +9 986410 +10 9864101 +11 108505112 +12 1302061345 +``` + +Рассмотрим задачу, где нужно ограничить возможные варианты +по максимальному количеству входящих в них элементов. + +## Задача о сервировке стола {#table-setting-problem} + +На ужин приглашено четверо гостей {n = 4}, из которых известно, что +приедут не более двух человек {k = 2}, причём порядок их появления +важен, поскольку от этого зависит сервировка стола. Известно также, что когда приедет первый, +то он скажет, кто будет второй и приедет ли он. Рассчитать возможные варианты сервировки стола. + +### Способ решения {#solution-approach} + +Напишем метод на Java для решения подобных задач, который будет принимать на вход три параметра: +размер последовательности, минимальное и максимальное количество элементов в комбинации. +Возвращать метод будет список комбинаций указанной длины. + +```java +static List> combinationsOfElements(int size, int min, int max) +``` + +Вызываем метод с отбором {min=0; max=2} +и получаем список комбинаций указанной длины. + +```java +// задача о сервировке стола +public static void main(String[] args) { + // приглашено четверо гостей + int[] arr = {1, 2, 3, 4}; + // n - количество элементов последовательности + // k - максимальное количество элементов в комбинации + int n = 4, k = 2; + // комбинации элементов указанной длины [0..2] + List> list = combinationsOfElements(n, 0, k); + // вывод + System.out.println("Количество вариантов: " + list.size()); + for (Set set : list) { + System.out.print("["); + for (int i : set) + System.out.print(arr[i]); + System.out.print("]"); + } +} +``` + +Вывод: + +``` +Количество вариантов: 17 +[][1][2][3][4][12][13][14][21][23][24][31][32][34][41][42][43] +``` + +## Комбинации элементов указанной длины {#combinations-of-length} + +Пишем метод на Java с использованием трёх вложенных циклов `for`. Далее для проверки вызываем +этот метод без отбора {min=0; max=size} и получаем все возможные комбинации. +Для примера возьмём две последовательности из трёх элементов: цифр {`123`} и букв {`XYZ`}. + +### Описание метода {#method-description} + +Подготавливаем два списка комбинаций: результирующий список и текущий список. В текущем списке +количество элементов во всех комбинациях будет одинаковым. Максимальное количество элементов +в комбинации — это размер последовательности. Начинаем с одной пустой комбинации и постепенно +увеличиваем количество элементов. + +Обходим возможные количества элементов и дополняем текущие комбинации теми индексами, которых +в них ещё нет. На каждом шаге увеличиваем текущее количество элементов в комбинациях на единицу +и, если оно попадает в отбор, тогда добавляем эти комбинации к результату. + +```java +/** + * @param size размер последовательности (0 ≤ min ≤ max ≤ size) + * @param min минимальное количество элементов в комбинации + * @param max максимальное количество элементов в комбинации + * @return комбинации элементов указанной длины + */ +static List> combinationsOfElements(int size, int min, int max) { + // некорректные входящие данные, возвращаем пустой список комбинаций + if (0 > min || min > max || max > size) return Collections.emptyList(); + // результирующий список комбинаций + List> result = new ArrayList<>(); + // текущий список комбинаций, количество элементов во всех + // комбинациях одинаковое, начинаем с одной пустой комбинации + List> sublist = Arrays.asList(Collections.emptySet()); + // обходим возможные количества элементов в комбинации + for (int l = 0; l < Math.min(size, max); l++) { + // если текущее количество элементов входит в отбор, + // тогда добавляем эти комбинации к результату + if (l >= min) result.addAll(sublist); + // следующий список комбинаций + List> nSublist = new ArrayList<>(); + // обходим текущий список комбинаций + for (Set comb : sublist) { + // обходим индексы элементов последовательности + for (int i = 0; i < size; i++) { + // пропускаем те индексы, которые уже есть + if (comb.contains(i)) continue; + // новая комбинация, копируем текущую + Set nComb = new LinkedHashSet<>(comb); + // добавляем текущий индекс + nComb.add(i); + // помещаем в новый список комбинаций + nSublist.add(nComb); + } + } + // обновляем текущий список комбинаций + sublist = nSublist; + } + // добавляем текущий список комбинаций к результату + result.addAll(sublist); + // возвращаем результат + return result; +} +``` + +```java +// запускаем программу и выводим результат +public static void main(String[] args) { + // две последовательности из трёх элементов + Integer[] arr1 = {1, 2, 3}; + String[] arr2 = {"X", "Y", "Z"}; + // количество элементов последовательности + int n = 3; + // все возможные комбинации элементов [0..n] + List> list = combinationsOfElements(n, 0, n); + // вывод + System.out.println("Количество вариантов: " + list.size()); + for (Object[] arr : Arrays.asList(arr1, arr2)) { + StringBuilder sb = new StringBuilder(); + for (Set set : list) { + sb.append("["); + for (int i : set) sb.append(arr[i]); + sb.append("]"); + } + System.out.println(sb); + } +} +``` + +Вывод для этого кода см. выше: [комбинации из трёх элементов](#combinations-of-elements). diff --git a/jekyll_site/ru/2021/10/04/cartesian-product-parallel-streams.md b/jekyll_site/ru/2021/10/04/cartesian-product-parallel-streams.md new file mode 100644 index 0000000..ffa6ebc --- /dev/null +++ b/jekyll_site/ru/2021/10/04/cartesian-product-parallel-streams.md @@ -0,0 +1,173 @@ +--- +title: Декартово произведение в параллельных потоках +description: Рассмотрим алгоритм получения декартова произведения с помощью потоков Java Stream. Это решение похоже на три вложенных цикла for с тем отличием, что... +sections: [Многопоточность,Комбинаторика,Прямое произведение] +tags: [java,многопоточность,комбинаторика,алгоритмы,декартово произведение] +canonical_url: /ru/2021/10/04/cartesian-product-parallel-streams.html +url_translated: /en/2021/10/05/cartesian-product-parallel-streams.html +title_translated: Cartesian product in parallel streams +date: 2021.10.04 +--- + +Рассмотрим алгоритм получения *декартова произведения* с помощью потоков Java Stream. Это решение +похоже на три вложенных цикла `for` с тем отличием, что здесь внешний цикл заменён на поток для +удобства последующего распараллеливания. Будем использовать метод `reduce` с тремя параметрами: +`identity`, `accumulator` и `combiner`. + +*Алгоритм с тремя вложенными циклами: +[Декартово произведение множеств]({{ '/ru/2021/09/06/cartesian-product-of-sets.html' | relative_url }}).* + +Входящие данные — это произвольное количество списков и их элементов. + +``` +a = [A1,B1,C1,D1] +b = [A2,B2,C2] +c = [A3,B3] +d = [A4] +``` + +Количество возможных комбинаций — это произведение количеств элементов всех списков: + +``` +4 * 3 * 2 * 1 = 24 +``` + +Получаем *поток списков* `Stream>` из входящих данных и вызываем метод `reduce`: + +{% capture md_list_into_div %} +- `identity` — сущность. В нашем случае это *список списков* `List>`, +заполненный одним пустым значением. Будем использовать его как промежуточный +результат и как финальный результат. + +- `accumulator` — накопитель. Для каждого шага *редукции* описываем метод с двумя +параметрами: промежуточный результат и текущий список. Дополняем промежуточный +результат значениями из текущего списка. + +- `combiner` — объединитель. Используется в многопоточном режиме, объединяем +результаты работы потоков, получаем *декартово произведение* результатов. +{% endcapture %} +
+{{- md_list_into_div | markdownify -}} +
+ +Пошагово процесс накопления *декартова произведения* выглядит следующим образом: + +``` +result0: [[]] + list1: [A1,B1,C1,D1] +-------- +result1: [[A1],[B1],[C1],[D1]] + list2: [A2,B2,C2] +-------- +result2: [[A1,A2],[A1,B2],[A1,C2],[B1,A2],[B1,B2],...[D1,C2]] + list3: [A3,B3] +-------- +result3: [[A1,A2,A3],[A1,A2,B3],[A1,B2,A3],[A1,B2,B3],[A1,C2,A3],...[D1,C2,B3]] + list4: [A4] +-------- +result4: [[A1,A2,A3,A4],[A1,A2,B3,A4],[A1,B2,A3,A4],[A1,B2,B3,A4],...[D1,C2,B3,A4]] +``` + +### Комбинации по столбцам {#combinations-by-columns} + +``` +Количество комбинаций: 24 +[A1, A2, A3, A4] [B1, A2, A3, A4] [C1, A2, A3, A4] [D1, A2, A3, A4] +[A1, A2, B3, A4] [B1, A2, B3, A4] [C1, A2, B3, A4] [D1, A2, B3, A4] +[A1, B2, A3, A4] [B1, B2, A3, A4] [C1, B2, A3, A4] [D1, B2, A3, A4] +[A1, B2, B3, A4] [B1, B2, B3, A4] [C1, B2, B3, A4] [D1, B2, B3, A4] +[A1, C2, A3, A4] [B1, C2, A3, A4] [C1, C2, A3, A4] [D1, C2, A3, A4] +[A1, C2, B3, A4] [B1, C2, B3, A4] [C1, C2, B3, A4] [D1, C2, B3, A4] +``` + +## Комбинации элементов в параллельных потоках {#combinations-of-elements-in-parallel-streams} + +В параллельном режиме скорость работы алгоритма увеличивается при перемножении *большого количества +маленьких списков*, например 20 списков по 2 элемента или 15 списков по 3 элемента. Время вычислений +уменьшается в *полтора-два* раза. В остальных случаях время работы примерно такое же, как у трёх +вложенных циклов `for`. + +```java +/** + * @param lists произвольное количество списков + * @param тип элементов списков + * @return декартово произведение переданных списков + */ +public static List> cartesianProduct(List> lists) { + // входящие данные не равны null + if (lists == null) return Collections.emptyList(); + // поток списков Stream> + return lists.stream() + // включаем параллельный режим + .parallel() + // отбрасываем нулевые и пустые списки + .filter(list -> list != null && list.size() > 0) + // сводим поток списков в один список, получаем декартово произведение + // reduce(identity, accumulator, combiner) + .reduce( // промежуточный результат, содержит одно пустое значение + Collections.singletonList(Collections.emptyList()), + // обходим переданные списки и дополняем промежуточный результат их + // элементами, на каждом шаге получаем новый промежуточный результат + (result, list) -> { + // следующий промежуточный результат + List> nResult = new ArrayList<>(result.size() * list.size()); + // строки текущего промежуточного результата + for (List row : result) { + // элементы текущего списка + for (T el : list) { + // новая строка для следующего промежуточного результата + List nRow = new ArrayList<>(row.size() + 1); + // добавляем текущую строку + nRow.addAll(row); + // добавляем текущий элемент + nRow.add(el); + // помещаем в следующий промежуточный результат + nResult.add(nRow); + } + } + // передаём на следующую итерацию + return nResult; + }, + // используется в многопоточном режиме, объединяем результаты + // работы потоков, получаем декартово произведение результатов + (result1, result2) -> { + // объединённый результат + List> result = new ArrayList<>(result1.size() * result2.size()); + // обходим результаты + for (List comb1 : result1) { + for (List comb2 : result2) { + // складываем комбинации + List comb = new ArrayList<>(comb1.size() + comb2.size()); + comb.addAll(comb1); + comb.addAll(comb2); + result.add(comb); + } + } + return result; + }); +} +``` + +```java +// запускаем программу и выводим результат +public static void main(String[] args) { + // произвольное количество списков и их элементов + List a = Arrays.asList("A1", "B1", "C1", "D1"); + List b = Arrays.asList("A2", "B2", "C2"); + List c = Arrays.asList("A3", "B3"); + List d = Collections.singletonList("A4"); + // декартово произведение + List> cp = cartesianProduct(Arrays.asList(a, b, c, d)); + // вывод + System.out.println("Количество комбинаций: " + cp.size()); + // комбинации по столбцам + int rows = 6; + IntStream.range(0, rows).forEach(i -> System.out.println( + IntStream.range(0, cp.size()) + .filter(j -> j % rows == i) + .mapToObj(j -> cp.get(j).toString()) + .collect(Collectors.joining(" ")))); +} +``` + +Вывод для этого кода см. выше: [комбинации по столбцам](#combinations-by-columns). diff --git a/jekyll_site/ru/index.md b/jekyll_site/ru/index.md new file mode 100644 index 0000000..4cd844a --- /dev/null +++ b/jekyll_site/ru/index.md @@ -0,0 +1,61 @@ +--- +title: Код с комментариями +description: Заметки на тему программирования с примерами кода и комментариями. Решения задач и описания решений. +sections: [Решения задач и описания решений] +tags: [java,комбинаторика,алгоритмы,реализация,множества,подмножества,списки,массивы,циклы,вложенные циклы] +canonical_url: / +url_translated: /en/ +title_translated: Code with comments +--- + +{%- assign articles = "" | split: "" %} +{%- assign articles = articles | push: "Декартово произведение в параллельных потоках" %} +{%- capture article_brief %} +Рассмотрим алгоритм получения *декартова произведения* с помощью потоков Java Stream. Это решение +похоже на три вложенных цикла `for` с тем отличием, что здесь внешний цикл заменён на поток для +удобства последующего распараллеливания. Будем использовать метод `reduce` с тремя параметрами: +`identity`, `accumulator` и `combiner`. + +В параллельном режиме скорость работы алгоритма увеличивается при перемножении *большого количества +маленьких списков*, например 20 списков по 2 элемента или 15 списков по 3 элемента. Время вычислений +уменьшается в *полтора-два* раза. В остальных случаях время работы примерно такое же, как у трёх +вложенных циклов `for`. +{%- endcapture %} +{%- assign articles = articles | push: article_brief %} +{%- assign articles = articles | push: "Комбинации элементов последовательности" %} +{%- capture article_brief %} +Рассмотрим задачу, в которой нужно получить все возможные комбинации элементов последовательности, +где количество элементов в комбинации не превышает заданного максимума, и напишем метод на Java +с соответствующим отбором по минимальному и максимальному количеству элементов. + +*Размещением* называется упорядоченный набор {`k`} различных элементов из множества различных +{`n`} элементов, где {k ≤ n}. Если {k = n}, то +такой упорядоченный набор называется *перестановкой*. Для универсальности решения *перестановки* +тоже будем учитывать как частный случай *размещения*. +{%- endcapture %} +{%- assign articles = articles | push: article_brief %} +{%- assign articles = articles | push: "Комбинации элементов по столбцам" %} +{%- capture article_brief %} +В двухмерном массиве данные хранятся построчно. Рассмотрим алгоритм получения *декартова +произведения* по столбцам с использованием трёх вложенных циклов. Количество строк и +колонок таблицы может быть произвольным. Последовательно перемножаем колонки и накапливаем +результат. Значения необязательно должны быть заполнены — нулевые элементы отбрасываем. +{%- endcapture %} +{%- assign articles = articles | push: article_brief %} +{%- assign articles = articles | push: "Треугольник Паскаля на Java" %} +{%- capture article_brief %} +Рассмотрим вариант реализации *треугольника Паскаля* на Java. Для простоты хранения и обработки +данных представим треугольник в виде двухмерного массива, в котором элементы первой строки и колонки +равны единице, а все остальные элементы — есть сумма двух предыдущих элементов в строке и в колонке. +{%- endcapture %} +{%- assign articles = articles | push: article_brief %} +{%- assign articles = articles | push: "Декартово произведение множеств" %} +{%- capture article_brief %} +Рассмотрим алгоритм получения *декартова произведения* нескольких множеств с использованием +трёх вложенных циклов. Количество множеств и их элементов может быть произвольным. Последовательно +перемножаем множества и накапливаем результат. Порядок значения не имеет, так как от перестановки +множителей произведение не меняется. В результате порядок будет отличаться, но комбинации будут +те же самые. +{%- endcapture %} +{%- assign articles = articles | push: article_brief %} +{%- include main_page.html articles = articles -%} diff --git a/package.sh b/package.sh new file mode 100755 index 0000000..f03a493 --- /dev/null +++ b/package.sh @@ -0,0 +1,5 @@ +#!/bin/bash +echo "Подготовка архива для последующего развёртывания." +cd _site || exit +rm -rf ../pomodoro2.zip +7z a ../pomodoro2.zip ./* diff --git a/serve.sh b/serve.sh new file mode 100755 index 0000000..55b689c --- /dev/null +++ b/serve.sh @@ -0,0 +1,4 @@ +#!/bin/bash +echo "Локальное развёртывание для проверки корректности сборки." +jekyll serve --skip-initial-build --disable-disk-cache --host localhost +echo "Адрес сервера: http://localhost:4000"