Skip to content

Commit

Permalink
Add parse for PrefLib data files.
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 597287351
Change-Id: Ica9550288e611f54187f63f88167dd4c47c87004
  • Loading branch information
lanctot committed Jan 23, 2024
1 parent 6679ada commit 271dd67
Show file tree
Hide file tree
Showing 2 changed files with 139 additions and 0 deletions.
79 changes: 79 additions & 0 deletions open_spiel/python/voting/preflib_util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Copyright 2023 DeepMind Technologies Limited
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Helpers to work with PrefLib data."""

import pyspiel
from open_spiel.python.voting import base


def parse_preflib_data(string_data: str) -> base.PreferenceProfile:
"""Parses the contents of a PrefLib data file.
Currently only supports SOC and SOI. See https://www.preflib.org/format.
Args:
string_data: the name of the file to parse.
Returns:
A preference profile.
"""
lines = string_data.split("\n")
alternatives = []
num_alternatives = None
num_votes = None
profile = base.PreferenceProfile()
for raw_line in lines:
line = raw_line.strip()
if not line: continue
if line.startswith("#"):
parts = line.split(" ")
if line.startswith("# DATA TYPE: "):
assert(parts[3] == "soc" or parts[3] == "soi")
elif line.startswith("# NUMBER ALTERNATIVES:"):
num_alternatives = int(parts[3])
alternatives = [None] * num_alternatives
elif line.startswith("# NUMBER VOTERS:"):
num_votes = int(parts[3])
elif line.startswith("# ALTERNATIVE NAME "):
num = int(parts[3].split(":")[0])
index_of_colon = line.index(":")
assert 1 <= num <= num_alternatives
alternatives[num-1] = line[index_of_colon+2:]
else:
if profile.num_alternatives() == 0:
profile = base.PreferenceProfile(alternatives=alternatives)
index_of_colon = line.index(":")
weight = int(line[:index_of_colon])
vote_parts = line[index_of_colon+2:].split(",")
vote = [alternatives[int(part) - 1] for part in vote_parts]
if weight > 0:
profile.add_vote(vote, weight)
assert num_votes == profile.num_votes()
return profile


def parse_preflib_datafile(filename: str) -> base.PreferenceProfile:
"""Parses a Preflib data file.
Currently only supports SOC and SOI. See https://www.preflib.org/format.
Args:
filename: the name of the file to parse.
Returns:
A preference profile.
"""
contents = pyspiel.read_contents_from_file(filename, "r")
return parse_preflib_data(contents)
60 changes: 60 additions & 0 deletions open_spiel/python/voting/preflib_util_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Copyright 2023 DeepMind Technologies Limited
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Tests for open_spiel.python.voting.util."""

from absl.testing import absltest
from open_spiel.python.voting import preflib_util

TEST_DATA = """
# FILE NAME: 00004-00000050.soc
# TITLE: Netflix Prize Data
# DESCRIPTION:
# DATA TYPE: soc
# MODIFICATION TYPE: induced
# RELATES TO:
# RELATED FILES:
# PUBLICATION DATE: 2013-08-17
# MODIFICATION DATE: 2022-09-16
# NUMBER ALTERNATIVES: 3
# NUMBER VOTERS: 391
# NUMBER UNIQUE ORDERS: 6
# ALTERNATIVE NAME 1: The Amityville Horror
# ALTERNATIVE NAME 2: Mars Attacks!
# ALTERNATIVE NAME 3: Lean on Me
186: 3,1,2
71: 1,3,2
58: 3,2,1
45: 2,3,1
18: 1,2,3
13: 2,1,3
"""


class UtilTest(absltest.TestCase):
def test_load_preflib(self):
print(TEST_DATA)
profile = preflib_util.parse_preflib_data(TEST_DATA)
print(profile)
self.assertEqual(profile.num_alternatives(), 3)
self.assertEqual(profile.num_votes(), 391)
self.assertListEqual(profile.alternatives, [
"The Amityville Horror", "Mars Attacks!", "Lean on Me"
])
print(profile.alternatives)
print(profile.margin_matrix())


if __name__ == "__main__":
absltest.main()

0 comments on commit 271dd67

Please sign in to comment.