1

Hi currently I try to look if a List contains an element. Currently the List holds a Object which holds another Object that should be checked.

These are the classes that the two List contains:

case class SourceFile(name: String, path: String, date_changed: java.util.Date)
case class DBSourceFile(id: UUID, file: SourceFile)

So I have two lists list1: List[SourceFile] and list2: List[DBSourceFile] currently i make a foreach loop to acces the SourceFile in list1:

for(file <- files)

currently i check it with count but i think a contains would be way better but i can't do something like this:

dbFiles.count(dbFile => dbFile.file.name == diskFile.name && dbFile.file.path == diskFile.path)

when i use contains

what is the prefered method ?

That's how i did it at the moment:

def checkExistingFilesInDB() {
  for(diskFile <- diskFiles) {
    val dbFile = dbFiles.filter(dbFile => dbFile.file.name == diskFile.name && dbFile.file.path == diskFile.path)
    if(dbFile.length == 1) {
      //println(dbFile(0))
      if(!(diskFile == dbFile(0).file)) {
        Logger.info("File updated: " + dbFile(0) + " \nwith: " + diskFile)
        SourceFile.update(DBSourceFile(dbFile(0).id, diskFile))  
      }
    }
    else if (dbFile.length == 0) { 
      SourceFile.createSourceFile(diskFile)
      Logger.info("File inserted into Database: " + diskFile)
    }
    else {
      // TODO: What happens if more than 1 reference of the file is in the database
      Logger.error("File exists two times on the database")
    } 
  }
}
Christian Schmitt
  • 837
  • 16
  • 47

1 Answers1

2

How are you doing to be using this check, precisely? Too often, exists and contains are used in quite clunky ways and you can eliminate them entirely with more idiomatic Scala

For example, your question implies you are doing something a little like this:

val fileCheck = { dbFile: SourceFile => dbFile.name == diskFile.name && dbFile.path == diskFile.path }
if (list1 exists fileCheck ) {
  var Files = list1 filter fileCheck
  for (file <- Files) {
    // Do something with file
  }
}

You can achieve this much more cleanly with a for comprehension which uses a guard to filter files matching your condition, as in

for (file <- list1 if file.name == diskFile.name && file.path == diskFile.path) {
  // Do Something with file
}

On the other hand, if you wanted to do something different if there is no such match, like this:

val fileCheck = { dbFile: SourceFile => dbFile.name == diskFile.name && dbFile.path == diskFile.path }
if (list1 exists fileCheck ) {
  val Files = list1 filter fileCheck
  for (file <- Files) {
    // Do something with file
  }
} else {
  // Meh! No matching files.
}

Then you could, for example, use the for comprehension with yield to give up the list of matching files

val files = for (file <- list1 if file.name == diskFile.name && file.path == diskFile.path) yield file

And do one thing if it is empty (Nil) and another if it is not. I still prefer that to using exists to perform the check and then reusing the check parameters to filter the list. There are many expressive ways to do this but it depends on the context.

Note: the for comprehension is more functional, less imperative (particularly when used with yield)

EDIT:

OK, the above was written before you posted your code. Firstly, in this case, I'd say you should probably stick with val dbFile = dbFiles.filter(...; a list comprehension doesn't get you anything extra here, so using filter is more clear.

Secondly, you should use match. Match is almost always better than any chain of if...else if...else. A simple if...else is fine but else if is error prone.

Now, you could do it like this

val dbFile = dbFiles.filter(dbFile => dbFile.file.name == diskFile.name && dbFile.file.path == diskFile.path
dbFiles.length match {
  case 0 => //Insert file into db
  case 1 => //Update existing db record
  case _ => //Must be more than one!  Aroogah!  Aroogah!
}

Which is nice and simple and will match every case. But Christian, consider this: if you have multiple db records, all with the same name and path and there's a real file that corresponds, surely all you need to do is keep one of those records and update it, while deleting the others? Let me show you how do do this with match

dbFile match {
  case Nil => // Empty list, insert file into db
  case first :: others => { // At least one match
    for (errorFile <- others) {
      SourceFile.delete(errorFile.id) // Assuming you have this or similar method
    }
    SourceFile.update(DBSourceFile(first.id, diskFile))
  }
}

This works because first :: others will match both a list of one or more. If there is only one element, others will be Nil, so the for comprehension will do nothing . Otherwise, it will contain the list of additional entries. If you care about which of the multiple entries you keep, you probably want either the first or the last entry; you can do that by adding a sort (presumably by id) to the line that builds dbFile

So you get to use a for comprehension in the end ;)

itsbruce
  • 4,825
  • 26
  • 35
  • Currently I updated everything with filter instead exists, my code looks like your last snippet or nearly as your last snippet. I just check if filter gives me a list with 0, 1 or more elements, if i have more it gives an exception – Christian Schmitt Sep 04 '13 at 11:54
  • @ChristianSchmitt I'm not sure if you're telling me you tried this and you get an unexpected exception or if your code is designed to throw an exception if there are more than two elements. – itsbruce Sep 04 '13 at 14:09
  • no, i just said that i implemented it like your snippet, with just a small change that i have an if-else which looks up the list length of the filtered list. if the filter list has 1 element it does something if the list has 0 elements it does something and if the list has more than 1 element it will throw an exception. I'm still feeling bad, if i wrote idomatic scala. i update my question with my new code. – Christian Schmitt Sep 04 '13 at 14:15
  • @ChristianSchmitt Ah, I see you updated your question. OK, when I have time later I will come back with more suggestions. – itsbruce Sep 04 '13 at 14:32
  • Updated the answer. Left the orignal, general recommendations in place and added specific ideas that match your actual code. – itsbruce Sep 04 '13 at 21:46
  • Wow that is really, really awesome and that's what i've been looking for. Just one thing i don't want to delete other records i just want to log them. since it should **never** happen that there are more than one entry, but due the data comes from an smb path i'm scared that the entries could getting inconsistent, but still your example matches this case as well, just a .log instead of delete ;) i didn't thought that match is so powerful... i'm really starting to fell in love with scala. – Christian Schmitt Sep 04 '13 at 22:15
  • If you have logged them, why keep them in the db? At least, why in the same table? You could even move them out of the main table into a duplicates table if you really wanted to keep a record. Or you could put constraints on the db table that stop duplicates being entered at all - then you would find out exactly when something had gone wrong. – itsbruce Sep 05 '13 at 06:52
  • hm that's true at all. But i can't make a unique constraint on the path or at the filename, i would need to make a new column with both, the filename and filepath since filenames could aren't unique (they could occur two times, just not on the same folder) – Christian Schmitt Sep 05 '13 at 07:44