私は2つの配列を持っています:
String[] operators = {"+", "-", "*"};
int[] numbers = {48, 24, 12, 6};
そして、私はこのような文字列形式ですべての可能な組み合わせを取得したい:
48+24+12+6
48+24+12-6
48+24+12*6
48+24-12+6
48+24-12-6
48+24-12*6
..........
48*24*12*6
これは私が試したもの:
for(int i = 0; i < operators.length; i++) {
System.out.println(numbers[0] + operators[i] + numbers[1] + operators[i] + numbers[2] + operators[i] + numbers[3]);
}
ただし、印刷されるのは次のとおりです。
48+24+12+6
48-24-12-6
48*24*12*6
これを解決するには?
これは重複ではありません。なぜなら、2ペアのデータをすべて取得するのではなく、4ペアのすべての組み合わせを取得するからです。複製は異なります。
トリプルループを使用します。
for (int i=0; i < operators.length; ++i) {
for (int j=0; j < operators.length; ++j) {
for (int k=0; k < operators.length; ++k) {
System.out.println(numbers[0] + operators[i] + numbers[1] + operators[j] +
numbers[2] + operators[k] + numbers[3]);
}
}
}
基本的に、演算子ベクトルの外積を取得する必要があります(ベクトルの場合)。 Javaでは、これは3重にネストされたループのセットに変換されます。
@TimBiegeleisenソリューションは魅力のように機能しますが、その複雑さが問題になる場合があります。より良いアプローチは、次のようなコードです。
static void combinationUtil(int[] arr, int n, int r, int index, int[] data, int i)
{
// Current combination is ready to be printed, print it
if (index == r)
{
for (int j=0; j<r; j++)
System.out.print(data[j]+" ");
System.out.println("");
return;
}
// When no more elements are there to put in data[]
if (i >= n)
return;
// current is included, put next at next location
data[index] = arr[i];
combinationUtil(arr, n, r, index+1, data, i+1);
// current is excluded, replace it with next (Note that
// i+1 is passed, but index is not changed)
combinationUtil(arr, n, r, index, data, i+1);
}
// The main function that prints all combinations of size r
// in arr[] of size n. This function mainly uses combinationUtil()
static void printCombination(int arr[], int n, int r)
{
// A temporary array to store all combination one by one
int data[]=new int[r];
// Print all combination using temprary array 'data[]'
combinationUtil(arr, n, r, 0, data, 0);
}
ソース: GeeksForGeeks and my IDE :)
これは、再帰的なソリューションの教科書のように聞こえます。
_public static void combineAndPrint(String[] pieces, String[] operators) {
if (pieces.length < 1) {
// no pieces? do nothing!
} else if (pieces.length == 1) {
// just one piece? no need to join anything, just print it!
System.out.println(pieces[0]);
} else {
// make a new array that's one piece shorter
String[] newPieces = new String[pieces.length - 1];
// copy all but the first two pieces into it
for (int i = 2; i < pieces.length; i++) {
newPieces[i - 1] = pieces[i];
}
// combine the first two pieces and recurse
for (int i = 0; i < operators.length; i++) {
newPieces[0] = pieces[0] + operators[i] + pieces[1];
combineAndPrint(newPieces, operators);
}
}
}
public static void main(String[] args) {
String[] operators = {"+", "-", "*"};
String[] numbers = {"48", "24", "12", "6"};
combineAndPrint(numbers, operators);
}
_
ところで、このメソッドを一般化して、生成された式を単に印刷するよりも多くのことができるようにするには、余分な_Consumer<String>
_パラメーターを受け入れるようにすることをお勧めします。つまり、メソッド宣言を次のように書き換えることができます。
_public static void combine(String[] pieces, String[] operators, Consumer<String> consumer) {
_
System.out.println(pieces[0])
をconsumer.accept(pieces[0])
で置き換え、combineAndPrint(newPieces, operators)
の再帰呼び出しをcombine(newPieces, operators, consumer)
で置き換えます。次に、メインメソッドから呼び出します。として:
_combine(numbers, operators, s -> System.out.println(s));
_
(もちろん、このより柔軟な方法で行うには、やや現代的なJavaバージョン— Java 8以降、具体的には—最初の例I上記のコードは、古代バージョンでもJava 1.0まで動作します。将来のバージョンでは、Java PythonやKotlin、さらには現代のJSでさえも既に持っているものです。そうすれば、消費者にそれ以上のことを渡す必要さえありません。)
私は代替の、過剰に設計された(しかし柔軟な!)「ビジネス」ソリューションを作りました。配列の長さと値(numbers
およびoperators
)は柔軟です。
package test1;
import Java.io.IOException;
import Java.util.ArrayList;
public class MainClass
{
public static void main(String[] args) throws IOException
{
String[] operators = {"+", "-", "*"};
int[] numbers = {48, 24, 12, 6};
ArrayList<String> strings = new MainClass().getAllPossibleCombinations(numbers, operators);
for (String string : strings)
{
System.out.println(string);
}
}
private ArrayList<String> getAllPossibleCombinations(int[] numbers, String[] operators)
{
if (numbers.length < 2) throw new IllegalArgumentException("Length of numbers-array must be at least 2");
if (operators.length < 1) throw new IllegalArgumentException("Length of operators-array must be at least 1");
ArrayList<String> returnList = new ArrayList<>();
int[] indexes = new int[numbers.length - 1];
while (true)
{
StringBuilder line = new StringBuilder();
for (int i = 0; i < numbers.length; i++)
{
int number = numbers[i];
line.append(number);
if (i < indexes.length)
{
line.append(operators[indexes[i]]);
}
}
returnList.add(line.toString());
try
{
this.updateIndexes(indexes, operators.length - 1);
}
catch (NoMoreCombinationsException e)
{
break;
}
}
return returnList;
}
private void updateIndexes(int[] currentIndexes, int maxValue) throws NoMoreCombinationsException
{
if (this.intArrayIsOnly(currentIndexes, maxValue))
{
throw new NoMoreCombinationsException();
}
for (int i = currentIndexes.length - 1; i >= 0; i--)
{
int currentIndex = currentIndexes[i];
if (currentIndex < maxValue)
{
currentIndexes[i] = currentIndex + 1;
break;
}
else
{
currentIndexes[i] = 0;
}
}
}
private boolean intArrayIsOnly(int[] array, int value)
{
for (int iteratedValue : array)
{
if (iteratedValue != value) return false;
}
return true;
}
}
class NoMoreCombinationsException extends Exception
{
public NoMoreCombinationsException()
{
}
public NoMoreCombinationsException(String message)
{
super(message);
}
public NoMoreCombinationsException(String message, Throwable cause)
{
super(message, cause);
}
public NoMoreCombinationsException(Throwable cause)
{
super(cause);
}
public NoMoreCombinationsException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace)
{
super(message, cause, enableSuppression, writableStackTrace);
}
}
魅力のように動作します:)
彼の答えのfindusl ですでに指摘したように、ここでの問題は、厳密に言えば、「2つの配列の組み合わせ」を見つけられないことです。代わりに、基本的に、使用可能な演算子の可能なすべての組み合わせを検索するだけです。
(後でオペランドでそれらを「インターベート」したいという事実は、質問の核心とは無関係です)
したがって、これを解決するための別のオプションがあります: 特定のセットからの特定の数の要素のすべての組み合わせで反復可能 (あなたの場合:演算子)を作成し、結果を単に他のセット(あなたの場合:オペランド)。
import Java.util.ArrayList;
import Java.util.Arrays;
import Java.util.Iterator;
import Java.util.List;
import Java.util.NoSuchElementException;
public class OperatorsTest
{
public static void main(String[] args)
{
String[] operators = {"+", "-", "*"};
int[] numbers = {48, 24, 12, 6};
CombinationIterable<String> iterable =
new CombinationIterable<String>(3, Arrays.asList(operators));
for (List<String> element : iterable)
{
System.out.println(interveave(element, numbers));
}
}
private static String interveave(List<String> operators, int numbers[])
{
StringBuilder sb = new StringBuilder();
for (int i=0; i<operators.size(); i++)
{
sb.append(numbers[i]);
sb.append(operators.get(i));
}
sb.append(numbers[numbers.length-1]);
return sb.toString();
}
}
class CombinationIterable<T> implements Iterable<List<T>>
{
private final List<T> input;
private final int sampleSize;
private final int numElements;
public CombinationIterable(int sampleSize, List<T> input)
{
this.sampleSize = sampleSize;
this.input = input;
numElements = (int) Math.pow(input.size(), sampleSize);
}
@Override
public Iterator<List<T>> iterator()
{
return new Iterator<List<T>>()
{
private int current = 0;
private final int chosen[] = new int[sampleSize];
@Override
public boolean hasNext()
{
return current < numElements;
}
@Override
public List<T> next()
{
if (!hasNext())
{
throw new NoSuchElementException("No more elements");
}
List<T> result = new ArrayList<T>(sampleSize);
for (int i = 0; i < sampleSize; i++)
{
result.add(input.get(chosen[i]));
}
increase();
current++;
return result;
}
private void increase()
{
int index = chosen.length - 1;
while (index >= 0)
{
if (chosen[index] < input.size() - 1)
{
chosen[index]++;
return;
}
chosen[index] = 0;
index--;
}
}
};
}
}
このタスクは、一定数のオペランドと演算子で実行できる一連の操作を見つけるタスクに似ているため、 this Q/A が関連している可能性があります。しかし、アソシエティビティや可換性のようなものをここで考慮する必要があるかどうかは、質問で言及されていません。
私はこのユースケースと他の多くをカバーするクラスを開発しました。 TallyCounter と呼びます。このクラスでは、次のように質問に答えられます。
package app;
import Java.util.HashMap;
import Java.util.Map;
import app.TallyCounter.Type;
public class App {
public static void main(String args[]) throws Exception {
Map<Long, String> map = new HashMap<>();
map.put(0l, "+");
map.put(1l, "-");
map.put(2l, "*");
TallyCounter counter = new TallyCounter(3, Type.NORMAL, 2);
do {
System.out.format("48%s24%s12%s6\n",
map.get(counter.getArray()[2]),
map.get(counter.getArray()[1]),
map.get(counter.getArray()[0])
);
counter.increment();
} while (!counter.overflowFlag);
}
}
なぜ答えがそのままなのかという背景情報を少し。この問題は、実際には「すべての可能な組み合わせ」とは呼ばれません。通常、要素をビットとして表現し、要素が含まれているかどうかに関係なく0または1に切り替えることができる問題です。これは2 ^ Nの複雑さを持ちます。ここで、Nは使用する演算子の量です。これは、単一のループで簡単に解決できます。
ただし、あなたの場合、「置換とシーケンスに関する問題を抱えている」ことになります。この複雑さはN ^ nです。ここで、nは演算子で埋めなければならないスポットの量です。 (これは、各スポットが10個の値になり得るピンコードでよく見られます)。したがって、これは「可能なすべての組み合わせ」問題よりも複雑なので、複数のループまたは再帰呼び出しが必要です。
そこで、「これを解決する方法」という質問に答えるために。根本的な問題の複雑さのために、複数のループまたは再帰を使用して解決する必要があります。
複数のループや再帰は必要ありません。
ループの数が制限され、再帰がまったくない例を示します。
int[][] combine (int[] values) {
int size = values.length;
int combinations = 1;
for(int i = 0; i < size; i++) {
combinations *= size;
}
// or int combinations = (int)Math.pow(size, size);
int[][] result = new int[combinations][size];
for(int i = 0; i < combinations; i++) {
int index = i;
for(int j = 0; j < size; j++) {
result[i][j] = values[index % size];
index /= size;
}
}
return result;
}
3つの要素で使用する場合、[1, 2, 3]
、以下のコードのように:
void testCombine() {
int[][] combinations = combine(new int[]{1, 2, 3});
for(int[] combination: combinations) {
System.out.println(Arrays.toString(combination));
}
}
次の結果になります。
[1, 1, 1]
[2, 1, 1]
[3, 1, 1]
[1, 2, 1]
[2, 2, 1]
[3, 2, 1]
[1, 3, 1]
[2, 3, 1]
[3, 3, 1]
[1, 1, 2]
[2, 1, 2]
[3, 1, 2]
[1, 2, 2]
[2, 2, 2]
[3, 2, 2]
[1, 3, 2]
[2, 3, 2]
[3, 3, 2]
[1, 1, 3]
[2, 1, 3]
[3, 1, 3]
[1, 2, 3]
[2, 2, 3]
[3, 2, 3]
[1, 3, 3]
[2, 3, 3]
[3, 3, 3]