用Java填充布尔数组


72

作为一个相当环保的Java程序员,我给自己设定了尝试编写简单文本冒险的艰巨挑战。毫不奇怪,我已经遇到了困难!

我试图给我的Location类一个属性来存储退出包含的属性。我为此使用了一个布尔数组,以实质上保存代表每个出口的true / false值。我并不完全相信

a)这是最有效的方法,并且

b)我正在使用正确的代码填充数组。

我希望收到所有反馈,即使是对完整的代码进行大修!

目前,当实例化一个Location时,我生成一个String,该字符串发送给setExits方法:

    String e = "N S U";
    secretRoom.setExits(e);

在Location类中,setExits如下所示:

public void setExits(String e) {
    if (e.contains("N"))
        bexits[0] = true;
    else if (e.contains("W"))
        bexits[1] = true;
    else if (e.contains("S"))
        bexits[2] = true;
    else if (e.contains("E"))
        bexits[3] = true;
    else if (e.contains("U"))
        bexits[4] = true;
    else if (e.contains("D"))
        bexits[5] = true;
}

老实说,我认为这看起来特别笨拙,但是我想不出另一种方法来做到这一点。我现在也不完全确定如何编写getExits方法...

任何帮助都将受到欢迎!


36
伙计,只要所有“绿色”程序员都能以这种方式提出问题。我是否也可以针对仅要求改进的此类问题提出codereview建议?祝好运。
MarioDS 2014年

1
@MDeSchaepmeester:英文不好...绿色的Java编码器是什么意思?:o
NoobEditor 2014年

1
@NoobEditor-意思是“新”。
鲁迪·克肖

3
您是否选择了这个项目专门是为了更好地学习Java,还是因为文本冒险很棒?对于前者,一切顺利,您将学到很多东西!但是,如果是后者,我可以推荐Inform 7还是TADS?
Paul Z

2
感谢您的赞美和建议!@PaulZ,我决定进行文字冒险,以提高Java的水平(以及因为它们很棒!)并自学其余部分。
lordchancellor 2014年

Answers:


29

是否有任何原因使您使用Strings而不传递进去booleans,即

public void setExits(boolean N, boolean E, boolean S, boolean W, boolean U, boolean D) 

还是有二传手

public void setNorthOpen(boolean open)
{
  bexits[4] = open;
}

其次,为什么将出口作为布尔数组存储,这是一个小的有限集,为什么不只是

boolean N,S,E,W,U,D;

这样一来,您就不必跟踪每个方向是数组中的哪个数字。

这是一个正确的答案(如果不是像@gexicide那样的最佳选择),但我完全鼓励任何人在这里查看其他答案,以有趣的方式看一下如何用Java以不同的方式完成事情。

备查

起作用的代码属于“代码审阅”,而不是“堆栈溢出”。尽管正如@kajacx所指出的,该代码实际上不起作用。


1
感谢@Ross,我喜欢您建议的安装员的外观。正如我所提到的,我对此很陌生,所以我有点迷茫!不过,谢谢您,我感谢您的帮助。
lordchancellor 2014年

2
另外,感谢您对代码审查的关注-我不知道这一点,但会记住以供将来参考。
lordchancellor 2014年

如果您想坚持使用Strings ,则此处的某些答案确实非常好,但除非有特殊原因,否则建议booleans像我的示例中所述。
罗斯·德鲁

14
setExits(false,true,false,true)会令人恐惧,并且另一个答案中建议的enum变体就更好了。
OliverS 2014年

1
我同意,多个参数不是很好,这就是为什么我建议使用accessor方法。我也同意,理想情况下,这EnumSet是最好的选择,但我已经读到OP是“绿色的”,并且认为从明显痛苦的String传球中脱身的简单垫脚石是更好的选择。我通常会将答案扩大到更高级,但同时会回答@gexicide,我不想回答盗用问题,现在两者都在这里供任何需要的人使用。不赞成投票,因为它不是绝对最佳答案,但这不是很正确吗?
罗斯·德鲁

128

最有效最富有表现力的方式如下:

enums用作Exits并使用anEnumSet来存储它们。EnumSetSet使用位字段表示枚举常量的高效实现。

这是您可以执行的操作:

public enum Exit { North, West, South, East, Up, Down; }

EnumSet<Exit> set = EnumSet.noneOf(Exit.class); // An empty set.

// Now you can simply add or remove exits, everything will be stored compactly

set.add(Exit.North); // Add exit
set.contains(Exit.West); // Test if an exit is present
set.remove(Exit.South); //Remove an exit

枚举集将所有出口存储在一个long内部,因此您的代码富有表现力,速度快,并节省了大量内存。


3
我喜欢这个答案的简单性!为了使代码更具可读性,我还将使每个枚举方向都使用全名,而不是第一个字母。即public enum Exit { North, West, South, East, Up, Down; }
-Dylan Watson

1
非常优雅的解决方案。我以前从未见过EnumSet像这样的习惯,但是它看起来像个习惯的好习惯。
接近

2
@zamnuts如果Enum中的元素过多,则EnumSet的实现将使用JumboEnumSet。见grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/...
WW。

1
@maaartinus:我不确定!我猜是因为类型的枚举集Exit将始终是同一类(RegularEnumSet),所以JIT可能很好地消除了所有虚拟调用并内联了所有内容。使用int中为了摆脱那很可能被优化掉了一些虚拟呼叫的气味很像过早的优化。
杀人事件

1
@gexicide同意,有可能。一个基准可以告诉我们是否有人好奇。即使没有,我也会在EnumSet这里使用任何东西之前至少考虑两次。
maaartinus 2014年

15

好的,首先,您的setExits()方法将无法正常工作,链接的if-elseif将最大程度地执行1个代码分支,例如:

if (e.contains("N"))
    bexits[0] = true;
else if (e.contains("W"))
    bexits[1] = true;

即使e同时包含NWbexits[0]也将被设置。同样,此方法将仅添加出口(例如,调用setExits("")不会删除任何现有出口。

我会将该方法更改为:

bexits[0] = e.contains("N");
bexits[1] = e.contains("W");
...

另外,我绝对不记得北在索引0上,西在索引1上,所以一种常见的做法是使用最终的静态常量来命名索引:

public static final int NORTH = 0;
public static final int WEST = 1;
...

然后,您可以编写setExits方法:

bexits[NORTH] = e.contains("N");
bexits[WEST] = e.contains("W");
...

(更易读)

最后,如果您希望代码更合理地安排,则可以 Exits表示可用出口类,并以布尔数组为后盾。然后,在创建String的地方,可以改而创建此类,并节省自己生成和解析字符串的工作。

编辑:

作为@gexicide的答案,有一个非常方便的类EnumSet,对于表示出口而言,它可能比bollean数组更好。


8
public static final int感觉像Java 1.4,并被枚举代替
EpicPandaForce 2014年

您将使用它作为访问数组的索引,我比使用enum并获取enum的id或其他获取索引的索引更喜欢它。
kajacx 2014年

这正是我在下面使用的答案。好吧,的确,您需要使用Directions.NORTH.ordinal()特定方向的索引...
EpicPandaForce 2014年

另一方面,EnumSet看起来真的很酷,不知道该课程,我将其添加到答案中。
kajacx 2014年

9

EnumSet在对方的回答是做到这一点的最好办法,我只是想补充一点,虽然对于未来,当你刚开始你是否可以移动但如果你正在向不看。

以及EnumSet你也有EnumMap

如果您定义房间类别/界面,则可以在房间类别内

Map<Direction, Room> exits = new EnumMap<>(Direction.class);

现在,您可以按以下方式将链接添加到地图中:

exits.put(Direction.NORTH, theRoomNorthOfMe);

然后,在房间之间移动的代码可能非常通用:

Room destination=currentRoom.getExit(directionMoved);

if (destination == null) {
    // Cannot move that way
} else {
    // Handle move to destination
}

这就是我要做的。
Ultimate Gobblement 2014年

7

我将创建一个Exit枚举,并在location类上仅设置一个Exit对象列表。

所以会是这样的:

public enum Exit { N, S, E, W, U, D }

List<Exit> exits = parseExits(String exitString);
location.setExits(exits);

您的意思是:NWES?;)
RobAu 2014年

1
这仍然需要使用相同的代码,只是移至parseExits
Ross Drew 2014年

+1枚举。如此强大的工具,因此经常未被充分利用。
鲁迪·克肖

哈哈!“ U”代表“向上”。显然,我对简单的指南针方向不满意...
lordchancellor 2014年

我不确定我是否遇到过枚举-我将去阅读一下,看看这是否对我来说是前进的方向。感谢您的建议!
lordchancellor 2014年

6

给定您的代码,这是我能想到的最易读的实现:

public class Exits {
    private static final char[] DIRECTIONS = "NSEWUD".toCharArray();

    public static void main(String... args) {
        String input = "N S E";
        boolean[] exits = new boolean[DIRECTIONS.length];

        for(int i = 0; i< exits.length; i++) {
            if (input.indexOf(DIRECTIONS[i]) >= 0) {
                exits[i] = true;
            }
        }
    }
}

话虽这么说,但仍有许多更清洁的解决方案。我个人会使用枚举和一个EnumSet

顺便说一句,您的原始代码不正确,因为它将在数组中将最多一个值设置为true。


3

如果要将出口定义为字符串,则应使用它。我会这样做:

public class LocationWithExits {
    public static final String NORTH_EXIT="[N]";
    public static final String SOUTH_EXIT="[S]";
    public static final String EAST_EXIT="[E]";
    public static final String WEST_EXIT="[W]";

    private final String exitLocations;

    public LocationWithExits(String exitLocations) {
        this.exitLocations = exitLocations;
    }
    public boolean hasNorthExit(){
        return exitLocations.contains(NORTH_EXIT);
    }
    public static void main(String[] args) {
        LocationWithExits testLocation=new LocationWithExits(NORTH_EXIT+SOUTH_EXIT);
        System.out.println("Has exit on north?: "+testLocation.hasNorthExit());
    }


}

如果忘记了确切的含义bexits [0],则使用布尔数组可能会导致很多问题。是北方还是南方?等等

或者,您可以只使用枚举和可用出口列表。然后在methid测试列表是否包含一定的枚举值


3

就个人而言,我认为您可以使用枚举来破解它,然后执行以下操作:

public void setExits(String e) {
    if (e.contains("N"))
        bexits[0] = true;
    else if (e.contains("W"))
        bexits[1] = true;
    else if (e.contains("S"))
        bexits[2] = true;
    else if (e.contains("E"))
        bexits[3] = true;
    else if (e.contains("U"))
        bexits[4] = true;
    else if (e.contains("D"))
        bexits[5] = true;
}

进入

public enum Directions
{
    NORTH("N"),
    WEST("W"),
    SOUTH("S"),
    EAST("E"),
    UP("U"),
    DOWN("D");

    private String identifier;

    private Directions(String identifier)
    {
        this.identifier = identifier;
    }

    public String getIdentifier()
    {
        return identifier;
    }
}

然后执行:

public void setExits(String e) 
{
  String[] exits = e.split(" ");
  for(String exit : exits)
  {
      for(Directions direction : Directions.values())
      {
          if(direction.getIdentifier().equals(exit))
          {
              bexits[direction.ordinal()] = true;
              break;
          }
      }
  }
}

尽管写下来之后,我真的无法告诉您它是否更好。可以肯定的是,添加新方向更加容易。


我以为将创建boolean bexits = new boolean[Directions.values().length];
Bexits

老实说,当我编写自己的代码时,我所做的是使用Map<Directions, Boolean> map = new HashMap<>();,并将其用作map.put(Directions.NORTH, false);map.get(Directions.NORTH);。这是一个比起来略高于漂亮,但它仍然不是一样漂亮的EnumSet通过gexicide以上。
EpicPandaForce 2014年

3

答案中列出的所有方法都是好的。但是我认为您需要采取的方法取决于您使用出口字段的方式。例如,如果您要以字符串形式处理出口,那么Ross Drews方法将需要很多if-else条件和变量。

String exit = "N E";
String[] exits = exit.split(" ");
boolean N = false, E = false, S = false, W = false, U = false, D = false;
for(String e : exits){
    if(e.equalsIgnoreCase("N")){
        N = true;
    } else if(e.equalsIgnoreCase("E")){
        E = true;
    } else if(e.equalsIgnoreCase("W")){
        W= true;
    } else if(e.equalsIgnoreCase("U")){
        U = true;
    } else if(e.equalsIgnoreCase("D")){
        D = true;
    } else if(e.equalsIgnoreCase("S")){
        S = true;
    }
}
setExits(N, E, S, W, U, D);

另外,如果您有出口,并且要检查某个地点是否有该特定出口,那么再次必须执行相同的操作

public boolean hasExit(String exit){
    if(e.equalsIgnoreCase("N")){
        return this.N; // Or the corresponding getter method
    } else if(e.equalsIgnoreCase("E")){
        return this.E;
    } else if(e.equalsIgnoreCase("W")){
        return this.W;
    } else if(e.equalsIgnoreCase("U")){
        return this.U;
    } else if(e.equalsIgnoreCase("D")){
        return this.D;
    } else if(e.equalsIgnoreCase("S")){
        return this.S;
    }
}

因此,如果您打算将其作为字符串进行操作,我认为最好的方法是使用列表和枚举。通过这种方式,您可以非常轻松地执行诸如hasExit,hasAnyExit,hasAllExits,hasNorthExit,hasSouthExit,getAvailableExits等方法。并且考虑使用列表(或集合)的退出次数(6)不会是开销。例如

枚举

public enum EXIT {
        EAST("E"),
        WEST("W"),
        NORTH("N"),
        SOUTH("S"),
        UP("U"),
        DOWN("D");

        private String exitCode;

        private EXIT(String exitCode) {
        this.exitCode = exitCode;
        }

        public String getExitCode() {
        return exitCode;
        }

        public static EXIT fromValue(String exitCode) {
            for (EXIT exit : values()) {
                if (exit.exitCode.equalsIgnoreCase(exitCode)) {
                    return exit;
                }
            }
            return null;
        }

        public static EXIT fromValue(char exitCode) {
            for (EXIT exit : values()) {
                if (exit.exitCode.equalsIgnoreCase(String.valueOf(exitCode))) {
                    return exit;
                }
            }
            return null;
        }
}

Location.java

import java.util.ArrayList;
import java.util.List;


public class Location {

    private List<EXIT> exits;

    public Location(){
        exits = new ArrayList<EXIT>();
    }

    public void setExits(String exits) {
        for(char exitCode :  exits.toCharArray()){
            EXIT exit = EXIT.fromValue(exitCode);
            if(exit != null){
                this.exits.add(exit);
            }
        }
    }

    public boolean hasExit(String exitCode){
        return exits.contains(EXIT.fromValue(exitCode));
    }

    public boolean hasAnyExit(String exits){
        for(char exitCode :  exits.toCharArray()){
            if(this.exits.contains(EXIT.fromValue(exitCode))){
                return true;
            }
        }
        return false;
    }

    public boolean hasAllExit(String exits){
        for(char exitCode :  exits.toCharArray()){
            EXIT exit = EXIT.fromValue(exitCode);
            if(exit != null && !this.exits.contains(exit)){
                return false;
            }
        }
        return true;
    }

    public boolean hasExit(char exitCode){
        return exits.contains(EXIT.fromValue(exitCode));
    }

    public boolean hasNorthExit(){
        return exits.contains(EXIT.NORTH);
    }

    public boolean hasSouthExit(){
        return exits.contains(EXIT.SOUTH);
    }

    public List<EXIT> getExits() {
        return exits;
    }

    public static void main(String args[]) {
        String exits = "N E W";
        Location location = new Location();
        location.setExits(exits);
        System.out.println(location.getExits());
        System.out.println(location.hasExit('W'));
        System.out.println(location.hasAllExit("N W"));
        System.out.println(location.hasAnyExit("U D"));
        System.out.println(location.hasNorthExit());
    }
}

1
似乎很多代码根本无法实现。长度可以减半,而不会失去有用性。另外,为什么List<EXIT>。为什么不Set<EXIT>呢?
Boann 2014年

是的,也可以设置它。
Syam S 2014年

这是太多的代码。数量没有增加任何东西。
Bogdan Alexandru


1

如果您需要通用解决方案,则可以使用地图,它从键(在您的情况下为W,S,E ..)映射到相应的值(在您的情况下为布尔值)。

当您执行时set,您将更新与键关联的值。当您执行时get,您可以使用参数键并简单地检索键的值。地图中已经存在此功能,称为putget


3
Map <Object,Boolean>用于检查是否已设置某项或存在的某项在逻辑上与具有Set <Object>并使用该contains方法相同。如果对象在集合中,则对应的映射将具有TRUE,如果对象不在集合中,则对应的映射将具有FALSE。不是说您的答案是错误的,而是另一种思考方法,它使用的空间更少:)
丹·

0

我真的很喜欢为String分配出口的想法,因为它使代码简短易读。完成后,我看不到为什么要创建一个布尔数组。如果您有一个字符串,请使用它,尽管您可能想添加一些验证以防止意外分配包含不需要字符的字符串:

private String exits;

public void setExits(String e) {
    if (!e.matches("[NSEWUD ]*")) throw new IllegalArgumentException();
    exits = e;
}

我要添加的唯一一件事是canExit可以使用direction参数调用的方法。例如if (location.canExit('N')) ...

public boolean canExit(char direction) {
    return exits.indexOf(direction) >= 0;
}

我喜欢枚举,但是在这里使用它们似乎对我来说是过度工程,这将很快变得令人讨厌。


**编辑**:实际上,请不要这样做。它回答了错误的问题,并且做了不需要做的事情。我刚刚注意到@TimB的答案使用地图(EnumMap)将方向与房间相关联。这说得通。

我仍然觉得,如果您只需要跟踪出口的存在,那么String是简单有效的,而其他任何事情都会使它过于复杂。但是,仅知道哪些出口可用是没有用的。您将需要通过这些出口,并且除非您的游戏的布局非常简单,否则代码无法为每个方向推断正确的房间,因此您需要将每个方向与另一个房间明确关联。因此,似乎没有任何实际方法可用于任何接受指令列表的方法“ setExits”(无论它在内部如何实现)。


0
public void setExits(String e)
{
    String directions="NwSEUD";
    for(int i=0;i<directions.length();i++)
    {
        if(e.contains(""+directions.charAt(i)))
        {
            bexits[i]=true;
            break;
        }
    }
}

重复做同一件事的方法..


0

较长的else if语句链应替换为switch语句。

Enum只要不关心效率,s是存储此类值的最有表现力的方法。请记住,这enum是一个类,因此创建新的枚举与相应的开销相关联。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.